commit 2a2772a5ebc74b99ec2b66d53383cb0d6e0a278a Author: 18226920803 Date: Mon Jun 8 11:46:28 2026 +0800 框架提交 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..51b7718 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# Maven +**/target/ + +# IDE +.idea/ +*.iml +.vscode/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log + +# Environment +.env +.env.local +**/package-lock.json diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..477a1b2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 1024-lab + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/smart-flow-api/pom.xml b/smart-flow-api/pom.xml new file mode 100644 index 0000000..f71c794 --- /dev/null +++ b/smart-flow-api/pom.xml @@ -0,0 +1,476 @@ + + 4.0.0 + + net.lab1024 + sa-parent + 3.0.1 + pom + + sa-parent + SmartFlow project + + + sa-base + sa-admin + warm-flow + + + + 17 + UTF-8 + UTF-8 + 3.3.1 + 2.0.8 + 6.4.3 + 3.5.7 + + 3.9.1 + 4.4.0 + 2.0.52 + 1.2.23 + 1.4.2 + 20.0 + 1.21 + 0.9.11 + 2.15.0 + 3.12.0 + 4.4 + 1.26.0 + 1.13 + 1.9 + 2.12.0 + 1.0.0 + 5.2.4 + 1.4 + 1.11.842 + 2.23.1 + 5.8.29 + 2.3 + 0.9.1 + 0.9.0 + 3.1 + 1.41.0 + 1.8.4 + 2.7.0 + 1.80 + 2.13.4 + 2.16.1 + 1.2.0 + 3.25.0 + 2.2 + 2.3.33 + 1.18.1 + 3.1.0 + 8.6.0 + + + + + + + + org.springframework.boot + spring-boot-dependencies + ${springboot.version} + pom + import + + + + + com.baomidou + mybatis-plus-spring-boot3-starter + ${mybatis-plus.version} + + + org.springframework.boot + spring-boot-starter-logging + + + org.mybatis + mybatis + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + + + + + + + + org.mybatis + mybatis + 3.5.16 + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 3.0.3 + + + + org.springframework.security + spring-security-crypto + ${spring-security-crypto.version} + + + + p6spy + p6spy + ${p6spy.version} + + + + cn.com.kingbase + kingbase8 + ${kingbase.version} + + + + com.github.xiaoymin + knife4j-openapi3-jakarta-spring-boot-starter + ${knife4j.version} + + + + com.alibaba + fastjson + ${fastjson.version} + + + + + com.alibaba + druid-spring-boot-3-starter + ${druid.version} + + + + com.googlecode.concurrentlinkedhashmap + concurrentlinkedhashmap-lru + ${google-linkedhashmap.version} + + + + com.google.guava + guava + ${google-guava.version} + + + + eu.bitwalker + UserAgentUtils + ${user-agent-utils.version} + + + + org.reflections + reflections + ${reflections.version} + + + guava + com.google.guava + + + + + + commons-io + commons-io + ${commons-io.version} + + + + org.apache.commons + commons-lang3 + ${commons-lang3.version} + + + + org.apache.commons + commons-collections4 + ${commons-collections4.version} + + + + org.apache.commons + commons-compress + ${commons-compress.version} + + + + commons-codec + commons-codec + ${commons-codec.version} + + + + com.amazonaws + aws-java-sdk-s3 + ${aws-java-sdk.version} + + + commons-logging + commons-logging + + + + + + org.apache.commons + commons-text + ${commons-text.version} + + + + cn.hutool + hutool-all + ${hutool.version} + + + + io.jsonwebtoken + jjwt + ${jjwt.version} + + + + com.auth0 + jwks-rsa + ${jwks-rsa.version} + + + + + org.apache.velocity + velocity-engine-core + ${velocity-engine-core.version} + + + org.apache.velocity.tools + velocity-tools-generic + ${velocity-tools.version} + + + + + + cn.dev33 + sa-token-spring-boot3-starter + ${sa-token.version} + + + + + cn.dev33 + sa-token-redis-jackson + ${sa-token.version} + + + + + + org.dromara.warm + warm-flow-mybatis-plus-sb3-starter + ${warm-flow.version} + pom + import + + + org.dromara.warm + warm-flow-plugin-ui-sb-web + ${warm-flow.version} + pom + import + + + + + + org.lionsoul + ip2region + ${ip2region.version} + + + + org.bouncycastle + bcprov-jdk18on + ${bcprov.version} + + + + cn.idev.excel + fastexcel + ${fast-excel.version} + + + logback-classic + ch.qos.logback + + + + + + org.apache.poi + poi + ${poi.version} + + + + org.apache.poi + poi-ooxml + ${poi.version} + + + + org.apache.poi + poi-scratchpad + ${poi.version} + + + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${jackson-datatype-jsr310.version} + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + ${jackson-dataformat-yaml.version} + + + + net.1024lab + smartdb + ${smartdb.version} + + + + + org.redisson + redisson-spring-boot-starter + ${redisson.version} + + + + org.yaml + snakeyaml + ${snakeyaml.version} + + + + org.jsoup + jsoup + ${jsoup.version} + + + + org.freemarker + freemarker + ${freemarker.version} + + + + org.apache.tika + tika-core + ${tika.version} + + + + + + + + ${project.name}-${profiles.active}-${project.version} + + + false + src/main/resources + + dev/* + test/* + pre/* + prod/* + + + + + src/main/resources/${profiles.active} + true + + *.yaml + + + + + src/main/resources/${profiles.active} + false + + *.* + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + -parameters + 17 + 17 + UTF-8 + + + + org.springframework.boot + spring-boot-maven-plugin + ${springboot.version} + + + + + + + + dev + + dev + + + true + + + + + test + + test + + + + + pre + + pre + + + + + prod + + prod + + + + + diff --git a/smart-flow-api/sa-admin/pom.xml b/smart-flow-api/sa-admin/pom.xml new file mode 100644 index 0000000..2f76617 --- /dev/null +++ b/smart-flow-api/sa-admin/pom.xml @@ -0,0 +1,103 @@ + + 4.0.0 + + net.lab1024 + sa-parent + 3.0.1 + ../pom.xml + + + sa-admin + 3.0.1 + jar + + sa-admin + sa-admin project + + + + + net.lab1024 + sa-base + 3.0.1 + + + + + com.baomidou + mybatis-plus-spring-boot3-starter + ${mybatis-plus.version} + + + + com.baomidou + mybatis-plus-extension + ${mybatis-plus.version} + + + + org.dromara.warm + warm-flow-mybatis-plus-sb3-starter + ${warm-flow.version} + + + org.dromara.warm + warm-flow-plugin-ui-sb-web + ${warm-flow.version} + + + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.6.1 + + + copy-dependencies + package + + copy-dependencies + + + ${project.build.directory}/lib + net.lab1024 + runtime + false + false + true + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${springboot.version} + + + + repackage + + + + + net.lab1024.sa.AdminApplication + ZIP + + + + net.lab1024 + sa-base + + + + + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/AdminApplication.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/AdminApplication.java new file mode 100644 index 0000000..1067011 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/AdminApplication.java @@ -0,0 +1,40 @@ +package net.lab1024.sa; + +import net.lab1024.sa.base.listener.Ip2RegionListener; +import net.lab1024.sa.base.listener.LogVariableListener; +import org.apache.ibatis.annotations.Mapper; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.scheduling.annotation.EnableScheduling; + +/** + * SmartAdmin 项目启动类 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2022-08-29 21:00:58 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@EnableCaching +@EnableScheduling +@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true) +@ComponentScan(AdminApplication.COMPONENT_SCAN) +@MapperScan(value = AdminApplication.COMPONENT_SCAN, annotationClass = Mapper.class) +@SpringBootApplication(exclude = {UserDetailsServiceAutoConfiguration.class}) +public class AdminApplication { + + public static final String COMPONENT_SCAN = "net.lab1024.sa"; + + public static void main(String[] args) { + SpringApplication application = new SpringApplication(AdminApplication.class); + // 添加 日志监听器,使 log4j2-spring.xml 可以间接读取到配置文件的属性 + application.addListeners(new LogVariableListener(), new Ip2RegionListener()); + application.run(args); + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/config/MvcConfig.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/config/MvcConfig.java new file mode 100644 index 0000000..c30c5ae --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/config/MvcConfig.java @@ -0,0 +1,44 @@ +package net.lab1024.sa.admin.config; + +import jakarta.annotation.Resource; +import net.lab1024.sa.admin.interceptor.AdminInterceptor; +import net.lab1024.sa.base.config.SwaggerConfig; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * web相关配置 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021-09-02 20:21:10 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Configuration +public class MvcConfig implements WebMvcConfigurer { + + @Resource + private AdminInterceptor adminInterceptor; + + + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(adminInterceptor) + .excludePathPatterns(SwaggerConfig.SWAGGER_WHITELIST) + .addPathPatterns("/**") + // 以上是sa-token案例,下面才是需要排除的地址 + .excludePathPatterns("/warm-flow-ui/**", "/warm-flow/**"); + } + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/"); + registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/"); + registry.addResourceHandler("/warm-flow-ui/**").addResourceLocations("classpath:/META-INF/resources/warm-flow-ui/"); + } + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/config/OperateLogAspectConfig.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/config/OperateLogAspectConfig.java new file mode 100644 index 0000000..bccbfb4 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/config/OperateLogAspectConfig.java @@ -0,0 +1,28 @@ +package net.lab1024.sa.admin.config; + +import net.lab1024.sa.base.module.support.operatelog.core.OperateLogAspect; +import net.lab1024.sa.base.module.support.operatelog.core.OperateLogConfig; +import org.springframework.context.annotation.Configuration; + +/** + * 操作日志切面 配置 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022-05-30 21:22:12 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Configuration +public class OperateLogAspectConfig extends OperateLogAspect{ + + /** + * 配置信息 + */ + @Override + public OperateLogConfig getOperateLogConfig() { + return OperateLogConfig.builder().corePoolSize(1).queueCapacity(10000).build(); + } + + +} \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/constant/AdminCacheConst.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/constant/AdminCacheConst.java new file mode 100644 index 0000000..10df40c --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/constant/AdminCacheConst.java @@ -0,0 +1,68 @@ +package net.lab1024.sa.admin.constant; + +import net.lab1024.sa.base.constant.CacheKeyConst; + +/** + * 缓存 key + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2022-01-07 18:59:22 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class AdminCacheConst extends CacheKeyConst { + + public static class Department { + + /** + * 部门列表 + */ + public static final String DEPARTMENT_LIST_CACHE = "department_list_cache"; + + /** + * 部门map + */ + public static final String DEPARTMENT_MAP_CACHE = "department_map_cache"; + + /** + * 部门树 + */ + public static final String DEPARTMENT_TREE_CACHE = "department_tree_cache"; + + /** + * 某个部门以及下级的id列表 + */ + public static final String DEPARTMENT_SELF_CHILDREN_CACHE = "department_self_children_cache"; + + /** + * 部门路径 缓存 + */ + public static final String DEPARTMENT_PATH_CACHE = "department_path_cache"; + + } + + public static class Employee { + public static final String EMPLOEE_LIST_CACHE = "employee_list_cache"; + + } + + public static class Role { + + public static final String ROLE_LIST_CACHE = "role_list_cache"; + + } + + /** + * 分类相关缓存 + */ + public static class Category { + + public static final String CATEGORY_ENTITY = "category_cache"; + + public static final String CATEGORY_SUB = "category_sub_cache"; + + public static final String CATEGORY_TREE = "category_tree_cache"; + } + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/constant/AdminRedisKeyConst.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/constant/AdminRedisKeyConst.java new file mode 100644 index 0000000..07cc8f6 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/constant/AdminRedisKeyConst.java @@ -0,0 +1,17 @@ +package net.lab1024.sa.admin.constant; + +import net.lab1024.sa.base.constant.RedisKeyConst; + +/** + * redis key 常量类 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2022-01-07 18:59:22 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class AdminRedisKeyConst extends RedisKeyConst { + + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/constant/AdminSwaggerTagConst.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/constant/AdminSwaggerTagConst.java new file mode 100644 index 0000000..5c226a6 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/constant/AdminSwaggerTagConst.java @@ -0,0 +1,70 @@ +package net.lab1024.sa.admin.constant; + +import net.lab1024.sa.base.constant.SwaggerTagConst; + +/** + * swagger + * + * @Author 1024创新实验室:罗伊 + * @Date 2022-01-07 18:59:22 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class AdminSwaggerTagConst extends SwaggerTagConst { + + public static class Business { + public static final String MANAGER_CATEGORY = "ERP进销存-分类管理"; + + public static final String MANAGER_GOODS = "ERP进销存-商品管理"; + + public static final String OA_BANK = "OA办公-银行卡信息"; + + public static final String OA_ENTERPRISE = "OA办公-企业"; + + public static final String OA_INVOICE = "OA办公-发票信息"; + + public static final String OA_NOTICE = "OA办公-通知公告"; + + } + + + public static class System { + + public static final String SYSTEM_LOGIN = "系统-员工登录"; + + public static final String SYSTEM_EMPLOYEE = "系统-员工管理"; + + public static final String SYSTEM_DEPARTMENT = "系统-部门管理"; + + public static final String SYSTEM_MENU = "系统-菜单"; + + public static final String SYSTEM_DATA_SCOPE = "系统-系统-数据范围"; + + public static final String SYSTEM_ROLE = "系统-角色"; + + public static final String SYSTEM_ROLE_DATA_SCOPE = "系统-角色-数据范围"; + + public static final String SYSTEM_ROLE_EMPLOYEE = "系统-角色-员工"; + + public static final String SYSTEM_ROLE_MENU = "系统-角色-菜单"; + + public static final String SYSTEM_POSITION = "系统 - 职务管理"; + + public static final String SYSTEM_REGION = "系统 - 区域管理"; + + public static final String SYSTEM_MESSAGE = "系统 - 消息"; + + + + } + public static class Flow { + + public static final String FLOW_DEFINITION = "工作流-流程定义"; + + public static final String FLOW_INSTANCE = "工作流-流程实例"; + + } + + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/interceptor/AdminInterceptor.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/interceptor/AdminInterceptor.java new file mode 100644 index 0000000..5bf8c73 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/interceptor/AdminInterceptor.java @@ -0,0 +1,143 @@ +package net.lab1024.sa.admin.interceptor; + +import cn.dev33.satoken.annotation.SaIgnore; +import cn.dev33.satoken.exception.SaTokenException; +import cn.dev33.satoken.stp.StpUtil; +import cn.dev33.satoken.strategy.SaAnnotationStrategy; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.admin.module.system.login.domain.RequestEmployee; +import net.lab1024.sa.admin.module.system.login.service.LoginService; +import net.lab1024.sa.base.common.annoation.NoNeedLogin; +import net.lab1024.sa.base.common.code.SystemErrorCode; +import net.lab1024.sa.base.common.code.UserErrorCode; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartRequestUtil; +import net.lab1024.sa.base.common.util.SmartResponseUtil; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; + +import java.lang.reflect.Method; + +/** + * admin 拦截器 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/7/26 20:20:33 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室,Since 2012 + */ + +@Component +@Slf4j +public class AdminInterceptor implements HandlerInterceptor { + + @Resource + private LoginService loginService; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + + // OPTIONS请求直接return + if (HttpMethod.OPTIONS.toString().equals(request.getMethod())) { + response.setStatus(HttpStatus.NO_CONTENT.value()); + return false; + } + + boolean isHandler = handler instanceof HandlerMethod; + if (!isHandler) { + return true; + } + + try { + // --------------- 第一步: 根据token 获取用户 --------------- + + String tokenValue = StpUtil.getTokenValue(); + String loginId = (String) StpUtil.getLoginIdByToken(tokenValue); + RequestEmployee requestEmployee = loginService.getLoginEmployee(loginId, request); + + // --------------- 第二步: 校验 登录 --------------- + + Method method = ((HandlerMethod) handler).getMethod(); + NoNeedLogin noNeedLogin = ((HandlerMethod) handler).getMethodAnnotation(NoNeedLogin.class); + if (noNeedLogin != null) { + checkActiveTimeout(requestEmployee); + return true; + } + + if (requestEmployee == null) { + SmartResponseUtil.write(response, ResponseDTO.error(UserErrorCode.LOGIN_STATE_INVALID)); + return false; + } + + // 检测token 活跃频率 + checkActiveTimeout(requestEmployee); + + + // --------------- 第三步: 校验 权限 --------------- + + SmartRequestUtil.setRequestUser(requestEmployee); + if (SaAnnotationStrategy.instance.isAnnotationPresent.apply(method, SaIgnore.class)) { + return true; + } + + // 如果是超级管理员的话,不需要校验权限 + if (requestEmployee.getAdministratorFlag()) { + return true; + } + + SaAnnotationStrategy.instance.checkMethodAnnotation.accept(method); + + } catch (SaTokenException e) { + /* + * sa-token 异常状态码 + * 具体请看: https://sa-token.cc/doc.html#/fun/exception-code + */ + int code = e.getCode(); + if (code == 11041 || code == 11051) { + SmartResponseUtil.write(response, ResponseDTO.error(UserErrorCode.NO_PERMISSION)); + } else if (code == 11016) { + SmartResponseUtil.write(response, ResponseDTO.error(UserErrorCode.LOGIN_ACTIVE_TIMEOUT)); + } else if (code >= 11011 && code <= 11015) { + SmartResponseUtil.write(response, ResponseDTO.error(UserErrorCode.LOGIN_STATE_INVALID)); + } else { + SmartResponseUtil.write(response, ResponseDTO.error(UserErrorCode.PARAM_ERROR)); + } + return false; + } catch (Throwable e) { + SmartResponseUtil.write(response, ResponseDTO.error(SystemErrorCode.SYSTEM_ERROR)); + log.error(e.getMessage(), e); + return false; + } + + // 通过验证 + return true; + } + + + /** + * 检测:token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结 + */ + private void checkActiveTimeout(RequestEmployee requestEmployee) { + // 用户不在线,也不用检测 + if (requestEmployee == null) { + return; + } + + StpUtil.checkActiveTimeout(); + StpUtil.updateLastActiveToNow(); + } + + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { + // 清除上下文 + SmartRequestUtil.remove(); + } +} \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/constant/CategoryTypeEnum.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/constant/CategoryTypeEnum.java new file mode 100644 index 0000000..c3be511 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/constant/CategoryTypeEnum.java @@ -0,0 +1,36 @@ +package net.lab1024.sa.admin.module.business.category.constant; + + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 分类类型 枚举 + * + * @Author 1024创新实验室: 胡克 + * @Date 2021/08/05 21:26:58 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@AllArgsConstructor +@Getter +public enum CategoryTypeEnum implements BaseEnum { + + /** + * 1 商品 + */ + GOODS(1, "商品"), + + /** + * 2 自定义 + */ + CUSTOM(2, "自定义"), + + ; + + private final Integer value; + + private final String desc; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/controller/CategoryController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/controller/CategoryController.java new file mode 100644 index 0000000..2c7ff73 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/controller/CategoryController.java @@ -0,0 +1,69 @@ +package net.lab1024.sa.admin.module.business.category.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import net.lab1024.sa.admin.constant.AdminSwaggerTagConst; +import net.lab1024.sa.admin.module.business.category.domain.form.CategoryAddForm; +import net.lab1024.sa.admin.module.business.category.domain.form.CategoryTreeQueryForm; +import net.lab1024.sa.admin.module.business.category.domain.form.CategoryUpdateForm; +import net.lab1024.sa.admin.module.business.category.domain.vo.CategoryTreeVO; +import net.lab1024.sa.admin.module.business.category.domain.vo.CategoryVO; +import net.lab1024.sa.admin.module.business.category.service.CategoryService; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 类目 + * + * @Author 1024创新实验室: 胡克 + * @Date 2021/08/05 21:26:58 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@RestController +@Tag(name = AdminSwaggerTagConst.Business.MANAGER_CATEGORY) +public class CategoryController { + + @Resource + private CategoryService categoryService; + + @Operation(summary = "添加类目 @author 胡克") + @PostMapping("/category/add") + @SaCheckPermission("category:add") + public ResponseDTO add(@RequestBody @Valid CategoryAddForm addForm) { + return categoryService.add(addForm); + } + + @Operation(summary = "更新类目 @author 胡克") + @PostMapping("/category/update") + @SaCheckPermission("category:update") + public ResponseDTO update(@RequestBody @Valid CategoryUpdateForm updateForm) { + return categoryService.update(updateForm); + } + + @Operation(summary = "查询类目详情 @author 胡克") + @GetMapping("/category/{categoryId}") + public ResponseDTO queryDetail(@PathVariable Long categoryId) { + return categoryService.queryDetail(categoryId); + } + + @Operation(summary = "查询类目层级树 @author 胡克") + @PostMapping("/category/tree") + @SaCheckPermission("category:tree") + public ResponseDTO> queryTree(@RequestBody @Valid CategoryTreeQueryForm queryForm) { + return categoryService.queryTree(queryForm); + } + + @Operation(summary = "删除类目 @author 胡克") + @GetMapping("/category/delete/{categoryId}") + @SaCheckPermission("category:delete") + public ResponseDTO delete(@PathVariable Long categoryId) { + return categoryService.delete(categoryId); + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/dao/CategoryDao.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/dao/CategoryDao.java new file mode 100644 index 0000000..c107b26 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/dao/CategoryDao.java @@ -0,0 +1,62 @@ +package net.lab1024.sa.admin.module.business.category.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import net.lab1024.sa.admin.module.business.category.constant.CategoryTypeEnum; +import net.lab1024.sa.admin.module.business.category.domain.entity.CategoryEntity; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 类目 dao + * + * @Author 1024创新实验室: 胡克 + * @Date 2021/08/05 21:26:58 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface CategoryDao extends BaseMapper { + + /** + * 根据父级id 类型 查询子类 + * + * @param parentIdList 父级id集合 + * @param deletedFlag 删除标识 + * @return 列表 + */ + List queryByParentId(@Param("parentIdList") List parentIdList, + @Param("deletedFlag") Boolean deletedFlag); + + /** + * 根据父级id 类型 查询子类 + * + * @param parentIdList 父级id集合 + * @param categoryType {@link CategoryTypeEnum} + * @param deletedFlag 删除标识 + * @return 列表 + */ + List queryByParentIdAndType(@Param("parentIdList") List parentIdList, + @Param("categoryType") Integer categoryType, + @Param("deletedFlag") Boolean deletedFlag); + + /** + * 某个类型的所有 + */ + List queryByType(@Param("categoryType") Integer categoryType, + @Param("deletedFlag") Boolean deletedFlag); + + /** + * 根据类型和id查询 + */ + CategoryEntity selectByTypeAndId(@Param("categoryType") Integer categoryType, @Param("categoryId") Long categoryId); + + /** + * 查看类目 具体条件 看sql + */ + CategoryEntity selectOne(CategoryEntity entity); + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/domain/dto/CategoryBaseDTO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/domain/dto/CategoryBaseDTO.java new file mode 100644 index 0000000..4849d60 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/domain/dto/CategoryBaseDTO.java @@ -0,0 +1,43 @@ +package net.lab1024.sa.admin.module.business.category.domain.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import net.lab1024.sa.admin.module.business.category.constant.CategoryTypeEnum; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; +import org.hibernate.validator.constraints.Length; + +/** + * 类目 基础属性 DTO 类 + * + * @author 胡克 + * @date 2021/1/20 16:17 + */ +@Data +public class CategoryBaseDTO { + + @Schema(description = "类目名称", required = true) + @NotBlank(message = "类目名称不能为空") + @Length(max = 20, message = "类目名称最多20字符") + private String categoryName; + + @SchemaEnum(desc = "分类类型", value = CategoryTypeEnum.class) + @CheckEnum(value = CategoryTypeEnum.class, required = true, message = "分类错误") + private Integer categoryType; + + @Schema(description = "父级类目id|可选") + private Long parentId; + + @Schema(description = "排序|可选") + private Integer sort; + + @Schema(description = "备注|可选") + @Length(max = 200, message = "备注最多200字符") + private String remark; + + @Schema(description = "禁用状态") + @NotNull(message = "禁用状态不能为空") + private Boolean disabledFlag; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/domain/dto/CategorySimpleDTO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/domain/dto/CategorySimpleDTO.java new file mode 100644 index 0000000..97d0310 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/domain/dto/CategorySimpleDTO.java @@ -0,0 +1,26 @@ +package net.lab1024.sa.admin.module.business.category.domain.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 类目 基础属性 DTO 类 + * + * @author 胡克 + * @date 2021/1/20 16:17 + */ +@Data +public class CategorySimpleDTO { + + @Schema(description = "类目id") + private Long categoryId; + + @Schema(description = "类目名称") + private String categoryName; + + @Schema(description = "类目层级全称") + private String categoryFullName; + + @Schema(description = "父级id") + private Long parentId; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/domain/entity/CategoryEntity.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/domain/entity/CategoryEntity.java new file mode 100644 index 0000000..b8d0a77 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/domain/entity/CategoryEntity.java @@ -0,0 +1,70 @@ +package net.lab1024.sa.admin.module.business.category.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import net.lab1024.sa.admin.module.business.category.constant.CategoryTypeEnum; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 类目 实体类 + * + * @Author 1024创新实验室: 胡克 + * @Date 2021/08/05 21:26:58 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName("t_category") +public class CategoryEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId(type = IdType.AUTO) + private Long categoryId; + + /** + * 类目名称 + */ + private String categoryName; + + /** + * 类目 类型 + * + * @see CategoryTypeEnum + */ + private Integer categoryType; + + /** + * 父级类目id + */ + private Long parentId; + + /** + * 是否禁用 + */ + private Boolean disabledFlag; + + /** + * 排序 + */ + private Integer sort; + + /** + * 删除状态 + */ + private Boolean deletedFlag; + + /** + * 备注 + */ + private String remark; + + private LocalDateTime updateTime; + + private LocalDateTime createTime; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/domain/form/CategoryAddForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/domain/form/CategoryAddForm.java new file mode 100644 index 0000000..df2a169 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/domain/form/CategoryAddForm.java @@ -0,0 +1,47 @@ +package net.lab1024.sa.admin.module.business.category.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import net.lab1024.sa.admin.module.business.category.constant.CategoryTypeEnum; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; +import org.hibernate.validator.constraints.Length; + +/** + * 类目 添加 + * + * @Author 1024创新实验室: 胡克 + * @Date 2021/08/05 21:26:58 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class CategoryAddForm { + + @Schema(description = "类目名称", required = true) + @NotBlank(message = "类目名称不能为空") + @Length(max = 20, message = "类目名称最多20字符") + private String categoryName; + + @SchemaEnum(desc = "分类类型", value = CategoryTypeEnum.class) + @CheckEnum(value = CategoryTypeEnum.class, required = true, message = "分类错误") + private Integer categoryType; + + @Schema(description = "父级类目id|可选") + private Long parentId; + + @Schema(description = "排序|可选") + private Integer sort; + + @Schema(description = "备注|可选") + @Length(max = 200, message = "备注最多200字符") + private String remark; + + @Schema(description = "禁用状态") + @NotNull(message = "禁用状态不能为空") + private Boolean disabledFlag; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/domain/form/CategoryTreeQueryForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/domain/form/CategoryTreeQueryForm.java new file mode 100644 index 0000000..87bf072 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/domain/form/CategoryTreeQueryForm.java @@ -0,0 +1,25 @@ +package net.lab1024.sa.admin.module.business.category.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.admin.module.business.category.constant.CategoryTypeEnum; +import net.lab1024.sa.base.common.swagger.SchemaEnum; + +/** + * 类目 层级树查询 + * + * @Author 1024创新实验室: 胡克 + * @Date 2021/08/05 21:26:58 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class CategoryTreeQueryForm { + + @SchemaEnum(desc = "分类类型|可选", value = CategoryTypeEnum.class) + private Integer categoryType; + + @Schema(description = "父级类目id|可选") + private Long parentId; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/domain/form/CategoryUpdateForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/domain/form/CategoryUpdateForm.java new file mode 100644 index 0000000..bfa4491 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/domain/form/CategoryUpdateForm.java @@ -0,0 +1,22 @@ +package net.lab1024.sa.admin.module.business.category.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 类目 更新 + * + * @Author 1024创新实验室: 胡克 + * @Date 2021/08/05 21:26:58 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class CategoryUpdateForm extends CategoryAddForm { + + @Schema(description = "类目id") + @NotNull(message = "类目id不能为空") + private Long categoryId; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/domain/vo/CategoryTreeVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/domain/vo/CategoryTreeVO.java new file mode 100644 index 0000000..f847b40 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/domain/vo/CategoryTreeVO.java @@ -0,0 +1,43 @@ +package net.lab1024.sa.admin.module.business.category.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 类目 层级树 vo + * + * @Author 1024创新实验室: 胡克 + * @Date 2021/08/05 21:26:58 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class CategoryTreeVO implements Serializable { + + private static final long serialVersionUID = 1L; + + @Schema(description = "类目id") + private Long categoryId; + + @Schema(description = "类目名称") + private String categoryName; + + @Schema(description = "类目层级全称") + private String categoryFullName; + + @Schema(description = "父级id") + private Long parentId; + + @Schema(description = "类目id") + private Long value; + + @Schema(description = "类目名称") + private String label; + + @Schema(description = "子类") + private List children; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/domain/vo/CategoryVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/domain/vo/CategoryVO.java new file mode 100644 index 0000000..1436958 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/domain/vo/CategoryVO.java @@ -0,0 +1,46 @@ +package net.lab1024.sa.admin.module.business.category.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.admin.module.business.category.constant.CategoryTypeEnum; +import net.lab1024.sa.base.common.swagger.SchemaEnum; + +import java.time.LocalDateTime; + +/** + * 类目 + * + * @Author 1024创新实验室: 胡克 + * @Date 2021/08/05 21:26:58 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class CategoryVO { + + @Schema(description = "类目名称", required = true) + private String categoryName; + + @SchemaEnum(desc = "分类类型", value = CategoryTypeEnum.class) + private Integer categoryType; + + @Schema(description = "父级类目id|可选") + private Long parentId; + + @Schema(description = "排序|可选") + private Integer sort; + + @Schema(description = "备注|可选") + private String remark; + + @Schema(description = "禁用状态") + private Boolean disabledFlag; + + @Schema(description = "类目id") + private Long categoryId; + + private LocalDateTime updateTime; + + private LocalDateTime createTime; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/manager/CategoryCacheManager.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/manager/CategoryCacheManager.java new file mode 100644 index 0000000..72a2be6 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/manager/CategoryCacheManager.java @@ -0,0 +1,113 @@ +package net.lab1024.sa.admin.module.business.category.manager; + +import com.google.common.collect.Lists; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.admin.constant.AdminCacheConst; +import net.lab1024.sa.admin.module.business.category.dao.CategoryDao; +import net.lab1024.sa.admin.module.business.category.domain.entity.CategoryEntity; +import net.lab1024.sa.admin.module.business.category.domain.vo.CategoryTreeVO; +import net.lab1024.sa.base.common.constant.StringConst; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 类目 查询 缓存 + * + * @Author 1024创新实验室: 胡克 + * @Date 2021/08/05 21:26:58 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +@Slf4j +public class CategoryCacheManager { + + + @Resource + private CategoryDao categoryDao; + + + /** + * 根据类目id 移除缓存 + */ + @CacheEvict(value = {AdminCacheConst.Category.CATEGORY_ENTITY, AdminCacheConst.Category.CATEGORY_SUB, AdminCacheConst.Category.CATEGORY_TREE}, allEntries = true) + public void removeCache() { + log.info("clear CATEGORY ,CATEGORY_SUB ,CATEGORY_TREE"); + } + + /** + * 查詢类目 + * + */ + @Cacheable(AdminCacheConst.Category.CATEGORY_ENTITY) + public CategoryEntity queryCategory(Long categoryId) { + return categoryDao.selectById(categoryId); + } + + /** + * 查询类目 子级 + * + */ + @Cacheable(AdminCacheConst.Category.CATEGORY_SUB) + public List querySubCategory(Long categoryId) { + return categoryDao.queryByParentId(Lists.newArrayList(categoryId), false); + } + + + /** + * 查询类目 层级树 + * 优先查询缓存 + */ + @Cacheable(AdminCacheConst.Category.CATEGORY_TREE) + public List queryCategoryTree(Long parentId, Integer categoryType) { + List allCategoryEntityList = categoryDao.queryByType(categoryType, false); + + List categoryEntityList = allCategoryEntityList.stream().filter(e -> e.getParentId().equals(parentId)).collect(Collectors.toList()); + List treeList = SmartBeanUtil.copyList(categoryEntityList, CategoryTreeVO.class); + treeList.forEach(e -> { + e.setLabel(e.getCategoryName()); + e.setValue(e.getCategoryId()); + e.setCategoryFullName(e.getCategoryName()); + }); + // 递归设置子类 + this.queryAndSetSubCategory(treeList, allCategoryEntityList); + return treeList; + } + + /** + * 递归查询设置类目子类 + * 从缓存查询子类 + * + */ + private void queryAndSetSubCategory(List treeList, List allCategoryEntityList) { + if (CollectionUtils.isEmpty(treeList)) { + return; + } + List parentIdList = treeList.stream().map(CategoryTreeVO::getValue).collect(Collectors.toList()); + List categoryEntityList = allCategoryEntityList.stream().filter(e -> parentIdList.contains(e.getParentId())).collect(Collectors.toList()); + Map> categorySubMap = categoryEntityList.stream().collect(Collectors.groupingBy(CategoryEntity::getParentId)); + treeList.forEach(e -> { + List childrenEntityList = categorySubMap.getOrDefault(e.getValue(), Lists.newArrayList()); + List childrenVOList = SmartBeanUtil.copyList(childrenEntityList, CategoryTreeVO.class); + childrenVOList.forEach(item -> { + item.setLabel(item.getCategoryName()); + item.setValue(item.getCategoryId()); + item.setCategoryFullName(e.getCategoryFullName() + StringConst.SEPARATOR_SLASH + item.getCategoryName()); + }); + // 递归查询 + this.queryAndSetSubCategory(childrenVOList, allCategoryEntityList); + e.setChildren(childrenVOList); + }); + } + + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/service/CategoryQueryService.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/service/CategoryQueryService.java new file mode 100644 index 0000000..9def152 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/service/CategoryQueryService.java @@ -0,0 +1,188 @@ +package net.lab1024.sa.admin.module.business.category.service; + +import cn.hutool.core.util.StrUtil; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.admin.module.business.category.domain.dto.CategorySimpleDTO; +import net.lab1024.sa.admin.module.business.category.domain.entity.CategoryEntity; +import net.lab1024.sa.admin.module.business.category.manager.CategoryCacheManager; +import net.lab1024.sa.base.common.constant.StringConst; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 类目 查询 业务类 + * + * @Author 1024创新实验室: 胡克 + * @Date 2021/08/05 21:26:58 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +@Slf4j +public class CategoryQueryService { + + private static final Long DEFAULT_CATEGORY_PARENT_ID = 0L; + + @Resource + private CategoryCacheManager categoryCacheManager; + + /** + * 根据 id 查询未删除的类目 + * + * @param categoryId + * @return 可能 null + */ + public Optional queryCategory(Long categoryId) { + if (null == categoryId) { + return Optional.empty(); + } + CategoryEntity entity = categoryCacheManager.queryCategory(categoryId); + if (null == entity || entity.getDeletedFlag()) { + return Optional.empty(); + } + return Optional.of(entity); + } + + + /** + * 根据 类目id集合 查询未删除的类目集合 + */ + public Map queryCategoryList(List categoryIdList) { + if (CollectionUtils.isEmpty(categoryIdList)) { + return Collections.emptyMap(); + } + categoryIdList = categoryIdList.stream().distinct().collect(Collectors.toList()); + Map categoryEntityMap = Maps.newHashMap(); + for (Long categoryId : categoryIdList) { + CategoryEntity categoryEntity = categoryCacheManager.queryCategory(categoryId); + if (categoryEntity != null) { + categoryEntityMap.put(categoryId, categoryEntity); + } + } + return categoryEntityMap; + } + + + /** + * 根据类目id 递归查询该id的所有子类id 递归查询 + * 同时存入缓存 + * 注意:查询出来的集合 不包含传递的父类参数 + */ + public List queryCategorySubId(List categoryIdList) { + if (CollectionUtils.isEmpty(categoryIdList)) { + return Collections.emptyList(); + } + //所有子类 + List categoryEntityList = Lists.newArrayList(); + categoryIdList.forEach(e -> { + categoryEntityList.addAll(categoryCacheManager.querySubCategory(e)); + }); + Map> subTypeMap = categoryEntityList.stream().collect(Collectors.groupingBy(CategoryEntity::getCategoryId)); + // 递归查询子类 + categoryIdList = subTypeMap.values().stream().flatMap(Collection::stream).map(CategoryEntity::getCategoryId).distinct().collect(Collectors.toList()); + if (CollectionUtils.isEmpty(categoryIdList)) { + return Lists.newArrayList(); + } + categoryIdList.addAll(this.queryCategorySubId(categoryIdList)); + return categoryIdList; + } + + + /** + * 处理类目名称 + */ + public List queryCategoryName(List categoryIdList) { + if (CollectionUtils.isEmpty(categoryIdList)) { + return null; + } + Map categoryMap = this.queryCategoryList(categoryIdList); + List categoryNameList = Lists.newArrayList(); + categoryIdList.forEach(e -> { + CategoryEntity categoryEntity = categoryMap.get(e); + if (categoryEntity != null) { + categoryNameList.add(categoryMap.get(e).getCategoryName()); + } + }); + return categoryNameList; + } + + /** + * 根据类目id 查询类目名称 + */ + public String queryCategoryName(Long categoryId) { + CategoryEntity categoryEntity = categoryCacheManager.queryCategory(categoryId); + if (null == categoryEntity || categoryEntity.getDeletedFlag()) { + return null; + } + return categoryEntity.getCategoryName(); + } + + /** + * 根据类目id 查询类目详情 包含类目全称 如:医考/医师资格/临床执业 + */ + public CategorySimpleDTO queryCategoryInfo(Long categoryId) { + CategoryEntity categoryEntity = categoryCacheManager.queryCategory(categoryId); + if (null == categoryEntity || categoryEntity.getDeletedFlag()) { + return null; + } + String fullName = this.queryFullName(categoryId); + // 返回DTO + CategorySimpleDTO categoryDTO = new CategorySimpleDTO(); + categoryDTO.setCategoryId(categoryId); + categoryDTO.setCategoryName(categoryEntity.getCategoryName()); + categoryDTO.setCategoryFullName(fullName); + categoryDTO.setParentId(categoryEntity.getParentId()); + return categoryDTO; + } + + /** + * 递归查询分类和所有父级类目 + * ps:特别注意返回的集合中 包含自己 + */ + public List queryCategoryAndParent(Long categoryId) { + List parentCategoryList = Lists.newArrayList(); + CategoryEntity categoryEntity = categoryCacheManager.queryCategory(categoryId); + if (null == categoryEntity || categoryEntity.getDeletedFlag()) { + return parentCategoryList; + } + + // 父级始终放在第一位 + parentCategoryList.add(0, categoryEntity); + Long parentId = categoryEntity.getParentId(); + if (Objects.equals(DEFAULT_CATEGORY_PARENT_ID, parentId)) { + return parentCategoryList; + } + parentCategoryList.addAll(0, this.queryCategoryAndParent(parentId)); + return parentCategoryList; + } + + /** + * 查询 分类全称 如:医考/医师资格/临床执业 + */ + public String queryFullName(Long categoryId) { + List parentCategoryList = this.queryCategoryAndParent(categoryId); + // 拼接父级类目名称 斜杠分隔返回 + List nameList = parentCategoryList.stream().map(CategoryEntity::getCategoryName).collect(Collectors.toList()); + return StrUtil.join(StringConst.SEPARATOR_SLASH, nameList); + } + + /** + * 查询 分类全称 如:医考/医师资格/临床执业 + */ + public Map queryFullName(List categoryIdList) { + if (CollectionUtils.isEmpty(categoryIdList)) { + return Maps.newHashMap(); + } + // 循环内查询的缓存 还ok + return categoryIdList.stream().collect(Collectors.toMap(Function.identity(), this::queryFullName)); + } + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/service/CategoryService.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/service/CategoryService.java new file mode 100644 index 0000000..130b685 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/category/service/CategoryService.java @@ -0,0 +1,206 @@ +package net.lab1024.sa.admin.module.business.category.service; + +import com.google.common.collect.Lists; +import jakarta.annotation.Resource; +import net.lab1024.sa.admin.module.business.category.dao.CategoryDao; +import net.lab1024.sa.admin.module.business.category.domain.entity.CategoryEntity; +import net.lab1024.sa.admin.module.business.category.domain.form.CategoryAddForm; +import net.lab1024.sa.admin.module.business.category.domain.form.CategoryTreeQueryForm; +import net.lab1024.sa.admin.module.business.category.domain.form.CategoryUpdateForm; +import net.lab1024.sa.admin.module.business.category.domain.vo.CategoryTreeVO; +import net.lab1024.sa.admin.module.business.category.domain.vo.CategoryVO; +import net.lab1024.sa.admin.module.business.category.manager.CategoryCacheManager; +import net.lab1024.sa.base.common.code.UserErrorCode; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.math.NumberUtils; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +/** + * 类目 + * + * @Author 1024创新实验室: 胡克 + * @Date 2021/08/05 21:26:58 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class CategoryService { + + @Resource + private CategoryDao categoryDao; + + @Resource + private CategoryQueryService categoryQueryService; + + @Resource + private CategoryCacheManager categoryCacheManager; + + /** + * 添加类目 + */ + public ResponseDTO add(CategoryAddForm addForm) { + // 校验类目 + CategoryEntity categoryEntity = SmartBeanUtil.copy(addForm, CategoryEntity.class); + ResponseDTO res = this.checkCategory(categoryEntity, false); + if (!res.getOk()) { + return res; + } + // 没有父类则使用默认父类 + Long parentId = null == addForm.getParentId() ? NumberUtils.LONG_ZERO : addForm.getParentId(); + categoryEntity.setParentId(parentId); + categoryEntity.setSort(null == addForm.getSort() ? 0 : addForm.getSort()); + categoryEntity.setDeletedFlag(false); + + // 保存数据 + categoryDao.insert(categoryEntity); + + // 更新缓存 + categoryCacheManager.removeCache(); + return ResponseDTO.ok(); + } + + /** + * 更新类目 + * 不能更新父级类目 + * + */ + public ResponseDTO update(CategoryUpdateForm updateForm) { + // 校验类目 + Long categoryId = updateForm.getCategoryId(); + Optional optional = categoryQueryService.queryCategory(categoryId); + if (!optional.isPresent()) { + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST); + } + CategoryEntity categoryEntity = SmartBeanUtil.copy(updateForm, CategoryEntity.class); + + /* + 不更新类目类型 + 不更新父类id + */ + Integer categoryType = optional.get().getCategoryType(); + categoryEntity.setCategoryType(categoryType); + categoryEntity.setParentId(optional.get().getParentId()); + + ResponseDTO responseDTO = this.checkCategory(categoryEntity, true); + if (!responseDTO.getOk()) { + return responseDTO; + } + categoryDao.updateById(categoryEntity); + + // 更新缓存 + categoryCacheManager.removeCache(); + return ResponseDTO.ok(); + } + + /** + * 新增/更新 类目时的 校验 + * + */ + private ResponseDTO checkCategory(CategoryEntity categoryEntity, boolean isUpdate) { + // 校验父级是否存在 + Long parentId = categoryEntity.getParentId(); + Integer categoryType = categoryEntity.getCategoryType(); + if (null != parentId) { + if (Objects.equals(categoryEntity.getCategoryId(), parentId)) { + return ResponseDTO.userErrorParam("父级类目怎么和自己相同了"); + } + if (!Objects.equals(parentId, NumberUtils.LONG_ZERO)) { + Optional optional = categoryQueryService.queryCategory(parentId); + if (!optional.isPresent()) { + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST, "父级类目不存在~"); + } + + CategoryEntity parent = optional.get(); + if (!Objects.equals(categoryType, parent.getCategoryType())) { + return ResponseDTO.userErrorParam("与父级类目类型不一致"); + } + } + + } else { + // 如果没有父类 使用默认父类 + parentId = NumberUtils.LONG_ZERO; + } + + // 校验同父类下 名称是否重复 + CategoryEntity queryEntity = new CategoryEntity(); + queryEntity.setParentId(parentId); + queryEntity.setCategoryType(categoryType); + queryEntity.setCategoryName(categoryEntity.getCategoryName()); + queryEntity.setDeletedFlag(false); + queryEntity = categoryDao.selectOne(queryEntity); + if (null != queryEntity) { + if (isUpdate) { + if (!Objects.equals(queryEntity.getCategoryId(), categoryEntity.getCategoryId())) { + return ResponseDTO.userErrorParam("同级下已存在相同类目~"); + } + } else { + return ResponseDTO.userErrorParam("同级下已存在相同类目~"); + } + } + return ResponseDTO.ok(); + } + + /** + * 查询 类目详情 + * + */ + public ResponseDTO queryDetail(Long categoryId) { + Optional optional = categoryQueryService.queryCategory(categoryId); + if (!optional.isPresent()) { + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST); + } + CategoryVO adminVO = SmartBeanUtil.copy(optional.get(), CategoryVO.class); + return ResponseDTO.ok(adminVO); + } + + /** + * 根据父级id 查询所有子类 返回层级树 + * 如果父类id 为空 返回所有类目层级 + * + */ + public ResponseDTO> queryTree(CategoryTreeQueryForm queryForm) { + if (null == queryForm.getParentId()) { + if (null == queryForm.getCategoryType()) { + return ResponseDTO.userErrorParam("类目类型不能为空"); + } + queryForm.setParentId(NumberUtils.LONG_ZERO); + } + List treeList = categoryCacheManager.queryCategoryTree(queryForm.getParentId(), queryForm.getCategoryType()); + return ResponseDTO.ok(treeList); + } + + /** + * 删除类目 + * 如果有未删除的子类 则无法删除 + * + */ + public ResponseDTO delete(Long categoryId) { + Optional optional = categoryQueryService.queryCategory(categoryId); + if (!optional.isPresent()) { + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST); + } + + List categorySubId = categoryQueryService.queryCategorySubId(Lists.newArrayList(categoryId)); + if (CollectionUtils.isNotEmpty(categorySubId)) { + return ResponseDTO.userErrorParam("请先删除子级类目"); + } + + // 更新数据 + CategoryEntity categoryEntity = new CategoryEntity(); + categoryEntity.setCategoryId(categoryId); + categoryEntity.setDeletedFlag(true); + categoryDao.updateById(categoryEntity); + + // 更新缓存 + categoryCacheManager.removeCache(); + return ResponseDTO.ok(); + } + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/goods/constant/GoodsStatusEnum.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/goods/constant/GoodsStatusEnum.java new file mode 100644 index 0000000..95d37d5 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/goods/constant/GoodsStatusEnum.java @@ -0,0 +1,42 @@ +package net.lab1024.sa.admin.module.business.goods.constant; + + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 商品状态 + * + * @Author 1024创新实验室: 胡克 + * @Date 2021-10-25 20:26:54 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@AllArgsConstructor +@Getter +public enum GoodsStatusEnum implements BaseEnum { + + /** + * 1 预约中 + */ + APPOINTMENT(1, "预约中"), + + /** + * 2 售卖 + */ + SELL(2, "售卖中"), + + /** + * 3 售罄 + */ + SELL_OUT(3, "售罄"), + + + ; + + private final Integer value; + + private final String desc; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/goods/controller/GoodsController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/goods/controller/GoodsController.java new file mode 100644 index 0000000..42b69b0 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/goods/controller/GoodsController.java @@ -0,0 +1,94 @@ +package net.lab1024.sa.admin.module.business.goods.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import net.lab1024.sa.admin.constant.AdminSwaggerTagConst; +import net.lab1024.sa.admin.module.business.goods.domain.form.GoodsAddForm; +import net.lab1024.sa.admin.module.business.goods.domain.form.GoodsQueryForm; +import net.lab1024.sa.admin.module.business.goods.domain.form.GoodsUpdateForm; +import net.lab1024.sa.admin.module.business.goods.domain.vo.GoodsExcelVO; +import net.lab1024.sa.admin.module.business.goods.domain.vo.GoodsVO; +import net.lab1024.sa.admin.module.business.goods.service.GoodsService; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.domain.ValidateList; +import net.lab1024.sa.base.common.util.SmartExcelUtil; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.List; + +/** + * 商品业务 + * + * @Author 1024创新实验室: 胡克 + * @Date 2021-10-25 20:26:54 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@RestController +@Tag(name = AdminSwaggerTagConst.Business.MANAGER_GOODS) +public class GoodsController { + + @Resource + private GoodsService goodsService; + + @Operation(summary = "分页查询 @author 胡克") + @PostMapping("/goods/query") + @SaCheckPermission("goods:query") + public ResponseDTO> query(@RequestBody @Valid GoodsQueryForm queryForm) { + return goodsService.query(queryForm); + } + + @Operation(summary = "添加商品 @author 胡克") + @PostMapping("/goods/add") + @SaCheckPermission("goods:add") + public ResponseDTO add(@RequestBody @Valid GoodsAddForm addForm) { + return goodsService.add(addForm); + } + + @Operation(summary = "更新商品 @author 胡克") + @PostMapping("/goods/update") + @SaCheckPermission("goods:update") + public ResponseDTO update(@RequestBody @Valid GoodsUpdateForm updateForm) { + return goodsService.update(updateForm); + } + + @Operation(summary = "删除 @author 卓大") + @GetMapping("/goods/delete/{goodsId}") + @SaCheckPermission("goods:delete") + public ResponseDTO delete(@PathVariable Long goodsId) { + return goodsService.delete(goodsId); + } + + @Operation(summary = "批量 @author 卓大") + @PostMapping("/goods/batchDelete") + @SaCheckPermission("goods:batchDelete") + public ResponseDTO batchDelete(@RequestBody @Valid ValidateList idList) { + return goodsService.batchDelete(idList); + } + + // --------------- 导出和导入 ------------------- + + @Operation(summary = "导入 @author 卓大") + @PostMapping("/goods/importGoods") + @SaCheckPermission("goods:importGoods") + public ResponseDTO importGoods(@RequestParam MultipartFile file) { + return goodsService.importGoods(file); + } + + @Operation(summary = "导出 @author 卓大") + @GetMapping("/goods/exportGoods") + @SaCheckPermission("goods:exportGoods") + public void exportGoods(HttpServletResponse response) throws IOException { + List goodsList = goodsService.getAllGoods(); + SmartExcelUtil.exportExcel(response,"商品列表.xlsx","商品",GoodsExcelVO.class, goodsList); + } + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/goods/dao/GoodsDao.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/goods/dao/GoodsDao.java new file mode 100644 index 0000000..932f480 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/goods/dao/GoodsDao.java @@ -0,0 +1,37 @@ +package net.lab1024.sa.admin.module.business.goods.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.admin.module.business.goods.domain.entity.GoodsEntity; +import net.lab1024.sa.admin.module.business.goods.domain.form.GoodsQueryForm; +import net.lab1024.sa.admin.module.business.goods.domain.vo.GoodsVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 商品 + * + * @Author 1024创新实验室: 胡克 + * @Date 2021-10-25 20:26:54 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface GoodsDao extends BaseMapper { + + /** + * 分页 查询商品 + * + */ + List query(Page page, @Param("query") GoodsQueryForm query); + + /** + * 批量更新删除状态 + */ + + void batchUpdateDeleted(@Param("goodsIdList")List goodsIdList,@Param("deletedFlag")Boolean deletedFlag); +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/goods/domain/entity/GoodsEntity.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/goods/domain/entity/GoodsEntity.java new file mode 100644 index 0000000..775074f --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/goods/domain/entity/GoodsEntity.java @@ -0,0 +1,71 @@ +package net.lab1024.sa.admin.module.business.goods.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 商品 实体类 + * + * @Author 1024创新实验室: 胡克 + * @Date 2021-10-25 20:26:54 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName("t_goods") +public class GoodsEntity { + + @TableId(type = IdType.AUTO) + private Long goodsId; + + /** + * 商品状态:[1:预约中,2:售卖中,3:售罄] + */ + private Integer goodsStatus; + + /** + * 商品分类 + */ + private Long categoryId; + + /** + * 商品名称 + */ + private String goodsName; + + /** + * 产地 + */ + private String place; + + /** + * 商品价格 + */ + private BigDecimal price; + + + /** + * 上架状态 + */ + private Boolean shelvesFlag; + + /** + * 删除状态 + */ + private Boolean deletedFlag; + + /** + * 备注 + */ + private String remark; + + private LocalDateTime updateTime; + + private LocalDateTime createTime; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/goods/domain/form/GoodsAddForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/goods/domain/form/GoodsAddForm.java new file mode 100644 index 0000000..c13620c --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/goods/domain/form/GoodsAddForm.java @@ -0,0 +1,56 @@ +package net.lab1024.sa.admin.module.business.goods.domain.form; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import net.lab1024.sa.admin.module.business.goods.constant.GoodsStatusEnum; +import net.lab1024.sa.base.common.json.deserializer.DictDataDeserializer; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; + +import java.math.BigDecimal; + +/** + * 商品 添加表单 + * + * @Author 1024创新实验室: 胡克 + * @Date 2021-10-25 20:26:54 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class GoodsAddForm { + + @Schema(description = "商品分类") + @NotNull(message = "商品分类不能为空") + private Long categoryId; + + @Schema(description = "商品名称") + @NotBlank(message = "商品名称不能为空") + private String goodsName; + + @SchemaEnum(GoodsStatusEnum.class) + @CheckEnum(message = "商品状态错误", value = GoodsStatusEnum.class, required = true) + private Integer goodsStatus; + + @Schema(description = "产地") + @NotBlank(message = "产地 不能为空 ") + @JsonDeserialize(using = DictDataDeserializer.class) + private String place; + + @Schema(description = "商品价格") + @NotNull(message = "商品价格不能为空") + @DecimalMin(value = "0", message = "商品价格最低0") + private BigDecimal price; + + @Schema(description = "上架状态") + @NotNull(message = "上架状态不能为空") + private Boolean shelvesFlag; + + @Schema(description = "备注|可选") + private String remark; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/goods/domain/form/GoodsImportForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/goods/domain/form/GoodsImportForm.java new file mode 100644 index 0000000..d6ccbff --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/goods/domain/form/GoodsImportForm.java @@ -0,0 +1,37 @@ +package net.lab1024.sa.admin.module.business.goods.domain.form; + +import cn.idev.excel.annotation.ExcelProperty; +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 商品 导入表单 + * + * @Author 1024创新实验室: 胡克 + * @Date 2021-10-25 20:26:54 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class GoodsImportForm { + + @ExcelProperty("商品分类") + private String categoryName; + + @ExcelProperty("商品名称") + private String goodsName; + + @ExcelProperty("商品状态错误") + private String goodsStatus; + + @ExcelProperty("产地") + private String place; + + @ExcelProperty("商品价格") + private BigDecimal price; + + @ExcelProperty("备注") + private String remark; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/goods/domain/form/GoodsQueryForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/goods/domain/form/GoodsQueryForm.java new file mode 100644 index 0000000..d726a9a --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/goods/domain/form/GoodsQueryForm.java @@ -0,0 +1,45 @@ +package net.lab1024.sa.admin.module.business.goods.domain.form; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.admin.module.business.goods.constant.GoodsStatusEnum; +import net.lab1024.sa.base.common.domain.PageParam; +import net.lab1024.sa.base.common.json.deserializer.DictDataDeserializer; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; +import org.hibernate.validator.constraints.Length; + +/** + * 商品 分页查询 + * + * @Author 1024创新实验室: 胡克 + * @Date 2021-10-25 20:26:54 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class GoodsQueryForm extends PageParam { + + @Schema(description = "商品分类") + private Integer categoryId; + + @Schema(description = "搜索词") + @Length(max = 30, message = "搜索词最多30字符") + private String searchWord; + + @SchemaEnum(GoodsStatusEnum.class) + @CheckEnum(message = "商品状态错误", value = GoodsStatusEnum.class, required = false) + private Integer goodsStatus; + + @Schema(description = "产地") + @JsonDeserialize(using = DictDataDeserializer.class) + private String place; + + @Schema(description = "上架状态") + private Boolean shelvesFlag; + + @Schema(hidden = true) + private Boolean deletedFlag; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/goods/domain/form/GoodsUpdateForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/goods/domain/form/GoodsUpdateForm.java new file mode 100644 index 0000000..4689653 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/goods/domain/form/GoodsUpdateForm.java @@ -0,0 +1,22 @@ +package net.lab1024.sa.admin.module.business.goods.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 商品 更新表单 + * + * @Author 1024创新实验室: 胡克 + * @Date 2021-10-25 20:26:54 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class GoodsUpdateForm extends GoodsAddForm { + + @Schema(description = "商品id") + @NotNull(message = "商品id不能为空") + private Long goodsId; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/goods/domain/vo/GoodsExcelVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/goods/domain/vo/GoodsExcelVO.java new file mode 100644 index 0000000..98a29b1 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/goods/domain/vo/GoodsExcelVO.java @@ -0,0 +1,44 @@ +package net.lab1024.sa.admin.module.business.goods.domain.vo; + + +import cn.idev.excel.annotation.ExcelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; + +/** + * excel商品 + * + * @Author 1024创新实验室: 胡克 + * @Date 2021-10-25 20:26:54 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class GoodsExcelVO { + + @ExcelProperty("商品分类") + private String categoryName; + + @ExcelProperty("商品名称") + private String goodsName; + + @ExcelProperty("商品状态错误") + private String goodsStatus; + + @ExcelProperty("产地") + private String place; + + @ExcelProperty("商品价格") + private BigDecimal price; + + @ExcelProperty("备注") + private String remark; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/goods/domain/vo/GoodsVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/goods/domain/vo/GoodsVO.java new file mode 100644 index 0000000..24f0773 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/goods/domain/vo/GoodsVO.java @@ -0,0 +1,54 @@ +package net.lab1024.sa.admin.module.business.goods.domain.vo; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.admin.module.business.goods.constant.GoodsStatusEnum; +import net.lab1024.sa.base.common.swagger.SchemaEnum; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 商品 + * + * @Author 1024创新实验室: 胡克 + * @Date 2021-10-25 20:26:54 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class GoodsVO { + + @Schema(description = "商品分类") + private Long categoryId; + + @Schema(description = "商品名称") + private String goodsName; + + @SchemaEnum(GoodsStatusEnum.class) + private Integer goodsStatus; + + @Schema(description = "产地") + private String place; + + @Schema(description = "商品价格") + private BigDecimal price; + + @Schema(description = "上架状态") + private Boolean shelvesFlag; + + @Schema(description = "备注|可选") + private String remark; + + @Schema(description = "商品id") + private Long goodsId; + + @Schema(description = "商品分类") + private String categoryName; + + private LocalDateTime updateTime; + + private LocalDateTime createTime; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/goods/service/GoodsService.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/goods/service/GoodsService.java new file mode 100644 index 0000000..e4d59fb --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/goods/service/GoodsService.java @@ -0,0 +1,211 @@ +package net.lab1024.sa.admin.module.business.goods.service; + +import cn.idev.excel.FastExcel; +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.admin.module.business.category.constant.CategoryTypeEnum; +import net.lab1024.sa.admin.module.business.category.domain.entity.CategoryEntity; +import net.lab1024.sa.admin.module.business.category.service.CategoryQueryService; +import net.lab1024.sa.admin.module.business.goods.constant.GoodsStatusEnum; +import net.lab1024.sa.admin.module.business.goods.dao.GoodsDao; +import net.lab1024.sa.admin.module.business.goods.domain.entity.GoodsEntity; +import net.lab1024.sa.admin.module.business.goods.domain.form.GoodsAddForm; +import net.lab1024.sa.admin.module.business.goods.domain.form.GoodsImportForm; +import net.lab1024.sa.admin.module.business.goods.domain.form.GoodsQueryForm; +import net.lab1024.sa.admin.module.business.goods.domain.form.GoodsUpdateForm; +import net.lab1024.sa.admin.module.business.goods.domain.vo.GoodsExcelVO; +import net.lab1024.sa.admin.module.business.goods.domain.vo.GoodsVO; +import net.lab1024.sa.base.common.code.UserErrorCode; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.exception.BusinessException; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartEnumUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.module.support.datatracer.constant.DataTracerTypeEnum; +import net.lab1024.sa.base.module.support.datatracer.service.DataTracerService; +import net.lab1024.sa.base.module.support.dict.service.DictService; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 商品 + * + * @Author 1024创新实验室: 胡克 + * @Date 2021-10-25 20:26:54 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +@Slf4j +public class GoodsService { + + @Resource + private GoodsDao goodsDao; + + @Resource + private CategoryQueryService categoryQueryService; + + @Resource + private DataTracerService dataTracerService; + + @Resource + private DictService dictService; + + /** + * 添加商品 + */ + @Transactional(rollbackFor = Exception.class) + public ResponseDTO add(GoodsAddForm addForm) { + // 商品校验 + ResponseDTO res = this.checkGoods(addForm); + if (!res.getOk()) { + return res; + } + GoodsEntity goodsEntity = SmartBeanUtil.copy(addForm, GoodsEntity.class); + goodsEntity.setDeletedFlag(Boolean.FALSE); + goodsDao.insert(goodsEntity); + dataTracerService.insert(goodsEntity.getGoodsId(), DataTracerTypeEnum.GOODS); + return ResponseDTO.ok(); + } + + /** + * 更新商品 + */ + @Transactional(rollbackFor = Exception.class) + public ResponseDTO update(GoodsUpdateForm updateForm) { + // 商品校验 + ResponseDTO res = this.checkGoods(updateForm); + if (!res.getOk()) { + return res; + } + GoodsEntity originEntity = goodsDao.selectById(updateForm.getGoodsId()); + GoodsEntity goodsEntity = SmartBeanUtil.copy(updateForm, GoodsEntity.class); + goodsDao.updateById(goodsEntity); + dataTracerService.update(updateForm.getGoodsId(), DataTracerTypeEnum.GOODS, originEntity, goodsEntity); + return ResponseDTO.ok(); + } + + /** + * 添加/更新 商品校验 + */ + private ResponseDTO checkGoods(GoodsAddForm addForm) { + // 校验类目id + Long categoryId = addForm.getCategoryId(); + Optional optional = categoryQueryService.queryCategory(categoryId); + if (!optional.isPresent() || !CategoryTypeEnum.GOODS.equalsValue(optional.get().getCategoryType())) { + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST, "商品类目不存在~"); + } + + return ResponseDTO.ok(); + } + + /** + * 删除 + */ + @Transactional(rollbackFor = Exception.class) + public ResponseDTO delete(Long goodsId) { + GoodsEntity goodsEntity = goodsDao.selectById(goodsId); + if (goodsEntity == null) { + return ResponseDTO.userErrorParam("商品不存在"); + } + + if (!goodsEntity.getGoodsStatus().equals(GoodsStatusEnum.SELL_OUT.getValue())) { + return ResponseDTO.userErrorParam("只有售罄的商品才可以删除"); + } + + batchDelete(Collections.singletonList(goodsId)); + dataTracerService.batchDelete(Collections.singletonList(goodsId), DataTracerTypeEnum.GOODS); + return ResponseDTO.ok(); + } + + /** + * 批量删除 + */ + public ResponseDTO batchDelete(List goodsIdList) { + if (CollectionUtils.isEmpty(goodsIdList)) { + return ResponseDTO.ok(); + } + + goodsDao.batchUpdateDeleted(goodsIdList, Boolean.TRUE); + return ResponseDTO.ok(); + } + + + /** + * 分页查询 + */ + public ResponseDTO> query(GoodsQueryForm queryForm) { + queryForm.setDeletedFlag(false); + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List list = goodsDao.query(page, queryForm); + PageResult pageResult = SmartPageUtil.convert2PageResult(page, list); + if (pageResult.getEmptyFlag()) { + return ResponseDTO.ok(pageResult); + } + // 查询分类名称 + List categoryIdList = list.stream().map(GoodsVO::getCategoryId).distinct().collect(Collectors.toList()); + Map categoryMap = categoryQueryService.queryCategoryList(categoryIdList); + list.forEach(e -> { + CategoryEntity categoryEntity = categoryMap.get(e.getCategoryId()); + if (categoryEntity != null) { + e.setCategoryName(categoryEntity.getCategoryName()); + } + }); + return ResponseDTO.ok(pageResult); + } + + /** + * 商品导入 + * + * @param file 上传文件 + * @return 结果 + */ + public ResponseDTO importGoods(MultipartFile file) { + List dataList; + try { + dataList = FastExcel.read(file.getInputStream()).head(GoodsImportForm.class) + .sheet() + .doReadSync(); + } catch (IOException e) { + log.error(e.getMessage(), e); + throw new BusinessException("数据格式存在问题,无法读取"); + } + + if (CollectionUtils.isEmpty(dataList)) { + return ResponseDTO.userErrorParam("数据为空"); + } + + return ResponseDTO.okMsg("成功导入" + dataList.size() + "条,具体数据为:" + JSON.toJSONString(dataList)); + } + + /** + * 商品导出 + */ + public List getAllGoods() { + List goodsEntityList = goodsDao.selectList(null); + String dictCode = "GOODS_PLACE"; + return goodsEntityList.stream() + .map(e -> + GoodsExcelVO.builder() + .goodsStatus(SmartEnumUtil.getEnumDescByValue(e.getGoodsStatus(), GoodsStatusEnum.class)) + .categoryName(categoryQueryService.queryCategoryName(e.getCategoryId())) + .place(Arrays.stream(e.getPlace().split(",")).map(code -> dictService.getDictDataLabel(dictCode, code)).collect(Collectors.joining(","))) + .price(e.getPrice()) + .goodsName(e.getGoodsName()) + .remark(e.getRemark()) + .build() + ) + .collect(Collectors.toList()); + + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/bank/BankController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/bank/BankController.java new file mode 100644 index 0000000..e165817 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/bank/BankController.java @@ -0,0 +1,81 @@ +package net.lab1024.sa.admin.module.business.oa.bank; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import net.lab1024.sa.admin.constant.AdminSwaggerTagConst; +import net.lab1024.sa.admin.module.business.oa.bank.domain.BankCreateForm; +import net.lab1024.sa.admin.module.business.oa.bank.domain.BankQueryForm; +import net.lab1024.sa.admin.module.business.oa.bank.domain.BankUpdateForm; +import net.lab1024.sa.admin.module.business.oa.bank.domain.BankVO; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.RequestUser; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartRequestUtil; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * OA办公-OA银行信息 + * + * @Author 1024创新实验室:善逸 + * @Date 2022/6/23 21:59:22 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@RestController +@Tag(name = AdminSwaggerTagConst.Business.OA_BANK) +public class BankController { + + @Resource + private BankService bankService; + + @Operation(summary = "分页查询银行信息 @author 善逸") + @PostMapping("/oa/bank/page/query") + @SaCheckPermission("oa:bank:query") + public ResponseDTO> queryByPage(@RequestBody @Valid BankQueryForm queryForm) { + return bankService.queryByPage(queryForm); + } + + @Operation(summary = "根据企业ID查询银行信息列表 @author 善逸") + @GetMapping("/oa/bank/query/list/{enterpriseId}") + @SaCheckPermission("oa:bank:query") + public ResponseDTO> queryList(@PathVariable Long enterpriseId) { + return bankService.queryList(enterpriseId); + } + + @Operation(summary = "查询银行信息详情 @author 善逸") + @GetMapping("/oa/bank/get/{bankId}") + @SaCheckPermission("oa:bank:query") + public ResponseDTO getDetail(@PathVariable Long bankId) { + return bankService.getDetail(bankId); + } + + @Operation(summary = "新建银行信息 @author 善逸") + @PostMapping("/oa/bank/create") + @SaCheckPermission("oa:bank:add") + public ResponseDTO createBank(@RequestBody @Valid BankCreateForm createVO) { + RequestUser requestUser = SmartRequestUtil.getRequestUser(); + createVO.setCreateUserId(requestUser.getUserId()); + createVO.setCreateUserName(requestUser.getUserName()); + return bankService.createBank(createVO); + } + + @Operation(summary = "编辑银行信息 @author 善逸") + @PostMapping("/oa/bank/update") + @SaCheckPermission("oa:bank:update") + public ResponseDTO updateBank(@RequestBody @Valid BankUpdateForm updateVO) { + return bankService.updateBank(updateVO); + } + + @Operation(summary = "删除银行信息 @author 善逸") + @GetMapping("/oa/bank/delete/{bankId}") + @SaCheckPermission("oa:bank:delete") + public ResponseDTO deleteBank(@PathVariable Long bankId) { + return bankService.deleteBank(bankId); + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/bank/BankDao.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/bank/BankDao.java new file mode 100644 index 0000000..f553221 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/bank/BankDao.java @@ -0,0 +1,47 @@ +package net.lab1024.sa.admin.module.business.oa.bank; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.admin.module.business.oa.bank.domain.BankEntity; +import net.lab1024.sa.admin.module.business.oa.bank.domain.BankQueryForm; +import net.lab1024.sa.admin.module.business.oa.bank.domain.BankVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * OA办公-OA银行信息 + * + * @Author 1024创新实验室:善逸 + * @Date 2022/6/23 21:59:22 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface BankDao extends BaseMapper { + + /** + * 根据账号查询 + */ + BankEntity queryByAccountNumber(@Param("enterpriseId") Long enterpriseId, @Param("accountNumber") String accountNumber, @Param("excludeBankId") Long excludeBankId, @Param("deletedFlag") Boolean deletedFlag); + + /** + * 删除银行信息 + * + */ + void deleteBank(@Param("bankId") Long bankId, @Param("deletedFlag") Boolean deletedFlag); + + /** + * 银行信息分页查询 + * + */ + List queryPage(Page page, @Param("queryForm") BankQueryForm queryForm); + + /** + * 查询银行信息详情 + */ + BankVO getDetail(@Param("bankId") Long bankId, @Param("deletedFlag") Boolean deletedFlag); +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/bank/BankService.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/bank/BankService.java new file mode 100644 index 0000000..177c50d --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/bank/BankService.java @@ -0,0 +1,145 @@ +package net.lab1024.sa.admin.module.business.oa.bank; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.admin.module.business.oa.bank.domain.*; +import net.lab1024.sa.admin.module.business.oa.enterprise.dao.EnterpriseDao; +import net.lab1024.sa.admin.module.business.oa.enterprise.domain.entity.EnterpriseEntity; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.module.support.datatracer.constant.DataTracerConst; +import net.lab1024.sa.base.module.support.datatracer.constant.DataTracerTypeEnum; +import net.lab1024.sa.base.module.support.datatracer.service.DataTracerService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Objects; + +/** + * OA办公-OA银行信息 + * + * @Author 1024创新实验室:善逸 + * @Date 2022/6/23 21:59:22 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +@Slf4j +public class BankService { + + @Resource + private BankDao bankDao; + + @Resource + private EnterpriseDao enterpriseDao; + + @Resource + private DataTracerService dataTracerService; + + /** + * 分页查询银行信息 + */ + public ResponseDTO> queryByPage(BankQueryForm queryForm) { + queryForm.setDeletedFlag(Boolean.FALSE); + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List bankList = bankDao.queryPage(page, queryForm); + PageResult pageResult = SmartPageUtil.convert2PageResult(page, bankList); + return ResponseDTO.ok(pageResult); + } + + /** + * 根据企业ID查询不分页的银行列表 + */ + public ResponseDTO> queryList(Long enterpriseId) { + BankQueryForm queryForm = new BankQueryForm(); + queryForm.setEnterpriseId(enterpriseId); + queryForm.setDeletedFlag(Boolean.FALSE); + List bankList = bankDao.queryPage(null, queryForm); + return ResponseDTO.ok(bankList); + } + + /** + * 查询银行信息详情 + */ + public ResponseDTO getDetail(Long bankId) { + // 校验银行信息是否存在 + BankVO bankVO = bankDao.getDetail(bankId, Boolean.FALSE); + if (Objects.isNull(bankVO)) { + return ResponseDTO.userErrorParam("银行信息不存在"); + } + return ResponseDTO.ok(bankVO); + } + + /** + * 新建银行信息 + */ + @Transactional(rollbackFor = Exception.class) + public ResponseDTO createBank(BankCreateForm createVO) { + Long enterpriseId = createVO.getEnterpriseId(); + // 校验企业是否存在 + EnterpriseEntity enterpriseDetail = enterpriseDao.selectById(enterpriseId); + if (Objects.isNull(enterpriseDetail) || enterpriseDetail.getDeletedFlag()) { + return ResponseDTO.userErrorParam("企业不存在"); + } + // 验证银行信息账号是否重复 + BankEntity validateBank = bankDao.queryByAccountNumber(enterpriseId, createVO.getAccountNumber(), null, Boolean.FALSE); + if (Objects.nonNull(validateBank)) { + return ResponseDTO.userErrorParam("银行信息账号重复"); + } + // 数据插入 + BankEntity insertBank = SmartBeanUtil.copy(createVO, BankEntity.class); + bankDao.insert(insertBank); + dataTracerService.addTrace(enterpriseId, DataTracerTypeEnum.OA_ENTERPRISE, "新增银行:" + DataTracerConst.HTML_BR + dataTracerService.getChangeContent(insertBank)); + return ResponseDTO.ok(); + } + + /** + * 编辑银行信息 + */ + @Transactional(rollbackFor = Exception.class) + public ResponseDTO updateBank(BankUpdateForm updateVO) { + Long enterpriseId = updateVO.getEnterpriseId(); + // 校验企业是否存在 + EnterpriseEntity enterpriseDetail = enterpriseDao.selectById(enterpriseId); + if (Objects.isNull(enterpriseDetail) || enterpriseDetail.getDeletedFlag()) { + return ResponseDTO.userErrorParam("企业不存在"); + } + Long bankId = updateVO.getBankId(); + // 校验银行信息是否存在 + BankEntity bankDetail = bankDao.selectById(bankId); + if (Objects.isNull(bankDetail) || bankDetail.getDeletedFlag()) { + return ResponseDTO.userErrorParam("银行信息不存在"); + } + // 验证银行信息账号是否重复 + BankEntity validateBank = bankDao.queryByAccountNumber(updateVO.getEnterpriseId(), updateVO.getAccountNumber(), bankId, Boolean.FALSE); + if (Objects.nonNull(validateBank)) { + return ResponseDTO.userErrorParam("银行信息账号重复"); + } + // 数据编辑 + BankEntity updateBank = SmartBeanUtil.copy(updateVO, BankEntity.class); + bankDao.updateById(updateBank); + dataTracerService.addTrace(enterpriseId, DataTracerTypeEnum.OA_ENTERPRISE, "更新银行:" + DataTracerConst.HTML_BR + dataTracerService.getChangeContent(bankDetail, updateBank)); + return ResponseDTO.ok(); + } + + + /** + * 删除银行信息 + */ + @Transactional(rollbackFor = Exception.class) + public ResponseDTO deleteBank(Long bankId) { + // 校验银行信息是否存在 + BankEntity bankDetail = bankDao.selectById(bankId); + if (Objects.isNull(bankDetail) || bankDetail.getDeletedFlag()) { + return ResponseDTO.userErrorParam("银行信息不存在"); + } + bankDao.deleteBank(bankId, Boolean.TRUE); + dataTracerService.addTrace(bankDetail.getEnterpriseId(), DataTracerTypeEnum.OA_ENTERPRISE, "删除银行:" + DataTracerConst.HTML_BR + dataTracerService.getChangeContent(bankDetail)); + return ResponseDTO.ok(); + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/bank/domain/BankCreateForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/bank/domain/BankCreateForm.java new file mode 100644 index 0000000..5398085 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/bank/domain/BankCreateForm.java @@ -0,0 +1,57 @@ +package net.lab1024.sa.admin.module.business.oa.bank.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +/** + * OA办公-银行信息新建 + * + * @Author 1024创新实验室:善逸 + * @Date 2022/6/23 21:59:22 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class BankCreateForm { + + @Schema(description = "开户银行") + @NotBlank(message = "开户银行不能为空") + @Length(max = 200, message = "开户银行最多200字符") + private String bankName; + + @Schema(description = "账户名称") + @NotBlank(message = "账户名称不能为空") + @Length(max = 200, message = "账户名称最多200字符") + private String accountName; + + @Schema(description = "账号") + @NotBlank(message = "账号不能为空") + @Length(max = 200, message = "账号最多200字符") + private String accountNumber; + + @Schema(description = "备注") + @Length(max = 500, message = "备注最多500字符") + private String remark; + + @Schema(description = "是否对公") + @NotNull(message = "是否对公不能为空") + private Boolean businessFlag; + + @Schema(description = "企业") + @NotNull(message = "企业不能为空") + private Long enterpriseId; + + @Schema(description = "禁用状态") + @NotNull(message = "禁用状态不能为空") + private Boolean disabledFlag; + + @Schema(hidden = true) + private Long createUserId; + + @Schema(hidden = true) + private String createUserName; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/bank/domain/BankEntity.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/bank/domain/BankEntity.java new file mode 100644 index 0000000..a9f0a06 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/bank/domain/BankEntity.java @@ -0,0 +1,95 @@ +package net.lab1024.sa.admin.module.business.oa.bank.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import net.lab1024.sa.base.module.support.datatracer.annoation.DataTracerFieldLabel; + +import java.time.LocalDateTime; + +/** + * OA办公-OA银行信息 + * + * @Author 1024创新实验室:善逸 + * @Date 2022/6/23 21:59:22 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName("t_oa_bank") +public class BankEntity { + + /** + * 银行信息ID + */ + @TableId(type = IdType.AUTO) + @DataTracerFieldLabel("银行信息ID") + private Long bankId; + + /** + * 开户银行 + */ + @DataTracerFieldLabel("开户银行") + private String bankName; + + /** + * 账户名称 + */ + @DataTracerFieldLabel("账户名称") + private String accountName; + + /** + * 账号 + */ + @DataTracerFieldLabel("账号") + private String accountNumber; + + /** + * 备注 + */ + @DataTracerFieldLabel("备注") + private String remark; + + /** + * 是否对公 + */ + @DataTracerFieldLabel("是否对公") + private Boolean businessFlag; + + /** + * 企业ID + */ + private Long enterpriseId; + + /** + * 禁用状态 + */ + @DataTracerFieldLabel("禁用状态") + private Boolean disabledFlag; + + /** + * 删除状态 + */ + private Boolean deletedFlag; + + /** + * 创建人ID + */ + private Long createUserId; + + /** + * 创建人ID + */ + private String createUserName; + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/bank/domain/BankQueryForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/bank/domain/BankQueryForm.java new file mode 100644 index 0000000..53a405c --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/bank/domain/BankQueryForm.java @@ -0,0 +1,40 @@ +package net.lab1024.sa.admin.module.business.oa.bank.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; +import org.hibernate.validator.constraints.Length; + +import java.time.LocalDate; + +/** + * OA办公-OA银行信息查询 + * + * @Author 1024创新实验室:善逸 + * @Date 2022/6/23 21:59:22 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class BankQueryForm extends PageParam { + + @Schema(description = "企业ID") + private Long enterpriseId; + + @Schema(description = "关键字") + @Length(max = 200, message = "关键字最多200字符") + private String keywords; + + @Schema(description = "开始时间") + private LocalDate startTime; + + @Schema(description = "结束时间") + private LocalDate endTime; + + @Schema(description = "禁用状态") + private Boolean disabledFlag; + + @Schema(description = "删除状态", hidden = true) + private Boolean deletedFlag; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/bank/domain/BankUpdateForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/bank/domain/BankUpdateForm.java new file mode 100644 index 0000000..e77d827 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/bank/domain/BankUpdateForm.java @@ -0,0 +1,22 @@ +package net.lab1024.sa.admin.module.business.oa.bank.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * OA办公-银行信息更新 + * + * @Author 1024创新实验室:善逸 + * @Date 2022/6/23 21:59:22 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class BankUpdateForm extends BankCreateForm { + + @Schema(description = "银行信息ID") + @NotNull(message = "银行信息ID不能为空") + private Long bankId; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/bank/domain/BankVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/bank/domain/BankVO.java new file mode 100644 index 0000000..d263f38 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/bank/domain/BankVO.java @@ -0,0 +1,58 @@ +package net.lab1024.sa.admin.module.business.oa.bank.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * OA办公-OA银行信息 + * + * @Author 1024创新实验室:善逸 + * @Date 2022/6/23 21:59:22 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class BankVO { + + @Schema(description = "银行信息ID") + private Long bankId; + + @Schema(description = "开户银行") + private String bankName; + + @Schema(description = "账户名称") + private String accountName; + + @Schema(description = "账号") + private String accountNumber; + + @Schema(description = "备注") + private String remark; + + @Schema(description = "是否对公") + private Boolean businessFlag; + + @Schema(description = "企业ID") + private Long enterpriseId; + + @Schema(description = "企业名称") + private String enterpriseName; + + @Schema(description = "禁用状态") + private Boolean disabledFlag; + + @Schema(description = "创建人ID") + private Long createUserId; + + @Schema(description = "创建人名称") + private String createUserName; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "更新时间") + private LocalDateTime updateTime; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/EnterpriseController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/EnterpriseController.java new file mode 100644 index 0000000..d919537 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/EnterpriseController.java @@ -0,0 +1,137 @@ +package net.lab1024.sa.admin.module.business.oa.enterprise; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.admin.constant.AdminSwaggerTagConst; +import net.lab1024.sa.admin.module.business.oa.enterprise.domain.form.*; +import net.lab1024.sa.admin.module.business.oa.enterprise.domain.vo.EnterpriseEmployeeVO; +import net.lab1024.sa.admin.module.business.oa.enterprise.domain.vo.EnterpriseExcelVO; +import net.lab1024.sa.admin.module.business.oa.enterprise.domain.vo.EnterpriseListVO; +import net.lab1024.sa.admin.module.business.oa.enterprise.domain.vo.EnterpriseVO; +import net.lab1024.sa.admin.util.AdminRequestUtil; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.RequestUser; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.*; +import net.lab1024.sa.base.module.support.operatelog.annotation.OperateLog; +import org.apache.commons.collections.CollectionUtils; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 企业 + * + * @Author 1024创新实验室: 开云 + * @Date 2022/7/28 20:37:15 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@RestController +@Tag(name = AdminSwaggerTagConst.Business.OA_ENTERPRISE) +@OperateLog +public class EnterpriseController { + + @Resource + private EnterpriseService enterpriseService; + + @Operation(summary = "分页查询企业模块 @author 开云") + @PostMapping("/oa/enterprise/page/query") + @SaCheckPermission("oa:enterprise:query") + public ResponseDTO> queryByPage(@RequestBody @Valid EnterpriseQueryForm queryForm) { + return enterpriseService.queryByPage(queryForm); + } + + @Operation(summary = "导出企业信息 @author 卓大") + @PostMapping("/oa/enterprise/exportExcel") + public void exportExcel(@RequestBody @Valid EnterpriseQueryForm queryForm, HttpServletResponse response) throws IOException { + List data = enterpriseService.getExcelExportData(queryForm); + if (CollectionUtils.isEmpty(data)) { + SmartResponseUtil.write(response, ResponseDTO.userErrorParam("暂无数据")); + return; + } + + String watermark = AdminRequestUtil.getRequestUser().getActualName(); + watermark += SmartLocalDateUtil.format(LocalDateTime.now(), SmartDateFormatterEnum.YMD_HMS); + + SmartExcelUtil.exportExcelWithWatermark(response,"企业基本信息.xlsx","企业信息",EnterpriseExcelVO.class,data,watermark); + + } + + @Operation(summary = "查询企业详情 @author 开云") + @GetMapping("/oa/enterprise/get/{enterpriseId}") + @SaCheckPermission("oa:enterprise:detail") + public ResponseDTO getDetail(@PathVariable Long enterpriseId) { + return ResponseDTO.ok(enterpriseService.getDetail(enterpriseId)); + } + + @Operation(summary = "新建企业 @author 开云") + @PostMapping("/oa/enterprise/create") + @SaCheckPermission("oa:enterprise:add") + public ResponseDTO createEnterprise(@RequestBody @Valid EnterpriseCreateForm createVO) { + RequestUser requestUser = SmartRequestUtil.getRequestUser(); + createVO.setCreateUserId(requestUser.getUserId()); + createVO.setCreateUserName(requestUser.getUserName()); + return enterpriseService.createEnterprise(createVO); + } + + @Operation(summary = "编辑企业 @author 开云") + @PostMapping("/oa/enterprise/update") + @SaCheckPermission("oa:enterprise:update") + public ResponseDTO updateEnterprise(@RequestBody @Valid EnterpriseUpdateForm updateVO) { + return enterpriseService.updateEnterprise(updateVO); + } + + @Operation(summary = "删除企业 @author 开云") + @GetMapping("/oa/enterprise/delete/{enterpriseId}") + @SaCheckPermission("oa:enterprise:delete") + public ResponseDTO deleteEnterprise(@PathVariable Long enterpriseId) { + return enterpriseService.deleteEnterprise(enterpriseId); + } + + @Operation(summary = "按照类型查询企业 @author 开云") + @GetMapping("/oa/enterprise/query/list") + @SaCheckPermission("oa:enterprise:query") + public ResponseDTO> queryList(@RequestParam(value = "type", required = false) Integer type) { + return enterpriseService.queryList(type); + } + + + @Operation(summary = "企业添加员工 @author 罗伊") + @PostMapping("/oa/enterprise/employee/add") + @SaCheckPermission("oa:enterprise:addEmployee") + public ResponseDTO addEmployee(@RequestBody @Valid EnterpriseEmployeeForm enterpriseEmployeeForm) { + return enterpriseService.addEmployee(enterpriseEmployeeForm); + } + + @Operation(summary = "查询企业全部员工 @author 罗伊") + @PostMapping("/oa/enterprise/employee/list") + @SaCheckPermission("oa:enterprise:queryEmployee") + public ResponseDTO> employeeList(@RequestBody @Valid List enterpriseIdList) { + return ResponseDTO.ok(enterpriseService.employeeList(enterpriseIdList)); + } + + @Operation(summary = "分页查询企业员工 @author 卓大") + @PostMapping("/oa/enterprise/employee/queryPage") + @SaCheckPermission("oa:enterprise:queryEmployee") + public ResponseDTO> queryPageEmployeeList(@RequestBody @Valid EnterpriseEmployeeQueryForm queryForm) { + return ResponseDTO.ok(enterpriseService.queryPageEmployeeList(queryForm)); + } + + + @Operation(summary = "企业删除员工 @author 罗伊") + @PostMapping("/oa/enterprise/employee/delete") + @SaCheckPermission("oa:enterprise:deleteEmployee") + public ResponseDTO deleteEmployee(@RequestBody @Valid EnterpriseEmployeeForm enterpriseEmployeeForm) { + return enterpriseService.deleteEmployee(enterpriseEmployeeForm); + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/EnterpriseEmployeeManager.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/EnterpriseEmployeeManager.java new file mode 100644 index 0000000..f90b21a --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/EnterpriseEmployeeManager.java @@ -0,0 +1,19 @@ +package net.lab1024.sa.admin.module.business.oa.enterprise; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import net.lab1024.sa.admin.module.business.oa.enterprise.dao.EnterpriseEmployeeDao; +import net.lab1024.sa.admin.module.business.oa.enterprise.domain.entity.EnterpriseEmployeeEntity; +import org.springframework.stereotype.Service; + +/** + * 企业员工关系 manager + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022/7/28 20:37:15 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class EnterpriseEmployeeManager extends ServiceImpl { +} \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/EnterpriseService.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/EnterpriseService.java new file mode 100644 index 0000000..5ea17e6 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/EnterpriseService.java @@ -0,0 +1,238 @@ +package net.lab1024.sa.admin.module.business.oa.enterprise; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.google.common.collect.Lists; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.admin.module.business.oa.enterprise.dao.EnterpriseDao; +import net.lab1024.sa.admin.module.business.oa.enterprise.dao.EnterpriseEmployeeDao; +import net.lab1024.sa.admin.module.business.oa.enterprise.domain.entity.EnterpriseEmployeeEntity; +import net.lab1024.sa.admin.module.business.oa.enterprise.domain.entity.EnterpriseEntity; +import net.lab1024.sa.admin.module.business.oa.enterprise.domain.form.*; +import net.lab1024.sa.admin.module.business.oa.enterprise.domain.vo.EnterpriseEmployeeVO; +import net.lab1024.sa.admin.module.business.oa.enterprise.domain.vo.EnterpriseExcelVO; +import net.lab1024.sa.admin.module.business.oa.enterprise.domain.vo.EnterpriseListVO; +import net.lab1024.sa.admin.module.business.oa.enterprise.domain.vo.EnterpriseVO; +import net.lab1024.sa.admin.module.system.department.service.DepartmentService; +import net.lab1024.sa.base.common.code.UserErrorCode; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.module.support.datatracer.constant.DataTracerTypeEnum; +import net.lab1024.sa.base.module.support.datatracer.domain.form.DataTracerForm; +import net.lab1024.sa.base.module.support.datatracer.service.DataTracerService; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * 企业 + * + * @Author 1024创新实验室: 开云 + * @Date 2022/7/28 20:37:15 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +@Slf4j +public class EnterpriseService { + + @Resource + private EnterpriseDao enterpriseDao; + + @Resource + private EnterpriseEmployeeDao enterpriseEmployeeDao; + + @Resource + private EnterpriseEmployeeManager enterpriseEmployeeManager; + + @Resource + private DataTracerService dataTracerService; + + @Resource + private DepartmentService departmentService; + + /** + * 分页查询企业模块 + * + */ + public ResponseDTO> queryByPage(EnterpriseQueryForm queryForm) { + queryForm.setDeletedFlag(Boolean.FALSE); + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List enterpriseList = enterpriseDao.queryPage(page, queryForm); + PageResult pageResult = SmartPageUtil.convert2PageResult(page, enterpriseList); + return ResponseDTO.ok(pageResult); + } + + /** + * 获取导出数据 + */ + public List getExcelExportData(EnterpriseQueryForm queryForm) { + queryForm.setDeletedFlag(false); + return enterpriseDao.selectExcelExportData(queryForm); + } + + /** + * 查询企业详情 + * + */ + public EnterpriseVO getDetail(Long enterpriseId) { + return enterpriseDao.getDetail(enterpriseId, Boolean.FALSE); + } + + /** + * 新建企业 + * + */ + @Transactional(rollbackFor = Exception.class) + public ResponseDTO createEnterprise(EnterpriseCreateForm createVO) { + // 验证企业名称是否重复 + EnterpriseEntity validateEnterprise = enterpriseDao.queryByEnterpriseName(createVO.getEnterpriseName(), null, Boolean.FALSE); + if (Objects.nonNull(validateEnterprise)) { + return ResponseDTO.userErrorParam("企业名称重复"); + } + // 数据插入 + EnterpriseEntity insertEnterprise = SmartBeanUtil.copy(createVO, EnterpriseEntity.class); + enterpriseDao.insert(insertEnterprise); + dataTracerService.insert(insertEnterprise.getEnterpriseId(), DataTracerTypeEnum.OA_ENTERPRISE); + return ResponseDTO.ok(); + } + + /** + * 编辑企业 + * + */ + @Transactional(rollbackFor = Exception.class) + public ResponseDTO updateEnterprise(EnterpriseUpdateForm updateVO) { + Long enterpriseId = updateVO.getEnterpriseId(); + // 校验企业是否存在 + EnterpriseEntity enterpriseDetail = enterpriseDao.selectById(enterpriseId); + if (Objects.isNull(enterpriseDetail) || enterpriseDetail.getDeletedFlag()) { + return ResponseDTO.userErrorParam("企业不存在"); + } + // 验证企业名称是否重复 + EnterpriseEntity validateEnterprise = enterpriseDao.queryByEnterpriseName(updateVO.getEnterpriseName(), enterpriseId, Boolean.FALSE); + if (Objects.nonNull(validateEnterprise)) { + return ResponseDTO.userErrorParam("企业名称重复"); + } + // 数据编辑 + EnterpriseEntity updateEntity = SmartBeanUtil.copy(enterpriseDetail, EnterpriseEntity.class); + SmartBeanUtil.copyProperties(updateVO, updateEntity); + enterpriseDao.updateById(updateEntity); + + //变更记录 + DataTracerForm dataTracerForm = DataTracerForm.builder() + .dataId(updateVO.getEnterpriseId()) + .type(DataTracerTypeEnum.OA_ENTERPRISE) + .content("修改企业信息") + .diffOld(dataTracerService.getChangeContent(enterpriseDetail)) + .diffNew(dataTracerService.getChangeContent(updateEntity)) + .build(); + + dataTracerService.addTrace(dataTracerForm); + return ResponseDTO.ok(); + } + + + /** + * 删除企业 + * + */ + @Transactional(rollbackFor = Exception.class) + public ResponseDTO deleteEnterprise(Long enterpriseId) { + // 校验企业是否存在 + EnterpriseEntity enterpriseDetail = enterpriseDao.selectById(enterpriseId); + if (Objects.isNull(enterpriseDetail) || enterpriseDetail.getDeletedFlag()) { + return ResponseDTO.userErrorParam("企业不存在"); + } + enterpriseDao.deleteEnterprise(enterpriseId, Boolean.TRUE); + dataTracerService.delete(enterpriseId, DataTracerTypeEnum.OA_ENTERPRISE); + return ResponseDTO.ok(); + } + + /** + * 企业列表查询 + */ + public ResponseDTO> queryList(Integer type) { + List enterpriseList = enterpriseDao.queryList(type, Boolean.FALSE, Boolean.FALSE); + return ResponseDTO.ok(enterpriseList); + } + + //----------------------------------------- 以下为员工相关-------------------------------------------- + + /** + * 企业添加员工 + * + */ + public synchronized ResponseDTO addEmployee(EnterpriseEmployeeForm enterpriseEmployeeForm) { + Long enterpriseId = enterpriseEmployeeForm.getEnterpriseId(); + EnterpriseEntity enterpriseEntity = enterpriseDao.selectById(enterpriseId); + if (enterpriseEntity == null || enterpriseEntity.getDeletedFlag()) { + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST); + } + //过滤掉已存在的员工 + List waitAddEmployeeIdList = enterpriseEmployeeForm.getEmployeeIdList(); + List enterpriseEmployeeEntityList = enterpriseEmployeeDao.selectByEnterpriseAndEmployeeIdList(enterpriseId, waitAddEmployeeIdList); + if (CollectionUtils.isNotEmpty(enterpriseEmployeeEntityList)) { + List existEmployeeIdList = enterpriseEmployeeEntityList.stream().map(EnterpriseEmployeeEntity::getEmployeeId).collect(Collectors.toList()); + waitAddEmployeeIdList = waitAddEmployeeIdList.stream().filter(e -> !existEmployeeIdList.contains(e)).collect(Collectors.toList()); + } + if (CollectionUtils.isEmpty(waitAddEmployeeIdList)) { + return ResponseDTO.ok(); + } + List batchAddList = Lists.newArrayList(); + for (Long employeeId : waitAddEmployeeIdList) { + EnterpriseEmployeeEntity enterpriseEmployeeEntity = new EnterpriseEmployeeEntity(); + enterpriseEmployeeEntity.setEnterpriseId(enterpriseId); + enterpriseEmployeeEntity.setEmployeeId(employeeId); + batchAddList.add(enterpriseEmployeeEntity); + } + enterpriseEmployeeManager.saveBatch(batchAddList); + return ResponseDTO.ok(); + } + + /** + * 企业删除员工 + * + */ + public synchronized ResponseDTO deleteEmployee(EnterpriseEmployeeForm enterpriseEmployeeForm) { + Long enterpriseId = enterpriseEmployeeForm.getEnterpriseId(); + EnterpriseEntity enterpriseEntity = enterpriseDao.selectById(enterpriseId); + if (enterpriseEntity == null || enterpriseEntity.getDeletedFlag()) { + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST); + } + List waitDeleteEmployeeIdList = enterpriseEmployeeForm.getEmployeeIdList(); + enterpriseEmployeeDao.deleteByEnterpriseAndEmployeeIdList(enterpriseId, waitDeleteEmployeeIdList); + return ResponseDTO.ok(); + } + + /** + * 企业下员工列表 + * + */ + public List employeeList(List enterpriseIdList) { + if (CollectionUtils.isEmpty(enterpriseIdList)) { + return Lists.newArrayList(); + } + return enterpriseEmployeeDao.selectByEnterpriseIdList(enterpriseIdList); + } + + /** + * 分页查询企业员工 + * + */ + public PageResult queryPageEmployeeList(EnterpriseEmployeeQueryForm queryForm) { + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List enterpriseEmployeeVOList = enterpriseEmployeeDao.queryPageEmployeeList(page, queryForm); + for (EnterpriseEmployeeVO enterpriseEmployeeVO : enterpriseEmployeeVOList) { + enterpriseEmployeeVO.setDepartmentName(departmentService.getDepartmentPath(enterpriseEmployeeVO.getDepartmentId())); + } + return SmartPageUtil.convert2PageResult(page, enterpriseEmployeeVOList); + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/constant/EnterpriseTypeEnum.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/constant/EnterpriseTypeEnum.java new file mode 100644 index 0000000..9603355 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/constant/EnterpriseTypeEnum.java @@ -0,0 +1,46 @@ +package net.lab1024.sa.admin.module.business.oa.enterprise.constant; + + +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 企业类型 + * + * @Author 1024创新实验室: 开云 + * @Date 2022/7/28 20:37:15 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public enum EnterpriseTypeEnum implements BaseEnum { + + /** + * 有限企业 + */ + NORMAL(1, "有限企业"), + + /** + * 外资企业 + */ + FOREIGN(2, "外资企业"), + ; + + private Integer value; + private String desc; + + EnterpriseTypeEnum(Integer value, String desc) { + this.value = value; + this.desc = desc; + } + + + @Override + public Integer getValue() { + return value; + } + + @Override + public String getDesc() { + return desc; + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/dao/EnterpriseDao.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/dao/EnterpriseDao.java new file mode 100644 index 0000000..bde9499 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/dao/EnterpriseDao.java @@ -0,0 +1,64 @@ +package net.lab1024.sa.admin.module.business.oa.enterprise.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.admin.module.business.oa.enterprise.domain.entity.EnterpriseEntity; +import net.lab1024.sa.admin.module.business.oa.enterprise.domain.form.EnterpriseQueryForm; +import net.lab1024.sa.admin.module.business.oa.enterprise.domain.vo.EnterpriseExcelVO; +import net.lab1024.sa.admin.module.business.oa.enterprise.domain.vo.EnterpriseListVO; +import net.lab1024.sa.admin.module.business.oa.enterprise.domain.vo.EnterpriseVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 企业 + * + * @Author 1024创新实验室: 开云 + * @Date 2022/7/28 20:37:15 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface EnterpriseDao extends BaseMapper { + + /** + * 根据企业名称查询 + * + */ + EnterpriseEntity queryByEnterpriseName(@Param("enterpriseName") String enterpriseName, @Param("excludeEnterpriseId") Long excludeEnterpriseId, @Param("deletedFlag") Boolean deletedFlag); + + /** + * 删除企业 + */ + void deleteEnterprise(@Param("enterpriseId") Long enterpriseId, @Param("deletedFlag") Boolean deletedFlag); + + /** + * 企业分页查询 + * + */ + List queryPage(Page page, @Param("queryForm") EnterpriseQueryForm queryForm); + + /** + * 查询导出的数据 + * + */ + List selectExcelExportData(@Param("queryForm") EnterpriseQueryForm queryForm); + + /** + * 查询企业详情 + * + */ + EnterpriseVO getDetail(@Param("enterpriseId") Long enterpriseId, @Param("deletedFlag") Boolean deletedFlag); + + /** + * 查询列表 + * + */ + List queryList(@Param("type") Integer type, @Param("disabledFlag") Boolean disabledFlag, @Param("deletedFlag") Boolean deletedFlag); + + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/dao/EnterpriseEmployeeDao.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/dao/EnterpriseEmployeeDao.java new file mode 100644 index 0000000..e73a493 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/dao/EnterpriseEmployeeDao.java @@ -0,0 +1,69 @@ +package net.lab1024.sa.admin.module.business.oa.enterprise.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.admin.module.business.oa.enterprise.domain.entity.EnterpriseEmployeeEntity; +import net.lab1024.sa.admin.module.business.oa.enterprise.domain.form.EnterpriseEmployeeQueryForm; +import net.lab1024.sa.admin.module.business.oa.enterprise.domain.vo.EnterpriseEmployeeVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.Collection; +import java.util.List; + +/** + * 企业员工 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022/7/28 20:37:15 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface EnterpriseEmployeeDao extends BaseMapper { + + + /** + * 根据员工查询 + */ + List selectByEmployeeIdList(@Param("employeeIdList")Collection employeeIdList); + + /** + * 查询员工关联的企业 + */ + List selectEnterpriseIdByEmployeeId(@Param("employeeId")Long employeeId); + /** + * 根据企业查询 + */ + List selectByEnterpriseIdList(@Param("enterpriseIdList")Collection enterpriseIdList); + /** + * 根据企业查询 + */ + List selectByEnterpriseId(@Param("enterpriseId")Long enterpriseId); + + /** + * 查询企业下的所有员工id + */ + List selectEmployeeIdByEnterpriseIdList(@Param("enterpriseIdList")Collection enterpriseIdList); + /** + * 根据员工删除 + */ + void deleteByEnterpriseAndEmployeeIdList(@Param("enterpriseId")Long enterpriseId, @Param("employeeIdList")Collection employeeIdList); + + /** + * 根据员工查询 + */ + List selectByEnterpriseAndEmployeeIdList(@Param("enterpriseId")Long enterpriseId, @Param("employeeIdList")Collection employeeIdList); + + /** + * 删除某员工关联的所有企业 + */ + void deleteByEmployeeId(@Param("employeeId")Long employeeId); + + /** + * 分页查询企业员工 + */ + List queryPageEmployeeList(Page page,@Param("queryForm") EnterpriseEmployeeQueryForm queryForm); +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/domain/entity/EnterpriseEmployeeEntity.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/domain/entity/EnterpriseEmployeeEntity.java new file mode 100644 index 0000000..a252e66 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/domain/entity/EnterpriseEmployeeEntity.java @@ -0,0 +1,51 @@ +package net.lab1024.sa.admin.module.business.oa.enterprise.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +/** + * 企业员工 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022/7/28 20:37:15 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName("t_oa_enterprise_employee") +@NoArgsConstructor +public class EnterpriseEmployeeEntity { + + @TableId(type = IdType.AUTO) + private Long enterpriseEmployeeId; + + /** + * 企业ID + */ + private Long enterpriseId; + /** + * 员工 + */ + private Long employeeId; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + + public EnterpriseEmployeeEntity(Long enterpriseId, Long employeeId) { + this.enterpriseId = enterpriseId; + this.employeeId = employeeId; + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/domain/entity/EnterpriseEntity.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/domain/entity/EnterpriseEntity.java new file mode 100644 index 0000000..6bbbf67 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/domain/entity/EnterpriseEntity.java @@ -0,0 +1,153 @@ +package net.lab1024.sa.admin.module.business.oa.enterprise.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import net.lab1024.sa.admin.module.business.oa.enterprise.constant.EnterpriseTypeEnum; +import net.lab1024.sa.base.module.support.datatracer.annoation.DataTracerFieldEnum; +import net.lab1024.sa.base.module.support.datatracer.annoation.DataTracerFieldLabel; + +import java.time.LocalDateTime; + +/** + * 企业 + * + * @Author 1024创新实验室: 开云 + * @Date 2022/7/28 20:37:15 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName("t_oa_enterprise") +public class EnterpriseEntity { + + /** + * 企业ID + */ + @TableId(type = IdType.AUTO) + private Long enterpriseId; + + /** + * 企业名称 + */ + @DataTracerFieldLabel("企业名称") + private String enterpriseName; + + /** + * 企业logo + */ + @DataTracerFieldLabel("企业logo") + private String enterpriseLogo; + + /** + * 统一社会信用代码 + */ + @DataTracerFieldLabel("统一社会信用代码") + private String unifiedSocialCreditCode; + + /** + * 类型 + * + * @see EnterpriseTypeEnum + */ + @DataTracerFieldLabel("类型") + @DataTracerFieldEnum(enumClass = EnterpriseTypeEnum.class) + private Integer type; + + /** + * 联系人 + */ + @DataTracerFieldLabel("联系人") + private String contact; + + /** + * 联系人电话 + */ + @DataTracerFieldLabel("联系人电话") + private String contactPhone; + + /** + * 邮箱 + */ + @DataTracerFieldLabel("邮箱") + private String email; + + /** + * 省份 + */ + private Integer province; + + /** + * 省份名称 + */ + @DataTracerFieldLabel("省份名称") + private String provinceName; + + /** + * 城市 + */ + private Integer city; + + /** + * 城市名称 + */ + @DataTracerFieldLabel("城市名称") + private String cityName; + + /** + * 区县 + */ + private Integer district; + + /** + * 区县名称 + */ + @DataTracerFieldLabel("区县名称") + private String districtName; + + /** + * 详细地址 + */ + @DataTracerFieldLabel("详细地址") + private String address; + + /** + * 营业执照 + */ + @DataTracerFieldLabel("营业执照") + private String businessLicense; + + /** + * 禁用状态 + */ + @DataTracerFieldLabel("禁用状态") + private Boolean disabledFlag; + + /** + * 删除状态 + */ + @DataTracerFieldLabel("删除状态") + private Boolean deletedFlag; + + /** + * 创建人ID + */ + private Long createUserId; + + /** + * 创建人ID + */ + private String createUserName; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/domain/form/EnterpriseCreateForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/domain/form/EnterpriseCreateForm.java new file mode 100644 index 0000000..02ff750 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/domain/form/EnterpriseCreateForm.java @@ -0,0 +1,100 @@ +package net.lab1024.sa.admin.module.business.oa.enterprise.domain.form; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import lombok.Data; +import net.lab1024.sa.admin.module.business.oa.enterprise.constant.EnterpriseTypeEnum; +import net.lab1024.sa.base.common.json.deserializer.FileKeyVoDeserializer; +import net.lab1024.sa.base.common.json.serializer.FileKeyVoSerializer; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.common.util.SmartVerificationUtil; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; +import org.hibernate.validator.constraints.Length; + +/** + * OA企业模块创建 + * + * @Author 1024创新实验室: 开云 + * @Date 2022/7/28 20:37:15 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class EnterpriseCreateForm { + + @Schema(description = "企业名称") + @NotBlank(message = "企业名称不能为空") + @Length(max = 200, message = "企业名称最多200字符") + private String enterpriseName; + + @Schema(description = "企业logo") + @JsonSerialize(using = FileKeyVoSerializer.class) + @JsonDeserialize(using = FileKeyVoDeserializer.class) + private String enterpriseLogo; + + @Schema(description = "统一社会信用代码") + @NotBlank(message = "统一社会信用代码不能为空") + @Length(max = 200, message = "统一社会信用代码最多200字符") + private String unifiedSocialCreditCode; + + @Schema(description = "联系人") + @NotBlank(message = "联系人不能为空") + @Length(max = 100, message = "联系人最多100字符") + private String contact; + + @Schema(description = "联系人电话") + @NotBlank(message = "联系人电话不能为空") + @Pattern(regexp = SmartVerificationUtil.PHONE_REGEXP, message = "手机号格式不正确") + private String contactPhone; + + @SchemaEnum(desc = "类型", value = EnterpriseTypeEnum.class) + @CheckEnum(message = "类型不正确", value = EnterpriseTypeEnum.class) + private Integer type; + + @Schema(description = "邮箱") + @Pattern(regexp = SmartVerificationUtil.EMAIL, message = "邮箱格式不正确") + private String email; + + @Schema(description = "省份") + private Integer province; + + @Schema(description = "省份名称") + private String provinceName; + + @Schema(description = "城市") + private Integer city; + + @Schema(description = "城市名称") + private String cityName; + + @Schema(description = "区县") + private Integer district; + + @Schema(description = "区县名称") + private String districtName; + + @Schema(description = "详细地址") + @Length(max = 500, message = "详细地址最多500字符") + private String address; + + @Schema(description = "营业执照") + @JsonSerialize(using = FileKeyVoSerializer.class) + @JsonDeserialize(using = FileKeyVoDeserializer.class) + private String businessLicense; + + @Schema(description = "禁用状态") + @NotNull(message = "禁用状态不能为空") + private Boolean disabledFlag; + + @Schema(description = "创建人", hidden = true) + private Long createUserId; + + @Schema(description = "创建人", hidden = true) + private String createUserName; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/domain/form/EnterpriseEmployeeForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/domain/form/EnterpriseEmployeeForm.java new file mode 100644 index 0000000..ce74d4e --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/domain/form/EnterpriseEmployeeForm.java @@ -0,0 +1,29 @@ +package net.lab1024.sa.admin.module.business.oa.enterprise.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +/** + * 企业员工 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022/7/28 20:37:15 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class EnterpriseEmployeeForm { + + @Schema(description = "企业id") + @NotNull(message = "企业id不能为空") + private Long enterpriseId; + + @Schema(description = "员工信息id") + @NotEmpty(message = "员工信息id不能为空") + private List employeeIdList; +} \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/domain/form/EnterpriseEmployeeQueryForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/domain/form/EnterpriseEmployeeQueryForm.java new file mode 100644 index 0000000..eba6b98 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/domain/form/EnterpriseEmployeeQueryForm.java @@ -0,0 +1,32 @@ +package net.lab1024.sa.admin.module.business.oa.enterprise.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; +import org.hibernate.validator.constraints.Length; + +/** + * 查询企业员工 + * + * @Author 1024创新实验室: 开云 + * @Date 2021-12-20 21:06:49 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class EnterpriseEmployeeQueryForm extends PageParam { + + @Schema(description = "搜索词") + @Length(max = 20, message = "搜索词最多20字符") + private String keyword; + + @Schema(description = "公司Id") + @NotNull(message = "公司id 不能为空") + private Long enterpriseId; + + @Schema(description = "删除标识", hidden = true) + private Boolean deletedFlag; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/domain/form/EnterpriseQueryForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/domain/form/EnterpriseQueryForm.java new file mode 100644 index 0000000..7891508 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/domain/form/EnterpriseQueryForm.java @@ -0,0 +1,38 @@ +package net.lab1024.sa.admin.module.business.oa.enterprise.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; +import org.hibernate.validator.constraints.Length; + +import java.time.LocalDate; + +/** + * OA企业模块分页查询 + * + * @Author 1024创新实验室: 开云 + * @Date 2022/7/28 20:37:15 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class EnterpriseQueryForm extends PageParam { + + @Schema(description = "关键字") + @Length(max = 200, message = "关键字最多200字符") + private String keywords; + + @Schema(description = "开始时间") + private LocalDate startTime; + + @Schema(description = "结束时间") + private LocalDate endTime; + + @Schema(description = "禁用状态") + private Boolean disabledFlag; + + @Schema(description = "删除状态", hidden = true) + private Boolean deletedFlag; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/domain/form/EnterpriseUpdateForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/domain/form/EnterpriseUpdateForm.java new file mode 100644 index 0000000..f2978d2 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/domain/form/EnterpriseUpdateForm.java @@ -0,0 +1,22 @@ +package net.lab1024.sa.admin.module.business.oa.enterprise.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * OA企业模块编辑 + * + * @Author 1024创新实验室: 开云 + * @Date 2022/7/28 20:37:15 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class EnterpriseUpdateForm extends EnterpriseCreateForm { + + @Schema(description = "企业ID") + @NotNull(message = "企业ID不能为空") + private Long enterpriseId; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/domain/vo/EnterpriseEmployeeVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/domain/vo/EnterpriseEmployeeVO.java new file mode 100644 index 0000000..18aa205 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/domain/vo/EnterpriseEmployeeVO.java @@ -0,0 +1,47 @@ +package net.lab1024.sa.admin.module.business.oa.enterprise.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 企业员工信息 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022/7/28 20:37:15 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class EnterpriseEmployeeVO { + + private Long enterpriseEmployeeId; + + @Schema(description = "企业ID") + private Long enterpriseId; + + @Schema(description = "企业名称") + private String enterpriseName; + + @Schema(description = "员工") + private Long employeeId; + + @Schema(description = "登录账号") + private String loginName; + + @Schema(description = "员工名称") + private String actualName; + + @Schema(description = "手机号码") + private String phone; + + @Schema(description = "部门id") + private Long departmentId; + + @Schema(description = "是否被禁用") + private Boolean disabledFlag; + + @Schema(description = "部门名称") + private String departmentName; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/domain/vo/EnterpriseExcelVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/domain/vo/EnterpriseExcelVO.java new file mode 100644 index 0000000..8b9c838 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/domain/vo/EnterpriseExcelVO.java @@ -0,0 +1,48 @@ +package net.lab1024.sa.admin.module.business.oa.enterprise.domain.vo; + +import cn.idev.excel.annotation.ExcelProperty; +import lombok.Data; + +/** + * 企业信息 + * + * @Author 1024创新实验室: 开云 + * @Date 2022/7/28 20:37:15 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class EnterpriseExcelVO { + + @ExcelProperty("企业名称") + private String enterpriseName; + + @ExcelProperty("统一社会信用代码") + private String unifiedSocialCreditCode; + + @ExcelProperty("企业类型") + private String typeName; + + @ExcelProperty("联系人") + private String contact; + + @ExcelProperty("联系人电话") + private String contactPhone; + + @ExcelProperty("邮箱") + private String email; + + @ExcelProperty("省份名称") + private String provinceName; + + @ExcelProperty("城市名称") + private String cityName; + + @ExcelProperty("区县名称") + private String districtName; + + @ExcelProperty("详细地址") + private String address; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/domain/vo/EnterpriseListVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/domain/vo/EnterpriseListVO.java new file mode 100644 index 0000000..1320292 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/domain/vo/EnterpriseListVO.java @@ -0,0 +1,20 @@ +package net.lab1024.sa.admin.module.business.oa.enterprise.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * OA企业模块列表 + * + * @author lihaifan + * @date 2022/6/23 14:31 + */ +@Data +public class EnterpriseListVO { + + @Schema(description = "企业ID") + private Long enterpriseId; + + @Schema(description = "企业名称") + private String enterpriseName; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/domain/vo/EnterpriseVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/domain/vo/EnterpriseVO.java new file mode 100644 index 0000000..54d9b47 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/enterprise/domain/vo/EnterpriseVO.java @@ -0,0 +1,89 @@ +package net.lab1024.sa.admin.module.business.oa.enterprise.domain.vo; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.admin.module.business.oa.enterprise.constant.EnterpriseTypeEnum; +import net.lab1024.sa.base.common.json.serializer.FileKeyVoSerializer; +import net.lab1024.sa.base.common.swagger.SchemaEnum; + +import java.time.LocalDateTime; + +/** + * 企业信息 + * + * @Author 1024创新实验室: 开云 + * @Date 2022/7/28 20:37:15 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class EnterpriseVO { + + @Schema(description = "企业ID") + private Long enterpriseId; + + @Schema(description = "企业名称") + private String enterpriseName; + + @Schema(description = "企业logo") + @JsonSerialize(using = FileKeyVoSerializer.class) + private String enterpriseLogo; + + @Schema(description = "统一社会信用代码") + private String unifiedSocialCreditCode; + + @SchemaEnum(desc = "类型", value = EnterpriseTypeEnum.class) + private Integer type; + + @Schema(description = "联系人") + private String contact; + + @Schema(description = "联系人电话") + private String contactPhone; + + @Schema(description = "邮箱") + private String email; + + @Schema(description = "省份") + private Integer province; + + @Schema(description = "省份名称") + private String provinceName; + + @Schema(description = "城市") + private Integer city; + + @Schema(description = "城市名称") + private String cityName; + + @Schema(description = "区县") + private Integer district; + + @Schema(description = "区县名称") + private String districtName; + + @Schema(description = "详细地址") + private String address; + + @Schema(description = "营业执照") + @JsonSerialize(using = FileKeyVoSerializer.class) + private String businessLicense; + + @Schema(description = "禁用状态") + private Boolean disabledFlag; + + @Schema(description = "创建人ID") + private Long createUserId; + + @Schema(description = "创建人名称") + private String createUserName; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "更新时间") + private LocalDateTime updateTime; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/invoice/InvoiceController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/invoice/InvoiceController.java new file mode 100644 index 0000000..13d3b98 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/invoice/InvoiceController.java @@ -0,0 +1,87 @@ +package net.lab1024.sa.admin.module.business.oa.invoice; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.admin.constant.AdminSwaggerTagConst; +import net.lab1024.sa.admin.module.business.oa.invoice.domain.InvoiceAddForm; +import net.lab1024.sa.admin.module.business.oa.invoice.domain.InvoiceQueryForm; +import net.lab1024.sa.admin.module.business.oa.invoice.domain.InvoiceUpdateForm; +import net.lab1024.sa.admin.module.business.oa.invoice.domain.InvoiceVO; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.RequestUser; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartRequestUtil; +import net.lab1024.sa.base.module.support.operatelog.annotation.OperateLog; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * OA发票信息 + * + * @Author 1024创新实验室: 善逸 + * @Date 2022-06-23 19:32:59 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@RestController +@Tag(name = AdminSwaggerTagConst.Business.OA_INVOICE) +public class InvoiceController { + + @Resource + private InvoiceService invoiceService; + + @Operation(summary = "分页查询发票信息 @author 善逸") + @PostMapping("/oa/invoice/page/query") + @SaCheckPermission("oa:invoice:query") + public ResponseDTO> queryByPage(@RequestBody @Valid InvoiceQueryForm queryForm) { + return invoiceService.queryByPage(queryForm); + } + + @Operation(summary = "查询发票信息详情 @author 善逸") + @GetMapping("/oa/invoice/get/{invoiceId}") + @SaCheckPermission("oa:invoice:query") + public ResponseDTO getDetail(@PathVariable Long invoiceId) { + return invoiceService.getDetail(invoiceId); + } + + @Operation(summary = "新建发票信息 @author 善逸") + @PostMapping("/oa/invoice/create") + @SaCheckPermission("oa:invoice:add") + public ResponseDTO createInvoice(@RequestBody @Valid InvoiceAddForm createVO) { + RequestUser requestUser = SmartRequestUtil.getRequestUser(); + createVO.setCreateUserId(requestUser.getUserId()); + createVO.setCreateUserName(requestUser.getUserName()); + return invoiceService.createInvoice(createVO); + } + + @OperateLog + @Operation(summary = "编辑发票信息 @author 善逸") + @PostMapping("/oa/invoice/update") + @SaCheckPermission("oa:invoice:update") + public ResponseDTO updateInvoice(@RequestBody @Valid InvoiceUpdateForm updateVO) { + return invoiceService.updateInvoice(updateVO); + } + + @Operation(summary = "删除发票信息 @author 善逸") + @GetMapping("/invoice/delete/{invoiceId}") + @SaCheckPermission("oa:invoice:delete") + public ResponseDTO deleteInvoice(@PathVariable Long invoiceId) { + return invoiceService.deleteInvoice(invoiceId); + } + + @Operation(summary = "查询列表 @author lidoudou") + @GetMapping("/oa/invoice/query/list/{enterpriseId}") + @SaCheckPermission("oa:invoice:query") + public ResponseDTO> queryList(@PathVariable Long enterpriseId) { + return invoiceService.queryList(enterpriseId); + } + + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/invoice/InvoiceDao.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/invoice/InvoiceDao.java new file mode 100644 index 0000000..68c8d9a --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/invoice/InvoiceDao.java @@ -0,0 +1,60 @@ +package net.lab1024.sa.admin.module.business.oa.invoice; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.admin.module.business.oa.invoice.domain.InvoiceEntity; +import net.lab1024.sa.admin.module.business.oa.invoice.domain.InvoiceQueryForm; +import net.lab1024.sa.admin.module.business.oa.invoice.domain.InvoiceVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * OA发票信息 + * + * @Author 1024创新实验室: 善逸 + * @Date 2022-06-23 19:32:59 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface InvoiceDao extends BaseMapper { + + /** + * 根据账号查询 + * @param enterpriseId + * @param accountNumber + * @param excludeInvoiceId + * @param deletedFlag + * @return + */ + InvoiceEntity queryByAccountNumber(@Param("enterpriseId") Long enterpriseId, @Param("accountNumber") String accountNumber, @Param("excludeInvoiceId") Long excludeInvoiceId, @Param("deletedFlag") Boolean deletedFlag); + + /** + * 删除发票信息 + * + * @param invoiceId + * @param deletedFlag + */ + void deleteInvoice(@Param("invoiceId") Long invoiceId, @Param("deletedFlag") Boolean deletedFlag); + + /** + * 发票信息分页查询 + * + * @param page + * @param queryForm + * @return + */ + List queryPage(Page page, @Param("queryForm") InvoiceQueryForm queryForm); + + /** + * 查询发票信息详情 + * @param invoiceId + * @param deletedFlag + * @return + */ + InvoiceVO getDetail(@Param("invoiceId") Long invoiceId, @Param("deletedFlag") Boolean deletedFlag); +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/invoice/InvoiceService.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/invoice/InvoiceService.java new file mode 100644 index 0000000..dda73c3 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/invoice/InvoiceService.java @@ -0,0 +1,143 @@ +package net.lab1024.sa.admin.module.business.oa.invoice; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.admin.module.business.oa.enterprise.EnterpriseService; +import net.lab1024.sa.admin.module.business.oa.enterprise.domain.vo.EnterpriseVO; +import net.lab1024.sa.admin.module.business.oa.invoice.domain.*; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.module.support.datatracer.constant.DataTracerConst; +import net.lab1024.sa.base.module.support.datatracer.constant.DataTracerTypeEnum; +import net.lab1024.sa.base.module.support.datatracer.service.DataTracerService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Objects; + +/** + * OA发票信息 + * + * @Author 1024创新实验室: 善逸 + * @Date 2022-06-23 19:32:59 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +@Slf4j +public class InvoiceService { + + @Resource + private InvoiceDao invoiceDao; + + @Resource + private EnterpriseService enterpriseService; + + @Resource + private DataTracerService dataTracerService; + + /** + * 分页查询发票信息 + */ + public ResponseDTO> queryByPage(InvoiceQueryForm queryForm) { + queryForm.setDeletedFlag(Boolean.FALSE); + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List invoiceList = invoiceDao.queryPage(page, queryForm); + PageResult pageResult = SmartPageUtil.convert2PageResult(page, invoiceList); + return ResponseDTO.ok(pageResult); + } + + public ResponseDTO> queryList(Long enterpriseId) { + InvoiceQueryForm queryForm = new InvoiceQueryForm(); + queryForm.setDeletedFlag(Boolean.FALSE); + queryForm.setDisabledFlag(Boolean.FALSE); + queryForm.setEnterpriseId(enterpriseId); + List invoiceList = invoiceDao.queryPage(null, queryForm); + return ResponseDTO.ok(invoiceList); + } + + /** + * 查询发票信息详情 + */ + public ResponseDTO getDetail(Long invoiceId) { + // 校验发票信息是否存在 + InvoiceVO invoiceVO = invoiceDao.getDetail(invoiceId, Boolean.FALSE); + if (Objects.isNull(invoiceVO)) { + return ResponseDTO.userErrorParam("发票信息不存在"); + } + return ResponseDTO.ok(invoiceVO); + } + + /** + * 新建发票信息 + */ + @Transactional(rollbackFor = Exception.class) + public ResponseDTO createInvoice(InvoiceAddForm createVO) { + Long enterpriseId = createVO.getEnterpriseId(); + // 校验企业是否存在 + EnterpriseVO enterpriseVO = enterpriseService.getDetail(enterpriseId); + if (Objects.isNull(enterpriseVO)) { + return ResponseDTO.userErrorParam("企业不存在"); + } + // 验证发票信息账号是否重复 + InvoiceEntity validateInvoice = invoiceDao.queryByAccountNumber(enterpriseId, createVO.getAccountNumber(), null, Boolean.FALSE); + if (Objects.nonNull(validateInvoice)) { + return ResponseDTO.userErrorParam("发票信息账号重复"); + } + // 数据插入 + InvoiceEntity insertInvoice = SmartBeanUtil.copy(createVO, InvoiceEntity.class); + invoiceDao.insert(insertInvoice); + dataTracerService.addTrace(enterpriseId, DataTracerTypeEnum.OA_ENTERPRISE, "新增发票:" + DataTracerConst.HTML_BR + dataTracerService.getChangeContent(insertInvoice)); + return ResponseDTO.ok(); + } + + /** + * 编辑发票信息 + */ + @Transactional(rollbackFor = Exception.class) + public ResponseDTO updateInvoice(InvoiceUpdateForm updateVO) { + Long enterpriseId = updateVO.getEnterpriseId(); + // 校验企业是否存在 + EnterpriseVO enterpriseVO = enterpriseService.getDetail(enterpriseId); + if (Objects.isNull(enterpriseVO)) { + return ResponseDTO.userErrorParam("企业不存在"); + } + Long invoiceId = updateVO.getInvoiceId(); + // 校验发票信息是否存在 + InvoiceEntity invoiceDetail = invoiceDao.selectById(invoiceId); + if (Objects.isNull(invoiceDetail) || invoiceDetail.getDeletedFlag()) { + return ResponseDTO.userErrorParam("发票信息不存在"); + } + // 验证发票信息账号是否重复 + InvoiceEntity validateInvoice = invoiceDao.queryByAccountNumber(updateVO.getEnterpriseId(), updateVO.getAccountNumber(), invoiceId, Boolean.FALSE); + if (Objects.nonNull(validateInvoice)) { + return ResponseDTO.userErrorParam("发票信息账号重复"); + } + // 数据编辑 + InvoiceEntity updateInvoice = SmartBeanUtil.copy(updateVO, InvoiceEntity.class); + invoiceDao.updateById(updateInvoice); + dataTracerService.addTrace(enterpriseId, DataTracerTypeEnum.OA_ENTERPRISE, "更新发票:" + DataTracerConst.HTML_BR + dataTracerService.getChangeContent(invoiceDetail, updateInvoice)); + return ResponseDTO.ok(); + } + + + /** + * 删除发票信息 + */ + @Transactional(rollbackFor = Exception.class) + public ResponseDTO deleteInvoice(Long invoiceId) { + // 校验发票信息是否存在 + InvoiceEntity invoiceDetail = invoiceDao.selectById(invoiceId); + if (Objects.isNull(invoiceDetail) || invoiceDetail.getDeletedFlag()) { + return ResponseDTO.userErrorParam("发票信息不存在"); + } + invoiceDao.deleteInvoice(invoiceId, Boolean.TRUE); + dataTracerService.addTrace(invoiceDetail.getEnterpriseId(), DataTracerTypeEnum.OA_ENTERPRISE, "删除发票:" + DataTracerConst.HTML_BR + dataTracerService.getChangeContent(invoiceDetail)); + return ResponseDTO.ok(); + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/invoice/domain/InvoiceAddForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/invoice/domain/InvoiceAddForm.java new file mode 100644 index 0000000..136178a --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/invoice/domain/InvoiceAddForm.java @@ -0,0 +1,58 @@ +package net.lab1024.sa.admin.module.business.oa.invoice.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +/** + * OA发票信息新建 + * + * @Author 1024创新实验室: 善逸 + * @Date 2022-06-23 19:32:59 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class InvoiceAddForm { + + @Schema(description = "开票抬头") + @NotBlank(message = "开票抬头不能为空") + @Length(max = 200, message = "开票抬头最多200字符") + private String invoiceHeads; + + @Schema(description = "纳税人识别号") + @NotBlank(message = "纳税人识别号不能为空") + @Length(max = 200, message = "纳税人识别号最多200字符") + private String taxpayerIdentificationNumber; + + @Schema(description = "银行账户") + @NotBlank(message = "银行账户不能为空") + @Length(max = 200, message = "银行账户最多200字符") + private String accountNumber; + + @Schema(description = "开户行") + @NotBlank(message = "开户行不能为空") + @Length(max = 200, message = "开户行最多200字符") + private String bankName; + + @Schema(description = "启用状态") + @NotNull(message = "启用状态不能为空") + private Boolean disabledFlag; + + @Schema(description = "备注") + @Length(max = 500, message = "备注最多500字符") + private String remark; + + @Schema(description = "企业") + @NotNull(message = "企业不能为空") + private Long enterpriseId; + + @Schema(description = "创建人", hidden = true) + private Long createUserId; + + @Schema(description = "创建人名称", hidden = true) + private String createUserName; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/invoice/domain/InvoiceEntity.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/invoice/domain/InvoiceEntity.java new file mode 100644 index 0000000..3fcde0f --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/invoice/domain/InvoiceEntity.java @@ -0,0 +1,98 @@ +package net.lab1024.sa.admin.module.business.oa.invoice.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import net.lab1024.sa.base.module.support.datatracer.annoation.DataTracerFieldLabel; + +import java.time.LocalDateTime; + +/** + * OA发票信息 + * + * @Author 1024创新实验室: 善逸 + * @Date 2022-06-23 19:32:59 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName("t_oa_invoice") +public class InvoiceEntity { + + /** + * 发票信息ID + */ + @TableId(type = IdType.AUTO) + private Long invoiceId; + + /** + * 开票抬头 + */ + @DataTracerFieldLabel("开票抬头") + private String invoiceHeads; + + /** + * 纳税人识别号 + */ + @DataTracerFieldLabel("纳税人识别号") + private String taxpayerIdentificationNumber; + + /** + * 银行账户 + */ + @DataTracerFieldLabel("银行账户") + private String accountNumber; + + /** + * 开户行 + */ + @DataTracerFieldLabel("开户行") + private String bankName; + + /** + * 备注 + */ + @DataTracerFieldLabel("备注") + private String remark; + + /** + * 企业ID + */ + private Long enterpriseId; + + /** + * 禁用状态 + */ + @DataTracerFieldLabel("禁用状态") + private Boolean disabledFlag; + + /** + * 删除状态 + */ + @DataTracerFieldLabel("删除状态") + private Boolean deletedFlag; + + /** + * 创建人ID + */ + private Long createUserId; + + /** + * 创建人ID + */ + private String createUserName; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/invoice/domain/InvoiceQueryForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/invoice/domain/InvoiceQueryForm.java new file mode 100644 index 0000000..497778f --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/invoice/domain/InvoiceQueryForm.java @@ -0,0 +1,40 @@ +package net.lab1024.sa.admin.module.business.oa.invoice.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; +import org.hibernate.validator.constraints.Length; + +import java.time.LocalDate; + +/** + * OA发票信息查询 + * + * @Author 1024创新实验室: 善逸 + * @Date 2022-06-23 19:32:59 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class InvoiceQueryForm extends PageParam { + + @Schema(description = "企业ID") + private Long enterpriseId; + + @Schema(description = "关键字") + @Length(max = 200, message = "关键字最多200字符") + private String keywords; + + @Schema(description = "开始时间") + private LocalDate startTime; + + @Schema(description = "结束时间") + private LocalDate endTime; + + @Schema(description = "禁用状态") + private Boolean disabledFlag; + + @Schema(description = "删除状态", hidden = true) + private Boolean deletedFlag; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/invoice/domain/InvoiceUpdateForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/invoice/domain/InvoiceUpdateForm.java new file mode 100644 index 0000000..a8c6d6c --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/invoice/domain/InvoiceUpdateForm.java @@ -0,0 +1,22 @@ +package net.lab1024.sa.admin.module.business.oa.invoice.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * OA发票信息编辑 + * + * @Author 1024创新实验室: 善逸 + * @Date 2022-06-23 19:32:59 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class InvoiceUpdateForm extends InvoiceAddForm { + + @Schema(description = "发票信息ID") + @NotNull(message = "发票信息ID不能为空") + private Long invoiceId; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/invoice/domain/InvoiceVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/invoice/domain/InvoiceVO.java new file mode 100644 index 0000000..2e9e645 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/invoice/domain/InvoiceVO.java @@ -0,0 +1,58 @@ +package net.lab1024.sa.admin.module.business.oa.invoice.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * OA发票信息 + * + * @Author 1024创新实验室: 善逸 + * @Date 2022-06-23 19:32:59 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class InvoiceVO { + + @Schema(description = "发票信息ID") + private Long invoiceId; + + @Schema(description = "开票抬头") + private String invoiceHeads; + + @Schema(description = "纳税人识别号") + private String taxpayerIdentificationNumber; + + @Schema(description = "银行账户") + private String accountNumber; + + @Schema(description = "开户行") + private String bankName; + + @Schema(description = "备注") + private String remark; + + @Schema(description = "企业") + private Long enterpriseId; + + @Schema(description = "企业名称") + private String enterpriseName; + + @Schema(description = "禁用状态") + private Boolean disabledFlag; + + @Schema(description = "创建人ID") + private Long createUserId; + + @Schema(description = "创建人名称") + private String createUserName; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "更新时间") + private LocalDateTime updateTime; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/leave/controller/LeaveController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/leave/controller/LeaveController.java new file mode 100644 index 0000000..134e864 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/leave/controller/LeaveController.java @@ -0,0 +1,78 @@ +package net.lab1024.sa.admin.module.business.oa.leave.controller; + +import net.lab1024.sa.admin.module.business.oa.leave.domain.form.LeaveAddForm; +import net.lab1024.sa.admin.module.business.oa.leave.domain.form.LeaveQueryForm; +import net.lab1024.sa.admin.module.business.oa.leave.domain.form.LeaveUpdateForm; +import net.lab1024.sa.admin.module.business.oa.leave.domain.vo.LeaveVO; +import net.lab1024.sa.admin.module.business.oa.leave.service.LeaveService; +import net.lab1024.sa.base.common.domain.ValidateList; +import net.lab1024.sa.base.common.util.SmartRequestUtil; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.domain.PageResult; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import cn.dev33.satoken.annotation.SaCheckPermission; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; + +/** + * OA 请假申请表 Controller + * + * @Author lf + * @Date 2025-05-31 20:00:10 + * @Copyright lf + */ + +@RestController +@Tag(name = "OA 请假申请表") +public class LeaveController { + + @Resource + private LeaveService leaveService; + + @Operation(summary = "分页查询 @author lf") + @PostMapping("/leave/queryPage") + @SaCheckPermission("leave:query") + public ResponseDTO> queryPage(@RequestBody @Valid LeaveQueryForm queryForm) { + return ResponseDTO.ok(leaveService.queryPage(queryForm)); + } + + @Operation(summary = "添加 @author lf") + @PostMapping("/leave/add") + @SaCheckPermission("leave:add") + public ResponseDTO add(@RequestBody @Valid LeaveAddForm addForm) { + addForm.setCreateUserId(Long.valueOf(SmartRequestUtil.getRequestUserId())); + return leaveService.add(addForm); + } + + @Operation(summary = "更新 @author lf") + @PostMapping("/leave/update") + @SaCheckPermission("leave:update") + public ResponseDTO update(@RequestBody @Valid LeaveUpdateForm updateForm) { + return leaveService.update(updateForm); + } + + @Operation(summary = "批量删除 @author lf") + @PostMapping("/leave/batchDelete") + @SaCheckPermission("leave:delete") + public ResponseDTO batchDelete(@RequestBody ValidateList idList) { + return leaveService.batchDelete(idList); + } + + @Operation(summary = "单个删除 @author lf") + @GetMapping("/leave/delete/{id}") + @SaCheckPermission("leave:delete") + public ResponseDTO batchDelete(@PathVariable Long id) { + return leaveService.delete(id); + } + + @GetMapping("/leave/{id}") + public ResponseDTO getDetail(@PathVariable Long id) { + return ResponseDTO.ok(leaveService.getDetail(id)); + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/leave/dao/LeaveDao.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/leave/dao/LeaveDao.java new file mode 100644 index 0000000..bb6ad9d --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/leave/dao/LeaveDao.java @@ -0,0 +1,33 @@ +package net.lab1024.sa.admin.module.business.oa.leave.dao; + +import java.util.List; +import net.lab1024.sa.admin.module.business.oa.leave.domain.entity.LeaveEntity; +import net.lab1024.sa.admin.module.business.oa.leave.domain.form.LeaveQueryForm; +import net.lab1024.sa.admin.module.business.oa.leave.domain.vo.LeaveVO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +/** + * OA 请假申请表 Dao + * + * @Author lf + * @Date 2025-05-31 20:00:10 + * @Copyright lf + */ + +@Mapper +public interface LeaveDao extends BaseMapper { + + /** + * 分页 查询 + * + * @param page + * @param queryForm + * @return + */ + List queryPage(Page page, @Param("queryForm") LeaveQueryForm queryForm); + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/leave/domain/entity/LeaveEntity.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/leave/domain/entity/LeaveEntity.java new file mode 100644 index 0000000..e572b47 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/leave/domain/entity/LeaveEntity.java @@ -0,0 +1,98 @@ +package net.lab1024.sa.admin.module.business.oa.leave.domain.entity; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.time.LocalDate; +import java.time.LocalDateTime; +import lombok.Data; + +/** + * OA 请假申请表 实体类 + * + * @Author lf + * @Date 2025-05-31 20:00:10 + * @Copyright lf + */ + +@Data +@TableName("t_leave") +public class LeaveEntity { + + /** + * 主键 + */ + @TableId(type = IdType.AUTO) + private Long leaveId; + + /** + * 请假类型 + */ + private String type; + + /** + * 请假原因 + */ + private String reason; + + /** + * 开始时间 + */ + private LocalDate startTime; + + /** + * 结束时间 + */ + private LocalDate endTime; + + /** + * 请假天数 + */ + private Integer day; + + /** + * 流程实例的id + */ + private Long instanceId; + + /** + * 节点编码 + */ + private String nodeCode; + + /** + * 流程节点名称 + */ + private String nodeName; + + /** + * 节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关) + */ + private Integer nodeType; + + /** + * 流程状态(0待提交 1审批中 2 审批通过 3自动通过 4终止 5作废 6撤销 7取回 8已完成 9已退回 10失效) + */ + private String flowStatus; + + private Boolean deletedFlag; + + private Long createUserId; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; + + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/leave/domain/form/LeaveAddForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/leave/domain/form/LeaveAddForm.java new file mode 100644 index 0000000..0848e50 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/leave/domain/form/LeaveAddForm.java @@ -0,0 +1,47 @@ +package net.lab1024.sa.admin.module.business.oa.leave.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.time.LocalDate; +import java.time.LocalDateTime; +import lombok.Data; + +/** + * OA 请假申请表 新建表单 + * + * @Author lf + * @Date 2025-05-31 20:00:10 + * @Copyright lf + */ + +@Data +public class LeaveAddForm { + + @Schema(description = "请假类型", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "请假类型 不能为空") + private String type; + + @Schema(description = "请假原因", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "请假原因 不能为空") + private String reason; + + /** + * 开始时间 + */ + private LocalDate startTime; + + /** + * 结束时间 + */ + private LocalDate endTime; + + /** + * 请假天数 + */ + private Integer day; + + @Schema(description = "创建者") + private Long createUserId; + +} \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/leave/domain/form/LeaveQueryForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/leave/domain/form/LeaveQueryForm.java new file mode 100644 index 0000000..9f21b67 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/leave/domain/form/LeaveQueryForm.java @@ -0,0 +1,37 @@ +package net.lab1024.sa.admin.module.business.oa.leave.domain.form; + +import net.lab1024.sa.base.common.domain.PageParam; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import io.swagger.v3.oas.annotations.media.Schema; +import java.time.LocalDate; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * OA 请假申请表 分页查询表单 + * + * @Author lf + * @Date 2025-05-31 20:00:10 + * @Copyright lf + */ + +@Data +@EqualsAndHashCode(callSuper = false) +public class LeaveQueryForm extends PageParam { + + @Schema(description = "请假类型") + private String type; + + @Schema(description = "开始时间") + private LocalDate startTimeBegin; + + @Schema(description = "开始时间") + private LocalDate startTimeEnd; + + @Schema(description = "请假原因") + private String reason; + + @Schema(description = "流程状态") + private String flowStatus; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/leave/domain/form/LeaveUpdateForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/leave/domain/form/LeaveUpdateForm.java new file mode 100644 index 0000000..6a43e68 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/leave/domain/form/LeaveUpdateForm.java @@ -0,0 +1,48 @@ +package net.lab1024.sa.admin.module.business.oa.leave.domain.form; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.time.LocalDate; +import java.time.LocalDateTime; +import lombok.Data; + +/** + * OA 请假申请表 更新表单 + * + * @Author lf + * @Date 2025-05-31 20:00:10 + * @Copyright lf + */ + +@Data +public class LeaveUpdateForm { + + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "主键 不能为空") + private Long leaveId; + + @Schema(description = "请假类型", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "请假类型 不能为空") + private String type; + + @Schema(description = "请假原因", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "请假原因 不能为空") + private String reason; + + /** + * 开始时间 + */ + private LocalDate startTime; + + /** + * 结束时间 + */ + private LocalDate endTime; + /** + * 请假天数 + */ + private Integer day; + +} \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/leave/domain/vo/LeaveVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/leave/domain/vo/LeaveVO.java new file mode 100644 index 0000000..65b178e --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/leave/domain/vo/LeaveVO.java @@ -0,0 +1,60 @@ +package net.lab1024.sa.admin.module.business.oa.leave.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.time.LocalDate; +import java.time.LocalDateTime; +import lombok.Data; + +/** + * OA 请假申请表 列表VO + * + * @Author lf + * @Date 2025-05-31 20:00:10 + * @Copyright lf + */ + +@Data +public class LeaveVO { + + + @Schema(description = "主键") + private Long leaveId; + + @Schema(description = "请假类型") + private String type; + + @Schema(description = "请假原因") + private String reason; + + /** + * 开始时间 + */ + private LocalDate startTime; + + /** + * 结束时间 + */ + private LocalDate endTime; + + @Schema(description = "请假天数") + private Integer day; + + @Schema(description = "流程节点名称") + private String nodeName; + + @Schema(description = "流程状态(0待提交 1审批中 2 审批通过 3自动通过 4终止 5作废 6撤销 7取回 8已完成 9已退回 10失效)") + private String flowStatus; + + @Schema(description = "创建者") + private String createUserName; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + /** + * 流程实例的id + */ + @Schema(description = "流程实例的id") + private Long instanceId; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/leave/manager/LeaveManager.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/leave/manager/LeaveManager.java new file mode 100644 index 0000000..8e3b45a --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/leave/manager/LeaveManager.java @@ -0,0 +1,20 @@ +package net.lab1024.sa.admin.module.business.oa.leave.manager; + +import net.lab1024.sa.admin.module.business.oa.leave.domain.entity.LeaveEntity; +import net.lab1024.sa.admin.module.business.oa.leave.dao.LeaveDao; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + * OA 请假申请表 Manager + * + * @Author lf + * @Date 2025-05-31 20:00:10 + * @Copyright lf + */ +@Service +public class LeaveManager extends ServiceImpl { + + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/leave/service/LeaveService.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/leave/service/LeaveService.java new file mode 100644 index 0000000..280a8d3 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/leave/service/LeaveService.java @@ -0,0 +1,90 @@ +package net.lab1024.sa.admin.module.business.oa.leave.service; + +import java.util.List; +import net.lab1024.sa.admin.module.business.oa.leave.dao.LeaveDao; +import net.lab1024.sa.admin.module.business.oa.leave.domain.entity.LeaveEntity; +import net.lab1024.sa.admin.module.business.oa.leave.domain.form.LeaveAddForm; +import net.lab1024.sa.admin.module.business.oa.leave.domain.form.LeaveQueryForm; +import net.lab1024.sa.admin.module.business.oa.leave.domain.form.LeaveUpdateForm; +import net.lab1024.sa.admin.module.business.oa.leave.domain.vo.LeaveVO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.domain.PageResult; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; + +import jakarta.annotation.Resource; + +/** + * OA 请假申请表 Service + * + * @Author lf + * @Date 2025-05-31 20:00:10 + * @Copyright lf + */ + +@Service +public class LeaveService { + + @Resource + private LeaveDao leaveDao; + + /** + * 分页查询 + */ + public PageResult queryPage(LeaveQueryForm queryForm) { + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List list = leaveDao.queryPage(page, queryForm); + return SmartPageUtil.convert2PageResult(page, list); + } + + /** + * 添加 + */ + public ResponseDTO add(LeaveAddForm addForm) { + LeaveEntity leaveEntity = SmartBeanUtil.copy(addForm, LeaveEntity.class); + leaveDao.insert(leaveEntity); + return ResponseDTO.ok(); + } + + /** + * 更新 + * + */ + public ResponseDTO update(LeaveUpdateForm updateForm) { + LeaveEntity leaveEntity = SmartBeanUtil.copy(updateForm, LeaveEntity.class); + leaveDao.updateById(leaveEntity); + return ResponseDTO.ok(); + } + + /** + * 批量删除 + */ + public ResponseDTO batchDelete(List idList) { + if (CollectionUtils.isEmpty(idList)){ + return ResponseDTO.ok(); + } + + leaveDao.deleteBatchIds(idList); + return ResponseDTO.ok(); + } + + /** + * 单个删除 + */ + public ResponseDTO delete(Long id) { + if (null == id){ + return ResponseDTO.ok(); + } + + leaveDao.deleteById(id); + return ResponseDTO.ok(); + } + + public LeaveVO getDetail(Long id) { + LeaveEntity leaveEntity = leaveDao.selectById(id); + return SmartBeanUtil.copy(leaveEntity, LeaveVO.class); + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/constant/NoticeVisibleRangeDataTypeEnum.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/constant/NoticeVisibleRangeDataTypeEnum.java new file mode 100644 index 0000000..eb4784f --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/constant/NoticeVisibleRangeDataTypeEnum.java @@ -0,0 +1,36 @@ +package net.lab1024.sa.admin.module.business.oa.notice.constant; + + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 公告、通知 可见范围类型 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-12 21:40:39 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Getter +@AllArgsConstructor +public enum NoticeVisibleRangeDataTypeEnum implements BaseEnum { + + /** + * 员工 + */ + EMPLOYEE(1, "员工"), + + /** + * 部门 + */ + DEPARTMENT(2, "部门"), + + ; + + private final Integer value; + + private final String desc; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/controller/NoticeController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/controller/NoticeController.java new file mode 100644 index 0000000..fb6d1fd --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/controller/NoticeController.java @@ -0,0 +1,141 @@ +package net.lab1024.sa.admin.module.business.oa.notice.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.hutool.extra.servlet.JakartaServletUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import net.lab1024.sa.admin.constant.AdminSwaggerTagConst; +import net.lab1024.sa.admin.module.business.oa.notice.domain.form.*; +import net.lab1024.sa.admin.module.business.oa.notice.domain.vo.*; +import net.lab1024.sa.admin.module.business.oa.notice.service.NoticeEmployeeService; +import net.lab1024.sa.admin.module.business.oa.notice.service.NoticeService; +import net.lab1024.sa.admin.module.business.oa.notice.service.NoticeTypeService; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartRequestUtil; +import net.lab1024.sa.base.module.support.operatelog.annotation.OperateLog; +import net.lab1024.sa.base.module.support.repeatsubmit.annoation.RepeatSubmit; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 公告、通知、新闻等等 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-12 21:40:39 + * @Wechat 卓大1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Tag(name = AdminSwaggerTagConst.Business.OA_NOTICE) +@RestController +@OperateLog +public class NoticeController { + + @Resource + private NoticeService noticeService; + + @Resource + private NoticeTypeService noticeTypeService; + + @Resource + private NoticeEmployeeService noticeEmployeeService; + + // --------------------- 通知公告类型 ------------------------- + + @Operation(summary = "通知公告类型-获取全部 @author 卓大") + @GetMapping("/oa/noticeType/getAll") + public ResponseDTO> getAll() { + return ResponseDTO.ok(noticeTypeService.getAll()); + } + + @Operation(summary = "通知公告类型-添加 @author 卓大") + @GetMapping("/oa/noticeType/add/{name}") + public ResponseDTO add(@PathVariable String name) { + return noticeTypeService.add(name); + } + + @Operation(summary = "通知公告类型-修改 @author 卓大") + @GetMapping("/oa/noticeType/update/{noticeTypeId}/{name}") + public ResponseDTO update(@PathVariable Long noticeTypeId, @PathVariable String name) { + return noticeTypeService.update(noticeTypeId, name); + } + + @Operation(summary = "通知公告类型-删除 @author 卓大") + @GetMapping("/oa/noticeType/delete/{noticeTypeId}") + public ResponseDTO deleteNoticeType(@PathVariable Long noticeTypeId) { + return noticeTypeService.delete(noticeTypeId); + } + + // --------------------- 【管理】通知公告------------------------- + + + @Operation(summary = "【管理】通知公告-分页查询 @author 卓大") + @PostMapping("/oa/notice/query") + @SaCheckPermission("oa:notice:query") + public ResponseDTO> query(@RequestBody @Valid NoticeQueryForm queryForm) { + return ResponseDTO.ok(noticeService.query(queryForm)); + } + + @Operation(summary = "【管理】通知公告-添加 @author 卓大") + @PostMapping("/oa/notice/add") + @RepeatSubmit + @SaCheckPermission("oa:notice:add") + public ResponseDTO add(@RequestBody @Valid NoticeAddForm addForm) { + addForm.setCreateUserId(SmartRequestUtil.getRequestUserId()); + return noticeService.add(addForm); + } + + @Operation(summary = "【管理】通知公告-更新 @author 卓大") + @PostMapping("/oa/notice/update") + @RepeatSubmit + @SaCheckPermission("oa:notice:update") + public ResponseDTO update(@RequestBody @Valid NoticeUpdateForm updateForm) { + + return noticeService.update(updateForm); + } + + @Operation(summary = "【管理】通知公告-更新详情 @author 卓大") + @GetMapping("/oa/notice/getUpdateVO/{noticeId}") + @SaCheckPermission("oa:notice:update") + public ResponseDTO getUpdateFormVO(@PathVariable Long noticeId) { + return ResponseDTO.ok(noticeService.getUpdateFormVO(noticeId)); + } + + @Operation(summary = "【管理】通知公告-删除 @author 卓大") + @GetMapping("/oa/notice/delete/{noticeId}") + @SaCheckPermission("oa:notice:delete") + public ResponseDTO delete(@PathVariable Long noticeId) { + return noticeService.delete(noticeId); + } + + // --------------------- 【员工】查看 通知公告 ------------------------- + + + @Operation(summary = "【员工】通知公告-查看详情 @author 卓大") + @GetMapping("/oa/notice/employee/view/{noticeId}") + public ResponseDTO view(@PathVariable Long noticeId, HttpServletRequest request) { + return noticeEmployeeService.view( + SmartRequestUtil.getRequestUserId(), + noticeId, + JakartaServletUtil.getClientIP(request), + request.getHeader("User-Agent") + ); + } + + @Operation(summary = "【员工】通知公告-查询全部 @author 卓大") + @PostMapping("/oa/notice/employee/query") + public ResponseDTO> queryEmployeeNotice(@RequestBody @Valid NoticeEmployeeQueryForm noticeEmployeeQueryForm) { + return noticeEmployeeService.queryList(SmartRequestUtil.getRequestUserId(), noticeEmployeeQueryForm); + } + + @Operation(summary = "【员工】通知公告-查询 查看记录 @author 卓大") + @PostMapping("/oa/notice/employee/queryViewRecord") + public ResponseDTO> queryViewRecord(@RequestBody @Valid NoticeViewRecordQueryForm noticeViewRecordQueryForm) { + return ResponseDTO.ok(noticeEmployeeService.queryViewRecord(noticeViewRecordQueryForm)); + } +} \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/dao/NoticeDao.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/dao/NoticeDao.java new file mode 100644 index 0000000..c3609d6 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/dao/NoticeDao.java @@ -0,0 +1,126 @@ +package net.lab1024.sa.admin.module.business.oa.notice.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.admin.module.business.oa.notice.domain.entity.NoticeEntity; +import net.lab1024.sa.admin.module.business.oa.notice.domain.form.NoticeEmployeeQueryForm; +import net.lab1024.sa.admin.module.business.oa.notice.domain.form.NoticeQueryForm; +import net.lab1024.sa.admin.module.business.oa.notice.domain.form.NoticeViewRecordQueryForm; +import net.lab1024.sa.admin.module.business.oa.notice.domain.form.NoticeVisibleRangeForm; +import net.lab1024.sa.admin.module.business.oa.notice.domain.vo.NoticeEmployeeVO; +import net.lab1024.sa.admin.module.business.oa.notice.domain.vo.NoticeVO; +import net.lab1024.sa.admin.module.business.oa.notice.domain.vo.NoticeViewRecordVO; +import net.lab1024.sa.admin.module.business.oa.notice.domain.vo.NoticeVisibleRangeVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 公告、通知、新闻等等 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-12 21:40:39 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface NoticeDao extends BaseMapper { + + // ================================= 数据范围相关 【子表】 ================================= + + /** + * 保存可见范围 + * + */ + void insertVisibleRange(@Param("noticeId") Long noticeId, @Param("visibleRangeFormList") List visibleRangeFormList); + + /** + * 删除可见范围 + * + */ + void deleteVisibleRange(@Param("noticeId") Long noticeId); + + /** + * 相关可见范围 + * + */ + List queryVisibleRange(@Param("noticeId") Long noticeId); + + // ================================= 通知公告【主表】 相关 ================================= + + /** + * 后管分页查询资讯 + * + */ + List query(Page page, @Param("query") NoticeQueryForm queryForm); + + + /** + * 更新删除状态 + * + */ + void updateDeletedFlag(@Param("noticeId") Long noticeId); + + // ================================= 通知公告【员工查看】 相关 ================================= + + /** + * 查询 员工 查看到的通知公告 + * + */ + List queryEmployeeNotice(Page page, + @Param("requestEmployeeId") Long requestEmployeeId, + @Param("query") NoticeEmployeeQueryForm noticeEmployeeQueryForm, + @Param("requestEmployeeDepartmentIdList") List requestEmployeeDepartmentIdList, + @Param("deletedFlag") boolean deletedFlag, + @Param("administratorFlag") boolean administratorFlag, + @Param("departmentDataType") Integer departmentDataType, + @Param("employeeDataType") Integer employeeDataType + + ); + + /** + * 查询 员工 未读的通知公告 + * + */ + List queryEmployeeNotViewNotice(Page page, + @Param("requestEmployeeId") Long requestEmployeeId, + @Param("query") NoticeEmployeeQueryForm noticeEmployeeQueryForm, + @Param("requestEmployeeDepartmentIdList") List requestEmployeeDepartmentIdList, + @Param("deletedFlag") boolean deletedFlag, + @Param("administratorFlag") boolean administratorFlag, + @Param("departmentDataType") Integer departmentDataType, + @Param("employeeDataType") Integer employeeDataType + + ); + + long viewRecordCount(@Param("noticeId")Long noticeId, @Param("employeeId")Long employeeId); + + /** + * 查询通知、公告的 查看记录 + */ + List queryNoticeViewRecordList(Page page,@Param("queryForm") NoticeViewRecordQueryForm noticeViewRecordQueryForm); + + /** + * 保存查看记录 + */ + void insertViewRecord(@Param("noticeId") Long noticeId, @Param("employeeId") Long employeeId, @Param("ip") String ip, @Param("userAgent") String userAgent,@Param("pageViewCount") Integer pageViewCount); + + /** + * 更新查看记录 + */ + void updateViewRecord(@Param("noticeId")Long noticeId, @Param("employeeId")Long requestEmployeeId,@Param("ip") String ip, @Param("userAgent")String userAgent); + + /** + * 更新 浏览量 + * + * @param noticeId 通知 id + * @param pageViewCountIncrement 页面浏览量的增量 + * @param userViewCountIncrement 用户浏览量的增量 + */ + void updateViewCount(@Param("noticeId")Long noticeId,@Param("pageViewCountIncrement") Integer pageViewCountIncrement, @Param("userViewCountIncrement")Integer userViewCountIncrement); + + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/dao/NoticeTypeDao.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/dao/NoticeTypeDao.java new file mode 100644 index 0000000..7f7ee5c --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/dao/NoticeTypeDao.java @@ -0,0 +1,20 @@ +package net.lab1024.sa.admin.module.business.oa.notice.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import net.lab1024.sa.admin.module.business.oa.notice.domain.entity.NoticeTypeEntity; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.stereotype.Component; + +/** + * 通知公告类型 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-12 21:40:39 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface NoticeTypeDao extends BaseMapper { + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/entity/NoticeEntity.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/entity/NoticeEntity.java new file mode 100644 index 0000000..3ae0e05 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/entity/NoticeEntity.java @@ -0,0 +1,114 @@ +package net.lab1024.sa.admin.module.business.oa.notice.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 通知公告 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-12 21:40:39 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName("t_notice") +public class NoticeEntity { + + @TableId(type = IdType.AUTO) + private Long noticeId; + + /** + * 类型 + */ + private Long noticeTypeId; + + /** + * 标题 + */ + private String title; + + /** + * 是否全部可见 + */ + private Boolean allVisibleFlag; + + /** + * 是否定时发布 + */ + private Boolean scheduledPublishFlag; + + /** + * 发布时间 + */ + private LocalDateTime publishTime; + + /** + * 内容 纯文本 + */ + private String contentText; + + /** + * 内容 html + */ + private String contentHtml; + + /** + * 附件 + * 多个英文逗号分隔 + */ + private String attachment; + + /** + * 页面浏览量 + */ + private Integer pageViewCount; + + /** + * 用户浏览量 + */ + private Integer userViewCount; + + /** + * 来源 + */ + private String source; + + /** + * 作者 + */ + private String author; + + /** + * 文号 + */ + private String documentNumber; + + /** + * 流程实例的id + */ + private Long instanceId; + + /** + * 流程节点名称 + */ + private String nodeName; + + /** + * 流程状态 + */ + private String flowStatus; + + private Boolean deletedFlag; + + private Long createUserId; + + private LocalDateTime updateTime; + + private LocalDateTime createTime; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/entity/NoticeTypeEntity.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/entity/NoticeTypeEntity.java new file mode 100644 index 0000000..48c1c8f --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/entity/NoticeTypeEntity.java @@ -0,0 +1,41 @@ +package net.lab1024.sa.admin.module.business.oa.notice.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +/** + * 通知公告类型 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-12 21:40:39 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName("t_notice_type") +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class NoticeTypeEntity { + + @TableId(type = IdType.AUTO) + private Long noticeTypeId; + + /** + * 名称 + */ + private String noticeTypeName; + + + private LocalDateTime updateTime; + + private LocalDateTime createTime; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/form/NoticeAddForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/form/NoticeAddForm.java new file mode 100644 index 0000000..1bd1fe3 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/form/NoticeAddForm.java @@ -0,0 +1,78 @@ +package net.lab1024.sa.admin.module.business.oa.notice.domain.form; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import net.lab1024.sa.base.common.json.deserializer.FileKeyVoDeserializer; +import org.hibernate.validator.constraints.Length; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 通知公告 添加表单 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-12 21:40:39 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class NoticeAddForm { + + @Schema(description = "标题") + @NotBlank(message = "标题不能为空") + @Length(max = 200, message = "标题最多200字符") + private String title; + + @Schema(description = "分类") + @NotNull(message = "分类不能为空") + private Long noticeTypeId; + + @Schema(description = "是否全部可见") + @NotNull(message = "是否全部可见不能为空") + private Boolean allVisibleFlag; + + @Schema(description = "是否定时发布") + @NotNull(message = "是否定时发布不能为空") + private Boolean scheduledPublishFlag; + + @Schema(description = "发布时间") + @NotNull(message = "发布时间不能为空") + private LocalDateTime publishTime; + + @Schema(description = "纯文本内容") + @NotNull(message = "文本内容不能为空") + private String contentText; + + @Schema(description = "html内容") + @NotNull(message = "html内容不能为空") + private String contentHtml; + + @Schema(description = "附件,多个英文逗号分隔|可选") + @Length(max = 1000, message = "最多1000字符") + @JsonDeserialize(using = FileKeyVoDeserializer.class) + private String attachment; + + @Schema(description = "作者") + @NotBlank(message = "作者不能为空") + private String author; + + @Schema(description = "来源") + @NotBlank(message = "标题不能为空") + private String source; + + @Schema(description = "文号") + private String documentNumber; + + @Schema(hidden = true) + private Long createUserId; + + @Schema(description = "可见范围设置|可选") + @Valid + private List visibleRangeList; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/form/NoticeEmployeeQueryForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/form/NoticeEmployeeQueryForm.java new file mode 100644 index 0000000..3f3d90b --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/form/NoticeEmployeeQueryForm.java @@ -0,0 +1,35 @@ +package net.lab1024.sa.admin.module.business.oa.notice.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; + +import java.time.LocalDate; + +/** + * 通知公告 员工查询表单 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-12 21:40:39 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class NoticeEmployeeQueryForm extends PageParam { + + @Schema(description = "标题、作者、来源、文号") + private String keywords; + + @Schema(description = "分类") + private Long noticeTypeId; + + @Schema(description = "发布-开始时间") + private LocalDate publishTimeBegin; + + @Schema(description = "未读标识") + private Boolean notViewFlag; + + @Schema(description = "发布-截止时间") + private LocalDate publishTimeEnd; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/form/NoticeQueryForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/form/NoticeQueryForm.java new file mode 100644 index 0000000..44f2efd --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/form/NoticeQueryForm.java @@ -0,0 +1,60 @@ +package net.lab1024.sa.admin.module.business.oa.notice.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; + +import java.time.LocalDate; + +/** + * 通知公告 管理查询表单 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-12 21:40:39 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class NoticeQueryForm extends PageParam { + + @Schema(description = "分类") + private Long noticeTypeId; + + @Schema(description = "标题、作者、来源") + private String keywords; + + @Schema(description = "文号") + private String documentNumber; + + @Schema(description = "创建人") + private String createUserName; + + /** + * 流程节点名称 + */ + @Schema(description = "流程节点") + private String nodeName; + + /** + * 流程状态 + */ + @Schema(description = "流程状态") + private String flowStatus; + + @Schema(description = "删除标识") + private Boolean deletedFlag; + + @Schema(description = "创建-开始时间") + private LocalDate createTimeBegin; + + @Schema(description = "创建-截止时间") + private LocalDate createTimeEnd; + + @Schema(description = "发布-开始时间") + private LocalDate publishTimeBegin; + + @Schema(description = "发布-截止时间") + private LocalDate publishTimeEnd; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/form/NoticeUpdateForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/form/NoticeUpdateForm.java new file mode 100644 index 0000000..401c37a --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/form/NoticeUpdateForm.java @@ -0,0 +1,39 @@ +package net.lab1024.sa.admin.module.business.oa.notice.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 通知公告 更新表单 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-12 21:40:39 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class NoticeUpdateForm extends NoticeAddForm { + + @Schema(description = "id") + @NotNull(message = "通知id不能为空") + private Long noticeId; + /** + * 流程实例的id + */ + @Schema(description = "流程实例的id") + private Long instanceId; + + /** + * 流程节点名称 + */ + @Schema(description = "流程节点") + private String nodeName; + + /** + * 流程状态 + */ + @Schema(description = "流程状态") + private String flowStatus; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/form/NoticeViewRecordQueryForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/form/NoticeViewRecordQueryForm.java new file mode 100644 index 0000000..415828c --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/form/NoticeViewRecordQueryForm.java @@ -0,0 +1,31 @@ +package net.lab1024.sa.admin.module.business.oa.notice.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; + +/** + * 通知公告 阅读记录查询 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-12 21:40:39 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class NoticeViewRecordQueryForm extends PageParam { + + @Schema(description = "通知公告id") + @NotNull(message = "通知公告id不能为空") + private Long noticeId; + + @Schema(description = "部门id") + private Long departmentId; + + @Schema(description = "关键字") + private String keywords; + + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/form/NoticeVisibleRangeForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/form/NoticeVisibleRangeForm.java new file mode 100644 index 0000000..c831db6 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/form/NoticeVisibleRangeForm.java @@ -0,0 +1,33 @@ +package net.lab1024.sa.admin.module.business.oa.notice.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import net.lab1024.sa.admin.module.business.oa.notice.constant.NoticeVisibleRangeDataTypeEnum; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; + +/** + * 通知公告 可见范围数据 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-12 21:40:39 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class NoticeVisibleRangeForm { + + @SchemaEnum(NoticeVisibleRangeDataTypeEnum.class) + @CheckEnum(value = NoticeVisibleRangeDataTypeEnum.class, required = true, message = "数据类型错误") + private Integer dataType; + + @Schema(description = "员工/部门id") + @NotNull(message = "员工/部门id不能为空") + private Long dataId; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/vo/NoticeDetailVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/vo/NoticeDetailVO.java new file mode 100644 index 0000000..096a0d0 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/vo/NoticeDetailVO.java @@ -0,0 +1,84 @@ +package net.lab1024.sa.admin.module.business.oa.notice.domain.vo; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import net.lab1024.sa.base.common.json.serializer.FileKeyVoSerializer; + +import java.time.LocalDateTime; + +/** + * 通知公告 详情 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-12 21:40:39 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class NoticeDetailVO { + + @Schema(description = "id") + private Long noticeId; + + @Schema(description = "标题") + private String title; + + @Schema(description = "分类") + private Long noticeTypeId; + + @Schema(description = "分类名称") + private Long noticeTypeName; + + @Schema(description = "是否全部可见") + @NotNull(message = "是否全部可见不能为空") + private Boolean allVisibleFlag; + + @Schema(description = "是否定时发布") + @NotNull(message = "是否定时发布不能为空") + private Boolean scheduledPublishFlag; + + @Schema(description = "纯文本内容") + private String contentText; + + @Schema(description = "html内容") + private String contentHtml; + + @Schema(description = "附件") + @JsonSerialize(using = FileKeyVoSerializer.class) + private String attachment; + + @Schema(description = "发布时间") + @NotNull(message = "发布时间不能为空") + private LocalDateTime publishTime; + + @Schema(description = "作者") + @NotBlank(message = "作者不能为空") + private String author; + + @Schema(description = "来源") + @NotBlank(message = "标题不能为空") + private String source; + + @Schema(description = "文号") + private String documentNumber; + + @Schema(description = "页面浏览量") + private Integer pageViewCount; + + @Schema(description = "用户浏览量") + private Integer userViewCount; + + @Schema(description = "创建人名称") + private Long createUserName; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "更新时间") + private LocalDateTime updateTime; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/vo/NoticeEmployeeVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/vo/NoticeEmployeeVO.java new file mode 100644 index 0000000..759ba6c --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/vo/NoticeEmployeeVO.java @@ -0,0 +1,26 @@ +package net.lab1024.sa.admin.module.business.oa.notice.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDate; + +/** + * 通知公告 员工查看 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-12 21:40:39 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class NoticeEmployeeVO extends NoticeVO { + + @Schema(description = "是否查看") + private Boolean viewFlag; + + @Schema(description = "发布日期") + private LocalDate publishDate; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/vo/NoticeTypeVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/vo/NoticeTypeVO.java new file mode 100644 index 0000000..2660959 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/vo/NoticeTypeVO.java @@ -0,0 +1,24 @@ +package net.lab1024.sa.admin.module.business.oa.notice.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 通知公告 类型 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-12 21:40:39 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class NoticeTypeVO { + + @Schema(description = "通知类型id") + private Long noticeTypeId; + + @Schema(description = "通知类型-名称") + private String noticeTypeName; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/vo/NoticeUpdateFormVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/vo/NoticeUpdateFormVO.java new file mode 100644 index 0000000..1302559 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/vo/NoticeUpdateFormVO.java @@ -0,0 +1,35 @@ +package net.lab1024.sa.admin.module.business.oa.notice.domain.vo; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.json.serializer.FileKeyVoSerializer; + +import java.util.List; + +/** + * 用于更新 【通知、公告】 的 VO 对象 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-12 21:40:39 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class NoticeUpdateFormVO extends NoticeVO { + + @Schema(description = "纯文本内容") + private String contentText; + + @Schema(description = "html内容") + private String contentHtml; + + @Schema(description = "附件") + @JsonSerialize(using = FileKeyVoSerializer.class) + private String attachment; + + @Schema(description = "可见范围") + private List visibleRangeList; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/vo/NoticeVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/vo/NoticeVO.java new file mode 100644 index 0000000..81a4dfb --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/vo/NoticeVO.java @@ -0,0 +1,90 @@ +package net.lab1024.sa.admin.module.business.oa.notice.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + + +/** + * 新闻、公告 VO + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-12 21:40:39 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class NoticeVO { + + @Schema(description = "id") + private Long noticeId; + + @Schema(description = "标题") + private String title; + + @Schema(description = "分类") + private Long noticeTypeId; + + @Schema(description = "分类名称") + private String noticeTypeName; + + @Schema(description = "是否全部可见") + private Boolean allVisibleFlag; + + @Schema(description = "是否定时发布") + private Boolean scheduledPublishFlag; + + @Schema(description = "发布状态") + private Boolean publishFlag; + + @Schema(description = "发布时间") + private LocalDateTime publishTime; + + @Schema(description = "作者") + private String author; + + @Schema(description = "来源") + private String source; + + @Schema(description = "文号") + private String documentNumber; + + @Schema(description = "页面浏览量") + private Integer pageViewCount; + + @Schema(description = "用户浏览量") + private Integer userViewCount; + + @Schema(description = "删除标识") + private Boolean deletedFlag; + + /** + * 流程实例的id + */ + @Schema(description = "流程实例的id") + private Long instanceId; + + /** + * 流程节点名称 + */ + @Schema(description = "流程节点") + private String nodeName; + + /** + * 流程状态 + */ + @Schema(description = "流程状态") + private String flowStatus; + + @Schema(description = "创建人名称") + private String createUserName; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "更新时间") + private LocalDateTime updateTime; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/vo/NoticeViewRecordVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/vo/NoticeViewRecordVO.java new file mode 100644 index 0000000..23b1adc --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/vo/NoticeViewRecordVO.java @@ -0,0 +1,49 @@ +package net.lab1024.sa.admin.module.business.oa.notice.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 浏览记录 VO + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-12 21:40:39 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class NoticeViewRecordVO { + + @Schema(description = "员工ID") + private Long employeeId; + + @Schema(description = "员工姓名") + private String employeeName; + + @Schema(description = "员工部门名称") + private String departmentName; + + @Schema(description = "查看次数") + private Integer pageViewCount; + + @Schema(description = "首次ip") + private String firstIp; + + @Schema(description = "首次用户设备等标识") + private String firstUserAgent; + + @Schema(description = "首次查看时间") + private LocalDateTime createTime; + + @Schema(description = "最后一次 ip") + private String lastIp; + + @Schema(description = "最后一次 用户设备等标识") + private String lastUserAgent; + + @Schema(description = "最后一次查看时间") + private LocalDateTime updateTime; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/vo/NoticeVisibleRangeVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/vo/NoticeVisibleRangeVO.java new file mode 100644 index 0000000..0206cad --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/domain/vo/NoticeVisibleRangeVO.java @@ -0,0 +1,29 @@ +package net.lab1024.sa.admin.module.business.oa.notice.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.admin.module.business.oa.notice.constant.NoticeVisibleRangeDataTypeEnum; +import net.lab1024.sa.base.common.swagger.SchemaEnum; + +/** + * 新闻、公告 可见范围数据 VO + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-12 21:40:39 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class NoticeVisibleRangeVO { + + @SchemaEnum(NoticeVisibleRangeDataTypeEnum.class) + private Integer dataType; + + @Schema(description = "员工/部门id") + private Long dataId; + + @Schema(description = "员工/部门 名称") + private String dataName; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/manager/NoticeManager.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/manager/NoticeManager.java new file mode 100644 index 0000000..78b02e1 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/manager/NoticeManager.java @@ -0,0 +1,62 @@ +package net.lab1024.sa.admin.module.business.oa.notice.manager; + +import jakarta.annotation.Resource; +import net.lab1024.sa.admin.module.business.oa.notice.dao.NoticeDao; +import net.lab1024.sa.admin.module.business.oa.notice.domain.entity.NoticeEntity; +import net.lab1024.sa.admin.module.business.oa.notice.domain.form.NoticeVisibleRangeForm; +import net.lab1024.sa.base.module.support.datatracer.constant.DataTracerTypeEnum; +import net.lab1024.sa.base.module.support.datatracer.service.DataTracerService; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/** + * 通知、公告 manager + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-12 21:40:39 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class NoticeManager { + + @Resource + private NoticeDao noticeDao; + + @Resource + private DataTracerService dataTracerService; + + /** + * 保存 + */ + @Transactional(rollbackFor = Throwable.class) + public void save(NoticeEntity noticeEntity, List visibleRangeFormList) { + noticeDao.insert(noticeEntity); + Long noticeId = noticeEntity.getNoticeId(); + // 保存可见范围 + if (CollectionUtils.isNotEmpty(visibleRangeFormList)) { + noticeDao.insertVisibleRange(noticeId, visibleRangeFormList); + } + dataTracerService.insert(noticeId, DataTracerTypeEnum.OA_NOTICE); + } + + /** + * 更新 + * + */ + @Transactional(rollbackFor = Throwable.class) + public void update(NoticeEntity old, NoticeEntity noticeEntity, List visibleRangeList) { + noticeDao.updateById(noticeEntity); + Long noticeId = noticeEntity.getNoticeId(); + // 保存可见范围 + if (CollectionUtils.isNotEmpty(visibleRangeList)) { + noticeDao.deleteVisibleRange(noticeId); + noticeDao.insertVisibleRange(noticeId, visibleRangeList); + } + dataTracerService.update(noticeId, DataTracerTypeEnum.OA_NOTICE, old, noticeEntity); + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/service/NoticeEmployeeService.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/service/NoticeEmployeeService.java new file mode 100644 index 0000000..2f2404d --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/service/NoticeEmployeeService.java @@ -0,0 +1,155 @@ +package net.lab1024.sa.admin.module.business.oa.notice.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.google.common.collect.Lists; +import jakarta.annotation.Resource; +import net.lab1024.sa.admin.module.business.oa.notice.constant.NoticeVisibleRangeDataTypeEnum; +import net.lab1024.sa.admin.module.business.oa.notice.dao.NoticeDao; +import net.lab1024.sa.admin.module.business.oa.notice.domain.form.NoticeEmployeeQueryForm; +import net.lab1024.sa.admin.module.business.oa.notice.domain.form.NoticeViewRecordQueryForm; +import net.lab1024.sa.admin.module.business.oa.notice.domain.vo.*; +import net.lab1024.sa.admin.module.system.department.service.DepartmentService; +import net.lab1024.sa.admin.module.system.employee.domain.entity.EmployeeEntity; +import net.lab1024.sa.admin.module.system.employee.service.EmployeeService; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + + +/** + * 员工查看 通知。公告 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-12 21:40:39 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class NoticeEmployeeService { + + @Resource + private NoticeDao noticeDao; + + @Resource + private NoticeService noticeService; + + @Resource + private DepartmentService departmentService; + + @Resource + private EmployeeService employeeService; + + /** + * 查询我的 通知、公告清单 + */ + public ResponseDTO> queryList(Long requestEmployeeId, NoticeEmployeeQueryForm noticeEmployeeQueryForm) { + Page page = SmartPageUtil.convert2PageQuery(noticeEmployeeQueryForm); + + List employeeDepartmentIdList = Lists.newArrayList(); + EmployeeEntity employeeEntity = employeeService.getById(requestEmployeeId); + // 如果不是管理员 则获取请求人的 部门及其子部门 + if (!employeeEntity.getAdministratorFlag() && employeeEntity.getDepartmentId() != null) { + employeeDepartmentIdList = departmentService.selfAndChildrenIdList(employeeEntity.getDepartmentId()); + } + + List noticeList = null; + //只查询未读的 + if (noticeEmployeeQueryForm.getNotViewFlag() != null && noticeEmployeeQueryForm.getNotViewFlag()) { + noticeList = noticeDao.queryEmployeeNotViewNotice(page, + requestEmployeeId, + noticeEmployeeQueryForm, + employeeDepartmentIdList, + false, + employeeEntity.getAdministratorFlag(), + NoticeVisibleRangeDataTypeEnum.DEPARTMENT.getValue(), + NoticeVisibleRangeDataTypeEnum.EMPLOYEE.getValue()); + } else { + // 查询全部 + noticeList = noticeDao.queryEmployeeNotice(page, + requestEmployeeId, + noticeEmployeeQueryForm, + employeeDepartmentIdList, + false, + employeeEntity.getAdministratorFlag(), + NoticeVisibleRangeDataTypeEnum.DEPARTMENT.getValue(), + NoticeVisibleRangeDataTypeEnum.EMPLOYEE.getValue()); + } + // 设置发布日期 + noticeList.forEach(notice -> notice.setPublishDate(notice.getPublishTime().toLocalDate())); + + return ResponseDTO.ok(SmartPageUtil.convert2PageResult(page, noticeList)); + } + + + /** + * 查询我的 待查看的 通知、公告清单 + */ + public ResponseDTO view(Long requestEmployeeId, Long noticeId, String ip, String userAgent) { + NoticeUpdateFormVO updateFormVO = noticeService.getUpdateFormVO(noticeId); + if (updateFormVO == null || Boolean.TRUE.equals(updateFormVO.getDeletedFlag())) { + return ResponseDTO.userErrorParam("通知公告不存在"); + } + + EmployeeEntity employeeEntity = employeeService.getById(requestEmployeeId); + if (!updateFormVO.getAllVisibleFlag() && !checkVisibleRange(updateFormVO.getVisibleRangeList(), requestEmployeeId, employeeEntity.getDepartmentId())) { + return ResponseDTO.userErrorParam("对不起,您没有权限查看内容"); + } + + NoticeDetailVO noticeDetailVO = SmartBeanUtil.copy(updateFormVO, NoticeDetailVO.class); + long viewCount = noticeDao.viewRecordCount(noticeId, requestEmployeeId); + if (viewCount == 0) { + noticeDao.insertViewRecord(noticeId, requestEmployeeId, ip, userAgent, 1); + // 该员工对于这个通知是第一次查看 页面浏览量+1 用户浏览量+1 + noticeDao.updateViewCount(noticeId, 1, 1); + noticeDetailVO.setPageViewCount(noticeDetailVO.getPageViewCount() + 1); + noticeDetailVO.setUserViewCount(noticeDetailVO.getUserViewCount() + 1); + } else { + noticeDao.updateViewRecord(noticeId, requestEmployeeId, ip, userAgent); + // 该员工对于这个通知不是第一次查看 页面浏览量+1 用户浏览量+0 + noticeDao.updateViewCount(noticeId, 1, 0); + noticeDetailVO.setPageViewCount(noticeDetailVO.getPageViewCount() + 1); + } + + return ResponseDTO.ok(noticeDetailVO); + } + + /** + * 校验是否有查看权限的范围 + * + */ + public boolean checkVisibleRange(List visibleRangeList, Long employeeId, Long departmentId) { + // 员工范围 + boolean anyMatch = visibleRangeList.stream().anyMatch(e -> NoticeVisibleRangeDataTypeEnum.EMPLOYEE.equalsValue(e.getDataType()) && Objects.equals(e.getDataId(), employeeId)); + if (anyMatch) { + return true; + } + + //部门范围 + List visibleDepartmentIdList = visibleRangeList.stream().filter(e -> NoticeVisibleRangeDataTypeEnum.DEPARTMENT.equalsValue(e.getDataType())) + .map(NoticeVisibleRangeVO::getDataId).collect(Collectors.toList()); + + for (Long visibleDepartmentId : visibleDepartmentIdList) { + List departmentIdList = departmentService.selfAndChildrenIdList(visibleDepartmentId); + if (departmentIdList.contains(departmentId)) { + return true; + } + } + return false; + } + + /** + * 分页查询 查看记录 + */ + public PageResult queryViewRecord(NoticeViewRecordQueryForm noticeViewRecordQueryForm) { + Page page = SmartPageUtil.convert2PageQuery(noticeViewRecordQueryForm); + List noticeViewRecordList = noticeDao.queryNoticeViewRecordList(page, noticeViewRecordQueryForm); + return SmartPageUtil.convert2PageResult(page, noticeViewRecordList); + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/service/NoticeService.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/service/NoticeService.java new file mode 100644 index 0000000..e3e4afb --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/service/NoticeService.java @@ -0,0 +1,240 @@ +package net.lab1024.sa.admin.module.business.oa.notice.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.google.common.collect.Maps; +import jakarta.annotation.Resource; +import net.lab1024.sa.admin.module.business.oa.notice.constant.NoticeVisibleRangeDataTypeEnum; +import net.lab1024.sa.admin.module.business.oa.notice.dao.NoticeDao; +import net.lab1024.sa.admin.module.business.oa.notice.domain.entity.NoticeEntity; +import net.lab1024.sa.admin.module.business.oa.notice.domain.form.NoticeAddForm; +import net.lab1024.sa.admin.module.business.oa.notice.domain.form.NoticeQueryForm; +import net.lab1024.sa.admin.module.business.oa.notice.domain.form.NoticeUpdateForm; +import net.lab1024.sa.admin.module.business.oa.notice.domain.form.NoticeVisibleRangeForm; +import net.lab1024.sa.admin.module.business.oa.notice.domain.vo.NoticeTypeVO; +import net.lab1024.sa.admin.module.business.oa.notice.domain.vo.NoticeUpdateFormVO; +import net.lab1024.sa.admin.module.business.oa.notice.domain.vo.NoticeVO; +import net.lab1024.sa.admin.module.business.oa.notice.domain.vo.NoticeVisibleRangeVO; +import net.lab1024.sa.admin.module.business.oa.notice.manager.NoticeManager; +import net.lab1024.sa.admin.module.system.department.dao.DepartmentDao; +import net.lab1024.sa.admin.module.system.department.domain.entity.DepartmentEntity; +import net.lab1024.sa.admin.module.system.department.domain.vo.DepartmentVO; +import net.lab1024.sa.admin.module.system.department.service.DepartmentService; +import net.lab1024.sa.admin.module.system.employee.dao.EmployeeDao; +import net.lab1024.sa.admin.module.system.employee.domain.entity.EmployeeEntity; +import net.lab1024.sa.base.common.constant.StringConst; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.module.support.datatracer.constant.DataTracerTypeEnum; +import net.lab1024.sa.base.module.support.datatracer.service.DataTracerService; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 通知。公告 后台管理业务 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-12 21:40:39 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class NoticeService { + + @Resource + private NoticeDao noticeDao; + + @Resource + private NoticeManager noticeManager; + + @Resource + private EmployeeDao employeeDao; + + @Resource + private DepartmentDao departmentDao; + + @Resource + private DepartmentService departmentService; + + @Resource + private NoticeTypeService noticeTypeService; + + @Resource + private DataTracerService dataTracerService; + + /** + * 查询 通知、公告 + * + */ + public PageResult query(NoticeQueryForm queryForm) { + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List list = noticeDao.query(page, queryForm); + LocalDateTime now = LocalDateTime.now(); + list.forEach(e -> e.setPublishFlag(e.getPublishTime().isBefore(now))); + return SmartPageUtil.convert2PageResult(page, list); + } + + /** + * 添加 + */ + public ResponseDTO add(NoticeAddForm addForm) { + // 校验并获取可见范围 + ResponseDTO validate = this.checkAndBuildVisibleRange(addForm); + if (!validate.getOk()) { + return ResponseDTO.error(validate); + } + + // build 资讯 + NoticeEntity noticeEntity = SmartBeanUtil.copy(addForm, NoticeEntity.class); + // 发布时间:不是定时发布时 默认为 当前 + if (!addForm.getScheduledPublishFlag()) { + noticeEntity.setPublishTime(LocalDateTime.now()); + } + // 保存数据 + noticeManager.save(noticeEntity, addForm.getVisibleRangeList()); + return ResponseDTO.ok(); + } + + /** + * 校验并返回可见范围 + * + */ + private ResponseDTO checkAndBuildVisibleRange(NoticeAddForm form) { + // 校验资讯分类 + NoticeTypeVO noticeType = noticeTypeService.getByNoticeTypeId(form.getNoticeTypeId()); + if (noticeType == null) { + return ResponseDTO.userErrorParam("分类不存在"); + } + + if (form.getAllVisibleFlag()) { + return ResponseDTO.ok(); + } + + /* + * 校验可见范围 + * 非全部可见时 校验选择的员工|部门 + */ + List visibleRangeUpdateList = form.getVisibleRangeList(); + if (CollectionUtils.isEmpty(visibleRangeUpdateList)) { + return ResponseDTO.userErrorParam("未设置可见范围"); + } + + // 校验可见范围-> 员工 + List employeeIdList = visibleRangeUpdateList.stream() + .filter(e -> NoticeVisibleRangeDataTypeEnum.EMPLOYEE.equalsValue(e.getDataType())) + .map(NoticeVisibleRangeForm::getDataId) + .distinct().collect(Collectors.toList()); + if (CollectionUtils.isNotEmpty(employeeIdList)) { + employeeIdList = employeeIdList.stream().distinct().collect(Collectors.toList()); + List dbEmployeeIdList = employeeDao.selectBatchIds(employeeIdList).stream().map(EmployeeEntity::getEmployeeId).collect(Collectors.toList()); + Collection subtract = CollectionUtils.subtract(employeeIdList, dbEmployeeIdList); + if (!subtract.isEmpty()) { + return ResponseDTO.userErrorParam("员工id不存在:" + subtract); + } + } + + // 校验可见范围-> 部门 + List deptIdList = visibleRangeUpdateList.stream() + .filter(e -> NoticeVisibleRangeDataTypeEnum.DEPARTMENT.equalsValue(e.getDataType())) + .map(NoticeVisibleRangeForm::getDataId) + .distinct().collect(Collectors.toList()); + if (CollectionUtils.isNotEmpty(deptIdList)) { + deptIdList = deptIdList.stream().distinct().collect(Collectors.toList()); + List dbDeptIdList = departmentDao.selectBatchIds(deptIdList).stream().map(DepartmentEntity::getDepartmentId).collect(Collectors.toList()); + Collection subtract = CollectionUtils.subtract(deptIdList, dbDeptIdList); + if (!subtract.isEmpty()) { + return ResponseDTO.userErrorParam("部门id不存在:" + subtract); + } + } + return ResponseDTO.ok(); + } + + + /** + * 更新 + * + */ + public ResponseDTO update(NoticeUpdateForm updateForm) { + + NoticeEntity oldNoticeEntity = noticeDao.selectById(updateForm.getNoticeId()); + if (oldNoticeEntity == null) { + return ResponseDTO.userErrorParam("通知不存在"); + } + + // 校验并获取可见范围 + ResponseDTO res = this.checkAndBuildVisibleRange(updateForm); + if (!res.getOk()) { + return ResponseDTO.error(res); + } + + // 更新 + NoticeEntity noticeEntity = SmartBeanUtil.copy(updateForm, NoticeEntity.class); + noticeManager.update(oldNoticeEntity, noticeEntity, updateForm.getVisibleRangeList()); + return ResponseDTO.ok(); + } + + + /** + * 删除 + * + */ + public ResponseDTO delete(Long noticeId) { + NoticeEntity noticeEntity = noticeDao.selectById(noticeId); + if (null == noticeEntity || noticeEntity.getDeletedFlag()) { + return ResponseDTO.userErrorParam("通知公告不存在"); + } + // 更新删除状态 + noticeDao.updateDeletedFlag(noticeId); + dataTracerService.delete(noticeId, DataTracerTypeEnum.OA_NOTICE); + return ResponseDTO.ok(); + } + + /** + * 获取更新表单用的详情 + */ + public NoticeUpdateFormVO getUpdateFormVO(Long noticeId) { + NoticeEntity noticeEntity = noticeDao.selectById(noticeId); + if (null == noticeEntity) { + return null; + } + + NoticeUpdateFormVO updateFormVO = SmartBeanUtil.copy(noticeEntity, NoticeUpdateFormVO.class); + NoticeTypeVO noticeType = noticeTypeService.getByNoticeTypeId(noticeEntity.getNoticeTypeId()); + updateFormVO.setNoticeTypeName(noticeType.getNoticeTypeName()); + updateFormVO.setPublishFlag(updateFormVO.getPublishTime() != null && updateFormVO.getPublishTime().isBefore(LocalDateTime.now())); + + if (!updateFormVO.getAllVisibleFlag()) { + List noticeVisibleRangeList = noticeDao.queryVisibleRange(noticeId); + List employeeIdList = noticeVisibleRangeList.stream().filter(e -> NoticeVisibleRangeDataTypeEnum.EMPLOYEE.getValue().equals(e.getDataType())) + .map(NoticeVisibleRangeVO::getDataId) + .collect(Collectors.toList()); + + Map employeeMap = null; + if (CollectionUtils.isNotEmpty(employeeIdList)) { + employeeMap = employeeDao.selectBatchIds(employeeIdList).stream().collect(Collectors.toMap(EmployeeEntity::getEmployeeId, Function.identity())); + } else { + employeeMap = Maps.newHashMap(); + } + for (NoticeVisibleRangeVO noticeVisibleRange : noticeVisibleRangeList) { + if (noticeVisibleRange.getDataType().equals(NoticeVisibleRangeDataTypeEnum.EMPLOYEE.getValue())) { + EmployeeEntity employeeEntity = employeeMap.get(noticeVisibleRange.getDataId()); + noticeVisibleRange.setDataName(employeeEntity == null ? StringConst.EMPTY : employeeEntity.getActualName()); + } else { + DepartmentVO departmentVO = departmentService.getDepartmentById(noticeVisibleRange.getDataId()); + noticeVisibleRange.setDataName(departmentVO == null ? StringConst.EMPTY : departmentVO.getName()); + } + } + updateFormVO.setVisibleRangeList(noticeVisibleRangeList); + } + return updateFormVO; + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/service/NoticeTypeService.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/service/NoticeTypeService.java new file mode 100644 index 0000000..6e8a311 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/oa/notice/service/NoticeTypeService.java @@ -0,0 +1,87 @@ +package net.lab1024.sa.admin.module.business.oa.notice.service; + +import cn.hutool.core.util.StrUtil; +import jakarta.annotation.Resource; +import net.lab1024.sa.admin.module.business.oa.notice.dao.NoticeTypeDao; +import net.lab1024.sa.admin.module.business.oa.notice.domain.entity.NoticeTypeEntity; +import net.lab1024.sa.admin.module.business.oa.notice.domain.vo.NoticeTypeVO; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * 通知。公告 类型 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-12 21:40:39 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class NoticeTypeService { + + @Resource + private NoticeTypeDao noticeTypeDao; + + /** + * 查询全部 + * @return + */ + public List getAll() { + return SmartBeanUtil.copyList(noticeTypeDao.selectList(null), NoticeTypeVO.class); + } + + public NoticeTypeVO getByNoticeTypeId(Long noticceTypeId) { + return SmartBeanUtil.copy(noticeTypeDao.selectById(noticceTypeId), NoticeTypeVO.class); + } + + public synchronized ResponseDTO add(String name) { + if (StrUtil.isBlank(name)) { + return ResponseDTO.userErrorParam("类型名称不能为空"); + } + + List noticeTypeEntityList = noticeTypeDao.selectList(null); + if (!CollectionUtils.isEmpty(noticeTypeEntityList)) { + boolean exist = noticeTypeEntityList.stream().map(NoticeTypeEntity::getNoticeTypeName).collect(Collectors.toSet()).contains(name); + if (exist) { + return ResponseDTO.userErrorParam("类型名称已经存在"); + } + } + noticeTypeDao.insert(NoticeTypeEntity.builder().noticeTypeName(name).build()); + return ResponseDTO.ok(); + } + + public synchronized ResponseDTO update(Long noticeTypeId, String name) { + if (StrUtil.isBlank(name)) { + return ResponseDTO.userErrorParam("类型名称不能为空"); + } + + NoticeTypeEntity noticeTypeEntity = noticeTypeDao.selectById(noticeTypeId); + if (noticeTypeEntity == null) { + return ResponseDTO.userErrorParam("类型名称不存在"); + } + + List noticeTypeEntityList = noticeTypeDao.selectList(null); + if (!CollectionUtils.isEmpty(noticeTypeEntityList)) { + Optional optionalNoticeTypeEntity = noticeTypeEntityList.stream().filter(e -> e.getNoticeTypeName().equals(name)).findFirst(); + if (optionalNoticeTypeEntity.isPresent() && !optionalNoticeTypeEntity.get().getNoticeTypeId().equals(noticeTypeId)) { + return ResponseDTO.userErrorParam("类型名称已经存在"); + } + } + noticeTypeEntity.setNoticeTypeName(name); + noticeTypeDao.updateById(noticeTypeEntity); + return ResponseDTO.ok(); + } + + public synchronized ResponseDTO delete(Long noticeTypeId) { + noticeTypeDao.deleteById(noticeTypeId); + return ResponseDTO.ok(); + } + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/adapter/AbstractWarmFlowAdapter.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/adapter/AbstractWarmFlowAdapter.java new file mode 100644 index 0000000..402e169 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/adapter/AbstractWarmFlowAdapter.java @@ -0,0 +1,44 @@ +package net.lab1024.sa.admin.module.flow.adapter; + +import jakarta.annotation.Resource; +import net.lab1024.sa.admin.module.system.employee.domain.entity.EmployeeEntity; +import net.lab1024.sa.admin.module.system.role.domain.vo.RoleSelectedVO; +import net.lab1024.sa.admin.module.system.role.service.RoleEmployeeService; +import org.dromara.warm.flow.core.enums.CooperateType; +import org.dromara.warm.flow.core.service.TaskService; +import org.dromara.warm.flow.core.utils.StreamUtils; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public abstract class AbstractWarmFlowAdapter { + @Resource + protected TaskService taskService; + + @Resource + private RoleEmployeeService roleEmployeeService; + + /** + * 获取权限 + * + * @return 权限列表 + */ + protected List permissionList(EmployeeEntity employee, String userId) { + List roles = roleEmployeeService.getRoleInfoListByEmployeeId(employee.getEmployeeId()); + List permissionList = StreamUtils.toList(roles, role -> "role:" + role.getRoleId()); + permissionList.add(userId); + permissionList.add("dept:" + employee.getEmployeeId()); + return permissionList; + } + + /** + * 根据类型获取描述 + * + * @param type 流程类型 + * @return value + */ + protected String type(Integer type) { + return CooperateType.getValueByKey(type); + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/adapter/AddSignatureAdapter.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/adapter/AddSignatureAdapter.java new file mode 100644 index 0000000..7199ba6 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/adapter/AddSignatureAdapter.java @@ -0,0 +1,36 @@ +package net.lab1024.sa.admin.module.flow.adapter; + +import net.lab1024.sa.admin.module.flow.domain.vo.WarmFlowInteractiveTypeVO; +import net.lab1024.sa.admin.module.system.employee.domain.entity.EmployeeEntity; +import net.lab1024.sa.base.common.util.SmartRequestUtil; +import org.dromara.warm.flow.core.dto.FlowParams; +import org.dromara.warm.flow.core.enums.CooperateType; +import org.springframework.stereotype.Component; + +import java.util.Objects; + +/** + * 加签适配器 + */ +@Component +public class AddSignatureAdapter extends AbstractWarmFlowAdapter implements WarmFlowAdapter { + @Override + public boolean isAdapter(Integer warmFlowType) { + return Objects.equals(CooperateType.ADD_SIGNATURE.getKey(), warmFlowType); + } + + @Override + public boolean adapter(WarmFlowInteractiveTypeVO obj) { + Long taskId = obj.getTaskId(); + String userId = String.valueOf(SmartRequestUtil.getRequestUserId()); + EmployeeEntity employee = new EmployeeEntity(); + employee.setEmployeeId(Long.parseLong(userId)); + FlowParams flowParams = new FlowParams() + .handler(userId) + .permissionFlag(permissionList(employee, userId)) + .addHandlers(obj.getAddHandlers()) + .message(this.type(obj.getOperatorType())); + + return super.taskService.addSignature(taskId, flowParams); + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/adapter/DeputeAdapter.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/adapter/DeputeAdapter.java new file mode 100644 index 0000000..ab77b01 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/adapter/DeputeAdapter.java @@ -0,0 +1,36 @@ +package net.lab1024.sa.admin.module.flow.adapter; + +import net.lab1024.sa.admin.module.flow.domain.vo.WarmFlowInteractiveTypeVO; +import net.lab1024.sa.admin.module.system.employee.domain.entity.EmployeeEntity; +import net.lab1024.sa.base.common.util.SmartRequestUtil; +import org.dromara.warm.flow.core.dto.FlowParams; +import org.dromara.warm.flow.core.enums.CooperateType; +import org.springframework.stereotype.Component; + +import java.util.Objects; + +/** + * 委派适配器 + */ +@Component +public class DeputeAdapter extends AbstractWarmFlowAdapter implements WarmFlowAdapter { + @Override + public boolean isAdapter(Integer warmFlowType) { + return Objects.equals(CooperateType.DEPUTE.getKey(), warmFlowType); + } + + @Override + public boolean adapter(WarmFlowInteractiveTypeVO obj) { + Long taskId = obj.getTaskId(); + String userId = String.valueOf(SmartRequestUtil.getRequestUserId()); + EmployeeEntity employee = new EmployeeEntity(); + employee.setEmployeeId(Long.parseLong(userId)); + FlowParams flowParams = new FlowParams() + .handler(userId) + .permissionFlag(permissionList(employee, userId)) + .addHandlers(obj.getAddHandlers()) + .message(this.type(obj.getOperatorType())); + + return super.taskService.depute(taskId, flowParams); + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/adapter/ReductionSignatureAdapter.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/adapter/ReductionSignatureAdapter.java new file mode 100644 index 0000000..0494a2f --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/adapter/ReductionSignatureAdapter.java @@ -0,0 +1,36 @@ +package net.lab1024.sa.admin.module.flow.adapter; + +import net.lab1024.sa.admin.module.flow.domain.vo.WarmFlowInteractiveTypeVO; +import net.lab1024.sa.admin.module.system.employee.domain.entity.EmployeeEntity; +import net.lab1024.sa.base.common.util.SmartRequestUtil; +import org.dromara.warm.flow.core.dto.FlowParams; +import org.dromara.warm.flow.core.enums.CooperateType; +import org.springframework.stereotype.Component; + +import java.util.Objects; + +/** + * 减签适配器 + */ +@Component +public class ReductionSignatureAdapter extends AbstractWarmFlowAdapter implements WarmFlowAdapter { + @Override + public boolean isAdapter(Integer warmFlowType) { + return Objects.equals(CooperateType.REDUCTION_SIGNATURE.getKey(), warmFlowType); + } + + @Override + public boolean adapter(WarmFlowInteractiveTypeVO obj) { + Long taskId = obj.getTaskId(); + String userId = String.valueOf(SmartRequestUtil.getRequestUserId()); + EmployeeEntity employee = new EmployeeEntity(); + employee.setEmployeeId(Long.parseLong(userId)); + FlowParams flowParams = new FlowParams() + .handler(userId) + .permissionFlag(permissionList(employee, userId)) + .reductionHandlers(obj.getAddHandlers()) + .message(type(obj.getOperatorType())); + + return super.taskService.reductionSignature(taskId, flowParams); + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/adapter/TransferAdapter.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/adapter/TransferAdapter.java new file mode 100644 index 0000000..8c2de24 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/adapter/TransferAdapter.java @@ -0,0 +1,36 @@ +package net.lab1024.sa.admin.module.flow.adapter; + +import net.lab1024.sa.admin.module.flow.domain.vo.WarmFlowInteractiveTypeVO; +import net.lab1024.sa.admin.module.system.employee.domain.entity.EmployeeEntity; +import net.lab1024.sa.base.common.util.SmartRequestUtil; +import org.dromara.warm.flow.core.dto.FlowParams; +import org.dromara.warm.flow.core.enums.CooperateType; +import org.springframework.stereotype.Component; + +import java.util.Objects; + +/** + * 转办适配器 + */ +@Component +public class TransferAdapter extends AbstractWarmFlowAdapter implements WarmFlowAdapter { + @Override + public boolean isAdapter(Integer warmFlowType) { + return Objects.equals(CooperateType.TRANSFER.getKey(), warmFlowType); + } + + @Override + public boolean adapter(WarmFlowInteractiveTypeVO obj) { + Long taskId = obj.getTaskId(); + String userId = String.valueOf(SmartRequestUtil.getRequestUserId()); + EmployeeEntity employee = new EmployeeEntity(); + employee.setEmployeeId(Long.parseLong(userId)); + FlowParams flowParams = new FlowParams() + .handler(userId) + .permissionFlag(permissionList(employee, userId)) + .addHandlers(obj.getAddHandlers()) + .message(this.type(obj.getOperatorType())); + + return super.taskService.transfer(taskId, flowParams); + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/adapter/WarmFlowAdapter.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/adapter/WarmFlowAdapter.java new file mode 100644 index 0000000..96502cc --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/adapter/WarmFlowAdapter.java @@ -0,0 +1,9 @@ +package net.lab1024.sa.admin.module.flow.adapter; + +import net.lab1024.sa.admin.module.flow.domain.vo.WarmFlowInteractiveTypeVO; + +public interface WarmFlowAdapter { + boolean isAdapter(Integer warmFlowType); + + boolean adapter(WarmFlowInteractiveTypeVO obj); +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/annoation/ConditionalOnEnable.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/annoation/ConditionalOnEnable.java new file mode 100644 index 0000000..015b8be --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/annoation/ConditionalOnEnable.java @@ -0,0 +1,14 @@ +package net.lab1024.sa.admin.module.flow.annoation; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD }) +@ConditionalOnProperty(value = "warm-flow.enabled", havingValue = "true") +public @interface ConditionalOnEnable { +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/controller/DefController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/controller/DefController.java new file mode 100644 index 0000000..fe14dac --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/controller/DefController.java @@ -0,0 +1,226 @@ +package net.lab1024.sa.admin.module.flow.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import net.lab1024.sa.admin.constant.AdminSwaggerTagConst; +import net.lab1024.sa.admin.module.flow.domain.form.FlowDefinitionQueryForm; +import net.lab1024.sa.admin.module.flow.domain.vo.FlowDefinitionVO; +import net.lab1024.sa.admin.module.flow.service.FlowDefinitionService; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartResponseUtil; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import org.dromara.warm.flow.core.FlowEngine; +import org.dromara.warm.flow.core.dto.DefJson; +import org.dromara.warm.flow.core.dto.FlowCombine; +import org.dromara.warm.flow.core.entity.Definition; +import org.dromara.warm.flow.core.entity.Node; +import org.dromara.warm.flow.core.entity.Skip; +import org.dromara.warm.flow.core.enums.NodeType; +import org.dromara.warm.flow.core.enums.SkipType; +import org.dromara.warm.flow.core.service.ChartService; +import org.dromara.warm.flow.core.service.DefService; +import org.dromara.warm.flow.core.utils.FlowConfigUtil; +import org.dromara.warm.flow.orm.entity.FlowDefinition; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.MediaTypeFactory; +import org.springframework.http.ResponseEntity; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +/** + * 流程定义Controller + * + * @author hh + * @date 2023-04-11 + */ +@Validated +@RestController +@Tag(name = AdminSwaggerTagConst.Flow.FLOW_DEFINITION) +@RequestMapping("/flow/definition") +public class DefController { + @Resource + private FlowDefinitionService flowDefinitionService; + + @Resource + private DefService defService; + + @Resource + private ChartService chartService; + + /** + * 分页查询流程定义列表 + */ + @Operation(summary = "分页查询 @author kaiyun") + @PostMapping("/queryPage") + public ResponseDTO> queryPage(@RequestBody @Valid FlowDefinitionQueryForm queryForm) { + return ResponseDTO.ok(flowDefinitionService.queryPage(queryForm)); + } + + /** + * 获取流程定义详细信息 + */ + @Operation(summary = "获取流程定义详细信息 @author kaiyun") + @SaCheckPermission("flow:definition:query") + @GetMapping(value = "/{id}") + public ResponseDTO getInfo(@PathVariable("id") Long id) { + return ResponseDTO.ok(defService.getById(id)); + } + + /** + * 新增流程定义 + */ + @Operation(summary = "添加 @author kaiyun") + @SaCheckPermission("flow:definition:add") + @PostMapping("/add") + @Transactional(rollbackFor = Exception.class) + public ResponseDTO add(@RequestBody DefJson definition) { + try { + defService.saveDef(definition, false); + } catch (Exception e) { + throw new RuntimeException(e); + } + return ResponseDTO.ok(true); + } + + /** + * 发布流程定义 + */ + @Operation(summary = "发布流程定义 @author kaiyun") + @SaCheckPermission("flow:definition:publish") + @GetMapping("/publish/{id}") + @Transactional(rollbackFor = Exception.class) + public ResponseDTO publish(@PathVariable("id") Long id) { + return ResponseDTO.ok(defService.publish(id)); + } + + /** + * 取消发布流程定义 + */ + @Operation(summary = "取消发布流程定义 @author kaiyun") + @SaCheckPermission("flow:definition:publish") + @GetMapping("/unPublish/{id}") + @Transactional(rollbackFor = Exception.class) + public ResponseDTO unPublish(@PathVariable("id") Long id) { + defService.unPublish(id); + return ResponseDTO.ok(); + } + + /** + * 修改流程定义 + */ + @Operation(summary = "更新 @author kaiyun") + @SaCheckPermission("flow:definition:edit") + @PostMapping("/update") + @Transactional(rollbackFor = Exception.class) + public ResponseDTO edit(@RequestBody FlowDefinition flowDefinition) { + return ResponseDTO.ok(defService.updateById(flowDefinition)); + } + + /** + * 删除流程定义 + */ + @Operation(summary = "删除 @author kaiyun") + @SaCheckPermission("flow:definition:remove") + @GetMapping("/delete/{ids}") + @Transactional(rollbackFor = Exception.class) + public ResponseDTO remove(@PathVariable List ids) { + return ResponseDTO.ok(defService.removeDef(ids)); + } + + /** + * 复制流程定义 + */ + @Operation(summary = "复制流程定义 @author kaiyun") + @SaCheckPermission("flow:definition:publish") + @GetMapping("/copyDef/{id}") + @Transactional(rollbackFor = Exception.class) + public ResponseDTO copyDef(@PathVariable("id") Long id) { + return ResponseDTO.ok(defService.copyDef(id)); + } + + @Operation(summary = "流程定义导入 @author kaiyun") + @SaCheckPermission("flow:definition:importDefinition") + @PostMapping("/importDefinition") + @Transactional(rollbackFor = Exception.class) + public ResponseDTO importDefinition(MultipartFile file) throws Exception { + defService.importIs(file.getInputStream()); + return ResponseDTO.ok(); + } + + @Operation(summary = "流程定义导出 @author kaiyun") + @SaCheckPermission("flow:definition:exportDefinition") + @PostMapping("/exportDefinition/{id}") + public void exportDefinition(@PathVariable("id") Long id, HttpServletResponse response) throws IOException { + // 要导出的字符串 + String content = defService.exportJson(id); + + if (SmartStringUtil.isNotEmpty(content)) { + SmartResponseUtil.setDownloadFileHeader(response, "flowDefinition" + id + ".json", (long) content.getBytes().length); + response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM + ";charset=utf-8"); + response.getOutputStream().write(content.getBytes()); + } + } + + /** + * 查询流程图 + * + * @param definitionId + * + * @return + */ + @Operation(summary = "查询流程图 @author kaiyun") + @GetMapping("/chartDef/{definitionId}") + public ResponseDTO chartDef(@PathVariable("definitionId") Long definitionId) { + return ResponseDTO.ok(""); + } + + /** + * 查询流程图 + * + * @param instanceId + * @return + */ + @Operation(summary = "查询流程图 @author kaiyun") + @GetMapping("/flowChart/{instanceId}") + public ResponseDTO flowChart(@PathVariable("instanceId") Long instanceId) throws IOException { + return ResponseDTO.ok(""); + } + + /** + * 激活流程 + * + * @param definitionId + * @return + */ + @Operation(summary = "激活流程 @author kaiyun") + @GetMapping("/active/{definitionId}") + public ResponseDTO active(@PathVariable("definitionId") Long definitionId) { + return ResponseDTO.ok(defService.active(definitionId)); + } + + /** + * 挂起流程 + * + * @param definitionId + * @return + */ + @Operation(summary = "挂起流程 @author kaiyun") + @GetMapping("/unActive/{definitionId}") + public ResponseDTO unActive(@PathVariable("definitionId") Long definitionId) { + return ResponseDTO.ok(defService.unActive(definitionId)); + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/controller/ExecuteController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/controller/ExecuteController.java new file mode 100644 index 0000000..64eb1b2 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/controller/ExecuteController.java @@ -0,0 +1,446 @@ +package net.lab1024.sa.admin.module.flow.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import net.lab1024.sa.admin.constant.AdminSwaggerTagConst; +import net.lab1024.sa.admin.module.flow.domain.form.ApproveTaskInsForm; +import net.lab1024.sa.admin.module.flow.domain.form.CompleteTaskForm; +import net.lab1024.sa.admin.module.flow.domain.form.StartProcessForm; +import net.lab1024.sa.admin.module.flow.domain.vo.FlowTaskVO; +import net.lab1024.sa.admin.module.flow.domain.vo.StartProcessReturnVO; +import net.lab1024.sa.admin.module.flow.domain.vo.WarmFlowInteractiveTypeVO; +import net.lab1024.sa.admin.module.flow.service.FlowTaskService; +import net.lab1024.sa.admin.module.flow.service.HhDefService; +import net.lab1024.sa.admin.module.system.employee.domain.entity.EmployeeEntity; +import net.lab1024.sa.admin.module.system.employee.domain.vo.EmployeeVO; +import net.lab1024.sa.admin.module.system.employee.service.EmployeeService; +import net.lab1024.sa.admin.module.system.login.service.LoginService; +import net.lab1024.sa.admin.module.system.role.domain.vo.RoleSelectedVO; +import net.lab1024.sa.admin.module.system.role.service.RoleEmployeeService; +import net.lab1024.sa.base.common.code.UnexpectedErrorCode; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartRequestUtil; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.dromara.warm.flow.core.FlowEngine; +import org.dromara.warm.flow.core.entity.*; +import org.dromara.warm.flow.core.enums.CooperateType; +import org.dromara.warm.flow.core.enums.NodeType; +import org.dromara.warm.flow.core.enums.UserType; +import org.dromara.warm.flow.core.service.*; +import org.dromara.warm.flow.core.utils.StreamUtils; +import org.dromara.warm.flow.orm.entity.FlowHisTask; +import org.dromara.warm.flow.orm.entity.FlowTask; +import org.springframework.beans.BeanUtils; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +/**load + * 流程实例Controller + * + * @author hh + * @date 2023-04-18 + */ +@Validated +@RestController +@Tag(name = AdminSwaggerTagConst.Flow.FLOW_INSTANCE) +@RequestMapping("/flow/execute") +public class ExecuteController { + @Resource + private EmployeeService employeeService; + + @Resource + private HisTaskService hisTaskService; + + @Resource + private TaskService taskService; + + @Resource + private NodeService nodeService; + + @Resource + private InsService insService; + + @Resource + private UserService flowUserservice; + + @Resource + private FlowTaskService flowTaskService; + + @Resource + private HhDefService hhDefService; + + @Resource + private RoleEmployeeService roleEmployeeService; + + @Resource + private LoginService loginService; + + + + /** + * 启动任务 + * + * @param startProcessForm 启动流程参数 + */ + @SaCheckPermission("flow:execute:startTask") + @PostMapping("/startWorkFlow") + public ResponseDTO startWorkFlow(@Validated() @RequestBody StartProcessForm startProcessForm) { + StartProcessReturnVO startProcessReturn = flowTaskService.startWorkFlow(startProcessForm); + return ResponseDTO.ok( startProcessReturn); + } + /** + * 审批任务 + * + * @param completeTaskForm 办理任务参数 + */ + @SaCheckPermission("flow:execute:approve") + @PostMapping("/completeTask") + public ResponseDTO completeTask(@RequestBody CompleteTaskForm completeTaskForm) { + boolean result = flowTaskService.completeTask(completeTaskForm); + if(result) + return ResponseDTO.ok(); + else + return ResponseDTO.error(UnexpectedErrorCode.BUSINESS_HANDING, "流程办理失败"); + } + + /** + *审批任务 + * + * @param approveTaskInsForm 办理任务参数 + */ + @SaCheckPermission("flow:execute:approve") + @PostMapping("/approveTaskByInstance") + public ResponseDTO approveTaskByInstance(@RequestBody ApproveTaskInsForm approveTaskInsForm) { + boolean result = flowTaskService.approveTaskByInstance(approveTaskInsForm); + if(result) + return ResponseDTO.ok(); + else + return ResponseDTO.error(UnexpectedErrorCode.BUSINESS_HANDING, "流程办理失败"); + } + + + /** + * 分页待办任务列表 + */ + @GetMapping("/toDoPage") + public ResponseDTO> toDoPage(FlowTask flowTask, @RequestParam(defaultValue = "1") int pageNum, + @RequestParam(defaultValue = "10") int pageSize) { + long userId = SmartRequestUtil.getRequestUserId(); + EmployeeEntity employee = employeeService.getById(userId); + List permissionList = permissionList(String.valueOf(employee.getEmployeeId()), employee.getDepartmentId(), employee); + flowTask.setPermissionList(permissionList); + // 使用 PageHelper 分页 + IPage pageParam = new Page<>(pageNum, pageSize); + IPage page = flowTaskService.toDoPage(pageParam, flowTask); + List list = page.getRecords(); + List taskIds = StreamUtils.toList(list, FlowTaskVO::getId); + List userList = flowUserservice.getByAssociateds(taskIds); + Map> map = StreamUtils.groupByKey(userList, User::getAssociated); + for (FlowTaskVO taskVo : list) { + if (SmartStringUtil.isNotNull(taskVo)) { + List users = map.get(taskVo.getId()); + if (CollectionUtils.isNotEmpty(users)) { + for (User user : users) { + if (UserType.APPROVAL.getKey().equals(user.getType())) { + if (SmartStringUtil.isEmpty(taskVo.getApprover())) { + taskVo.setApprover(""); + } + String name = loginService.getName(user.getProcessedBy(), null, null); + if (SmartStringUtil.isNotEmpty(name)) taskVo.setApprover(taskVo.getApprover().concat(name).concat(";")); + } else if (UserType.TRANSFER.getKey().equals(user.getType())) { + if (SmartStringUtil.isEmpty(taskVo.getTransferredBy())) { + taskVo.setTransferredBy(""); + } + String name = loginService.getName(user.getProcessedBy(), null, null); + if (SmartStringUtil.isNotEmpty(name)) taskVo.setTransferredBy(taskVo.getTransferredBy().concat(name).concat(";")); + } else if (UserType.DEPUTE.getKey().equals(user.getType())) { + if (SmartStringUtil.isEmpty(taskVo.getDelegate())) { + taskVo.setDelegate(""); + } + String name = loginService.getName(user.getProcessedBy(), null, null); + if (SmartStringUtil.isNotEmpty(name)) taskVo.setDelegate(taskVo.getDelegate().concat(name).concat(";")); + } + } + } + } + } + // 转换为自定义分页响应对象 + PageResult pageResult = new PageResult<>(); + pageResult.setList(page.getRecords()); + pageResult.setTotal(page.getTotal()); + pageResult.setPageNum( page.getCurrent()); + pageResult.setPageSize( page.getSize()); + pageResult.setPages( page.getPages()); + return ResponseDTO.ok(pageResult); + } + + /** + * 分页抄送任务列表 + * author:暗影 + */ + @GetMapping("/copyPage") + public ResponseDTO> copyPage(FlowTask flowTask, @RequestParam(defaultValue = "1") int pageNum, + @RequestParam(defaultValue = "10") int pageSize) { + long userId = SmartRequestUtil.getRequestUserId(); + EmployeeEntity employee = employeeService.getById(userId); + List permissionList = permissionList(String.valueOf(employee.getEmployeeId()), employee.getDepartmentId(), employee); + flowTask.setPermissionList(permissionList); + // 使用 PageHelper 分页 + IPage pageParam = new Page<>(pageNum, pageSize); + IPage page = flowTaskService.copyPage(pageParam, flowTask); + + // 转换为自定义分页响应对象 + PageResult pageResult = new PageResult<>(); + pageResult.setList(page.getRecords()); + pageResult.setTotal(page.getTotal()); + pageResult.setPageNum( page.getCurrent()); + pageResult.setPageSize( page.getSize()); + pageResult.setPages( page.getPages()); + return ResponseDTO.ok(pageResult); + } + /** + * 分页已办任务列表 + */ + @GetMapping("/donePage") + public ResponseDTO> donePage(FlowHisTask flowHisTask, @RequestParam(defaultValue = "1") int pageNum, + @RequestParam(defaultValue = "10") int pageSize) { + + long userId = SmartRequestUtil.getRequestUserId(); + EmployeeEntity employee = employeeService.getById(userId); + List permissionList = permissionList(String.valueOf(employee.getEmployeeId()), employee.getDepartmentId(), employee); + flowHisTask.setPermissionList(permissionList); + // 使用 PageHelper 分页 + IPage pageParam = new Page<>(pageNum, pageSize); + IPage page = flowTaskService.donePage(pageParam,flowHisTask); + List list = page.getRecords(); + Map userMap = StreamUtils.toMap(employeeService.queryAllEmployee(false).getData() + , EmployeeVO::getEmployeeId, EmployeeVO::getActualName); + if (CollectionUtils.isNotEmpty(list)) { + for (FlowHisTask hisTask : list) { + if (SmartStringUtil.isNotEmpty(hisTask.getApprover())) { + String name = loginService.getName(hisTask.getApprover(), null, null); + hisTask.setApprover(name); + } + if (SmartStringUtil.isNotEmpty(hisTask.getCollaborator())) { + String[] split = hisTask.getCollaborator().split(","); + if (split.length > 1) { + List names = new ArrayList<>(); + for (String s : split) { + names.add(userMap.get(Long.valueOf(s))); + } + hisTask.setCollaborator(StringUtils.join(names, ",")); + } + } + } + } + // 转换为自定义分页响应对象 + PageResult pageResult = new PageResult<>(); + pageResult.setList(page.getRecords()); + pageResult.setTotal(page.getTotal()); + pageResult.setPageNum( page.getCurrent()); + pageResult.setPageSize( page.getSize()); + pageResult.setPages( page.getPages()); + return ResponseDTO.ok(pageResult); + } + + + /** + * 查询已办任务历史记录 + */ + @GetMapping("/doneList/{instanceId}") + public ResponseDTO> doneList(@PathVariable("instanceId") Long instanceId) { + List flowHisTasks = hisTaskService.orderById().desc().list(new FlowHisTask().setInstanceId(instanceId)); + Map userMap = StreamUtils.toMap(employeeService.queryAllEmployee(false).getData() + , EmployeeVO::getEmployeeId, EmployeeVO::getActualName); + List flowHisTaskList = new ArrayList<>(); + if (CollectionUtils.isNotEmpty(flowHisTasks)) { + for (HisTask hisTask : flowHisTasks) { + if (SmartStringUtil.isNotEmpty(hisTask.getApprover())) { + String name = loginService.getName(hisTask.getApprover(), null, null); + hisTask.setApprover(name); + } + if (SmartStringUtil.isNotEmpty(hisTask.getCollaborator())) { + String[] split = hisTask.getCollaborator().split(","); + if (split.length > 1) { + List names = new ArrayList<>(); + for (String s : split) { + names.add(userMap.get(Long.valueOf(s))); + } + hisTask.setCollaborator(StringUtils.join(names, ",")); + } + } + FlowHisTask flowHisTask = new FlowHisTask(); + BeanUtils.copyProperties(hisTask, flowHisTask); + flowHisTaskList.add(flowHisTask); + } + } + return ResponseDTO.ok(flowHisTaskList); + } + + /** + * 根据taskId查询代表任务 + * + * @param taskId + * @return + */ + @GetMapping("/getTaskById/{taskId}") + public ResponseDTO getTaskById(@PathVariable("taskId") Long taskId) { + return ResponseDTO.ok(taskService.getById(taskId)); + } + + /** + * 根据taskId查询代表任务 + * + * @param taskId + * @return + */ + @GetMapping("/getTaskDetail/{taskId}") + public ResponseDTO getTaskDetailById(@PathVariable("taskId") Long taskId) { + return ResponseDTO.ok(flowTaskService.getTaskDetailById(taskId)); + } + + /** + * 根据taskId查询代表任务 + * + * @param taskId + * @return + */ + @GetMapping("/getDoneTaskDetail/{taskId}") + public ResponseDTO getDoneTaskDetail(@PathVariable("taskId") Long taskId) { + return ResponseDTO.ok(flowTaskService.getDoneTaskDetailById(taskId)); + } + + /** + * 查询跳转任意节点列表 + */ + @SaCheckPermission("flow:execute:goAnyNode") + @GetMapping("/anyNodeList/{instanceId}") + public ResponseDTO> anyNodeList(@PathVariable("instanceId") Long instanceId) { + Instance instance = insService.getById(instanceId); + List nodeList = nodeService.list(FlowEngine.newNode().setDefinitionId(instance.getDefinitionId())); + nodeList.removeIf(node -> NodeType.isGateWay(node.getNodeType())); + return ResponseDTO.ok(nodeList); + } + + /** + * 处理非办理的流程交互类型 + * + * @param WarmFlowInteractiveTypeVO 要转办用户 + * @return 是否成功 + */ + @PostMapping("/interactiveType") + public ResponseDTO interactiveType(WarmFlowInteractiveTypeVO WarmFlowInteractiveTypeVO) { + return ResponseDTO.ok(hhDefService.interactiveType(WarmFlowInteractiveTypeVO)); + } + + /** + * 交互类型可以选择的用户 + * + * @param warmFlowInteractiveTypeVO 交互类型请求类 + * @return 是否成功 + */ + @GetMapping("/interactiveTypeEmployeeEntity") + public ResponseDTO> interactiveTypeEmployeeEntity(WarmFlowInteractiveTypeVO warmFlowInteractiveTypeVO, @RequestParam(defaultValue = "1") int pageNum, + @RequestParam(defaultValue = "10") int pageSize) { + + long userId = SmartRequestUtil.getRequestUserId(); + Integer operatorType = warmFlowInteractiveTypeVO.getOperatorType(); + // 构建分页对象 + Page pageParam = new Page<>(pageNum, pageSize); + // 根据操作类型查询分页数据 + Page page; + + Long taskId = warmFlowInteractiveTypeVO.getTaskId(); + List users = flowUserservice.listByAssociatedAndTypes(taskId); + if (!Objects.equals(CooperateType.REDUCTION_SIGNATURE.getKey(), operatorType)) { + List userIds = StreamUtils.toList(users, User::getProcessedBy); + warmFlowInteractiveTypeVO.setUserIds(userIds); + page = employeeService.selectNotUserList(pageParam,warmFlowInteractiveTypeVO); + } else { + List userIds = StreamUtils.toList(users, User::getProcessedBy); + warmFlowInteractiveTypeVO.setUserIds(userIds); + page = employeeService.selectUserList(pageParam,warmFlowInteractiveTypeVO); + // 过滤当前用户(内存过滤,适用于小数据量) + List filteredList = page.getRecords().stream() + .filter(employee -> !Objects.equals(employee.getEmployeeId(), userId)) + .collect(Collectors.toList()); + page.setRecords(filteredList);} + // 转换为自定义分页响应对象 + PageResult pageResult = new PageResult<>(); + pageResult.setList(page.getRecords()); + pageResult.setTotal(page.getTotal()); + pageResult.setPageNum( page.getCurrent()); + pageResult.setPageSize( page.getSize()); + pageResult.setPages( page.getPages()); + return ResponseDTO.ok(pageResult); + } + + /** + * 激活流程 + * + * @param instanceId + * @return + */ + @GetMapping("/active/{instanceId}") + public ResponseDTO active(@PathVariable("instanceId") Long instanceId) { + return ResponseDTO.ok(insService.active(instanceId)); + } + + /** + * 挂起流程 + * + * @param instanceId + * @return + */ + @GetMapping("/unActive/{instanceId}") + public ResponseDTO unActive(@PathVariable("instanceId") Long instanceId) { + return ResponseDTO.ok(insService.unActive(instanceId)); + } + + /** + * 获取权限 + * + * @param userId 用户编号 + * @param deptId 部门编号 + * @param employee 登陆用户 + * @return 权限列表 + */ + private List permissionList(String userId, Long deptId, EmployeeEntity employee) { + List roles = roleEmployeeService.getRoleInfoListByEmployeeId(employee.getEmployeeId()); + List permissionList = new ArrayList<>(); + if (CollectionUtils.isNotEmpty(roles)) { + permissionList = StreamUtils.toList(roles, role -> "role:" + role.getRoleId()); + } + permissionList.add(userId); + if (Objects.nonNull(deptId)) { + permissionList.add("dept:" + deptId); + } + return permissionList; + } + + /** + * 根据ID反显姓名 + * + * @param ids 需要反显姓名的用户ID + * @return {@link ResponseDTO< List>} + * @author liangli + * @date 2024/8/21 17:08 + **/ + @GetMapping(value = "/idReverseDisplayName/{ids}") + public ResponseDTO> idReverseDisplayName(@PathVariable Long[] ids) { + return ResponseDTO.ok(flowTaskService.idReverseDisplayName(ids)); + } + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/contstant/FlowConstant.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/contstant/FlowConstant.java new file mode 100644 index 0000000..b45a279 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/contstant/FlowConstant.java @@ -0,0 +1,71 @@ +package net.lab1024.sa.admin.module.flow.contstant; + + +/** + * 工作流常量 + * + * @author may + */ +public interface FlowConstant { + + /** + * 流程发起人 + */ + String INITIATOR = "initiator"; + + /** + * 流程实例id + */ + String PROCESS_INSTANCE_ID = "processInstanceId"; + + + /** + * 业务表 + */ + String BUSINESS_TABLE = "businessTable"; + /** + * 业务id + */ + String BUSINESS_ID = "businessId"; + + /** + * 任务id + */ + String TASK_ID = "taskId"; + + /** + * 委托 + */ + String DELEGATE_TASK = "delegateTask"; + + /** + * 转办 + */ + String TRANSFER_TASK = "transferTask"; + + /** + * 加签 + */ + String ADD_SIGNATURE = "addSignature"; + + /** + * 减签 + */ + String REDUCTION_SIGNATURE = "reductionSignature"; + + /** + * 流程分类Id转名称 + */ + String CATEGORY_ID_TO_NAME = "category_id_to_name"; + + /** + * 流程分类名称 + */ + String FLOW_CATEGORY_NAME = "flow_category_name#30d"; + + /** + * 默认租户OA申请分类id + */ + Long FLOW_CATEGORY_ID = 100L; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/dao/FlowDefinitionDao.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/dao/FlowDefinitionDao.java new file mode 100644 index 0000000..c94cbbe --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/dao/FlowDefinitionDao.java @@ -0,0 +1,45 @@ +package net.lab1024.sa.admin.module.flow.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.admin.module.flow.domain.form.FlowDefinitionQueryForm; +import net.lab1024.sa.admin.module.flow.domain.vo.FlowDefinitionVO; +import net.lab1024.sa.admin.module.system.position.domain.entity.PositionEntity; +import net.lab1024.sa.admin.module.system.position.domain.form.PositionQueryForm; +import net.lab1024.sa.admin.module.system.position.domain.vo.PositionVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.dromara.warm.flow.orm.entity.FlowDefinition; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 职务表 Dao + * + * @Author kaiyun + * @Date 2024-06-23 23:31:38 + * @Copyright 1024创新实验室 + */ + +@Mapper +@Component +public interface FlowDefinitionDao extends BaseMapper { + + /** + * 分页 查询 + * + * @param page + * @param queryForm + * @return + */ + List queryPage(Page page, @Param("queryForm") FlowDefinitionQueryForm queryForm); + + + /** + * 查询 + * @param deletedFlag + * @return + */ + List queryList(@Param("deletedFlag") Boolean deletedFlag); +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/domain/form/ApproveTaskInsForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/domain/form/ApproveTaskInsForm.java new file mode 100644 index 0000000..3712431 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/domain/form/ApproveTaskInsForm.java @@ -0,0 +1,79 @@ +package net.lab1024.sa.admin.module.flow.domain.form; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import net.lab1024.sa.admin.module.flow.domain.vo.FlowCopyVO; + +import java.io.Serial; +import java.io.Serializable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * 办理任务请求对象 + * + * @author may + */ +@Data +public class ApproveTaskInsForm implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 任务id + */ + @NotNull(message = "任务id不能为空") + private Long InstanceId; + + /** + * 附件id + */ + private String fileId; + + /** + * 抄送人员 + */ + private List flowCopyList; + + /** + * 消息类型 + */ + private List messageType; + + /** + * 办理意见 + */ + private String message; + + /** + * 消息通知 + */ + private String notice; + + /** + * 流程变量 + */ + private Map variables; + + /** + * 弹窗选择的办理人 + */ + private Map assigneeMap; + + /** + * 扩展变量(此处为逗号分隔的ossId) + */ + private String ext; + + public Map getVariables() { + if (variables == null) { + return new HashMap<>(16); + } + variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue())); + return variables; + } + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/domain/form/CompleteTaskForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/domain/form/CompleteTaskForm.java new file mode 100644 index 0000000..b06cfeb --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/domain/form/CompleteTaskForm.java @@ -0,0 +1,79 @@ +package net.lab1024.sa.admin.module.flow.domain.form; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import net.lab1024.sa.admin.module.flow.domain.vo.FlowCopyVO; + +import java.io.Serial; +import java.io.Serializable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * 办理任务请求对象 + * + * @author may + */ +@Data +public class CompleteTaskForm implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 任务id + */ + @NotNull(message = "任务id不能为空") + private Long taskId; + + /** + * 附件id + */ + private String fileId; + + /** + * 抄送人员 + */ + private List flowCopyList; + + /** + * 消息类型 + */ + private List messageType; + + /** + * 办理意见 + */ + private String message; + + /** + * 消息通知 + */ + private String notice; + + /** + * 流程变量 + */ + private Map variables; + + /** + * 弹窗选择的办理人 + */ + private Map assigneeMap; + + /** + * 扩展变量(此处为逗号分隔的ossId) + */ + private String ext; + + public Map getVariables() { + if (variables == null) { + return new HashMap<>(16); + } + variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue())); + return variables; + } + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/domain/form/FlowDefinitionAddForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/domain/form/FlowDefinitionAddForm.java new file mode 100644 index 0000000..befad60 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/domain/form/FlowDefinitionAddForm.java @@ -0,0 +1,55 @@ +package net.lab1024.sa.admin.module.flow.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 职务表 新建表单 + * + * @Author kaiyun + * @Date 2024-06-23 23:31:38 + * @Copyright 1024创新实验室 + */ + +@Data +public class FlowDefinitionAddForm { + + @Schema(description = "流程编码", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "流程编码不能为空") + private String flowCode; + + @Schema(description = "流程名称") + @NotBlank(message = "流程名称不能为空") + private String flowName; + + @Schema(description = "流程类别") + private String category; + + /** + * 审批表单是否自定义(Y是 N否) + */ + @Schema(description = "审批表单是否自定义") + @NotBlank(message = "审批表单是否自定义不能为空") + private String formCustom; + + /** + * 审批表单路径 + */ + @Schema(description = "审批表单路径") + private String formPath; + + /** + * 监听器类型 + */ + @Schema(description = "监听器类型") + private String listenerType; + + /** + * 监听器路径 + */ + @Schema(description = "监听器路径") + private String listenerPath; + +} \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/domain/form/FlowDefinitionQueryForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/domain/form/FlowDefinitionQueryForm.java new file mode 100644 index 0000000..6e480ed --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/domain/form/FlowDefinitionQueryForm.java @@ -0,0 +1,16 @@ +package net.lab1024.sa.admin.module.flow.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; + +@Data +public class FlowDefinitionQueryForm extends PageParam { + private String flowCode; + private String flowName; + private String category; + private String version; + + @Schema(hidden = true) + private Boolean deletedFlag; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/domain/form/FlowDefinitionUpdateForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/domain/form/FlowDefinitionUpdateForm.java new file mode 100644 index 0000000..3f1378c --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/domain/form/FlowDefinitionUpdateForm.java @@ -0,0 +1,22 @@ +package net.lab1024.sa.admin.module.flow.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 职务表 更新表单 + * + * @Author kaiyun + * @Date 2024-06-23 23:31:38 + * @Copyright 1024创新实验室 + */ + +@Data +public class FlowDefinitionUpdateForm extends FlowDefinitionAddForm { + + @Schema(description = "流程ID", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "流程ID 不能为空") + private Long id; + +} \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/domain/form/StartProcessForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/domain/form/StartProcessForm.java new file mode 100644 index 0000000..a1e3d2a --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/domain/form/StartProcessForm.java @@ -0,0 +1,47 @@ +package net.lab1024.sa.admin.module.flow.domain.form; + + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import java.io.Serial; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * 启动流程对象 + * + * @author may + */ +@Data +public class StartProcessForm implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 业务唯一值id + */ + @NotBlank(message = "业务ID不能为空") + private String businessId; + + /** + * 流程定义编码 + */ + @NotBlank(message = "流程定义编码不能为空") + private String flowCode; + + /** + * 流程变量,前端会提交一个元素{'entity': {业务详情数据对象}} + */ + private Map variables; + + public Map getVariables() { + if (variables == null) { + return new HashMap<>(16); + } + variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue())); + return variables; + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/domain/vo/FlowCopyVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/domain/vo/FlowCopyVO.java new file mode 100644 index 0000000..ee6dd4b --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/domain/vo/FlowCopyVO.java @@ -0,0 +1,30 @@ +package net.lab1024.sa.admin.module.flow.domain.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + + +/** + * 抄送 + * + * @author may + */ +@Data +public class FlowCopyVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 用户id + */ + private Long userId; + + /** + * 用户名称 + */ + private String userName; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/domain/vo/FlowDefinitionVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/domain/vo/FlowDefinitionVO.java new file mode 100644 index 0000000..6f8c6cd --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/domain/vo/FlowDefinitionVO.java @@ -0,0 +1,95 @@ +package net.lab1024.sa.admin.module.flow.domain.vo; + +import lombok.Data; +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 流程定义视图 + * + * @author may + */ +@Data +public class FlowDefinitionVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private Long id; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + + /** + * 租户ID + */ + private String tenantId; + + /** + * 删除标记 + */ + private String delFlag; + + /** + * 流程定义编码 + */ + private String flowCode; + + /** + * 流程定义名称 + */ + private String flowName; + + /** + * 流程分类id + */ + private String category; + + /** + * 流程版本 + */ + private String version; + + /** + * 是否发布(0未发布 1已发布 9失效) + */ + private Integer isPublish; + + /** + * 审批表单是否自定义(Y是 N否) + */ + private String formCustom; + + /** + * 审批表单路径 + */ + private String formPath; + + /** + * 流程激活状态(0挂起 1激活) + */ + private Integer activityStatus; + + /** + * 监听器类型 + */ + private String listenerType; + + /** + * 监听器路径 + */ + private String listenerPath; + + /** + * 扩展字段,预留给业务系统使用 + */ + private String ext; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/domain/vo/FlowInstanceVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/domain/vo/FlowInstanceVO.java new file mode 100644 index 0000000..2c08d86 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/domain/vo/FlowInstanceVO.java @@ -0,0 +1,131 @@ +package net.lab1024.sa.admin.module.flow.domain.vo; + +import lombok.Data; +import java.util.Date; + +/** + * 流程实例视图 + * + * @author may + */ +@Data +public class FlowInstanceVO { + + private Long id; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + + /** + * 租户ID + */ + private String tenantId; + + /** + * 删除标记 + */ + private String delFlag; + + /** + * 对应flow_definition表的id + */ + private Long definitionId; + + /** + * 流程定义名称 + */ + private String flowName; + + /** + * 流程定义编码 + */ + private String flowCode; + + /** + * 业务id + */ + private String businessId; + + /** + * 节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关) + */ + private Integer nodeType; + + /** + * 流程节点编码 每个流程的nodeCode是唯一的,即definitionId+nodeCode唯一,在数据库层面做了控制 + */ + private String nodeCode; + + /** + * 流程节点名称 + */ + private String nodeName; + + /** + * 流程变量 + */ + private String variable; + + /** + * 流程状态(0待提交 1审批中 2 审批通过 3自动通过 8已完成 9已退回 10失效) + */ + private String flowStatus; + + /** + * 流程状态 + */ + private String flowStatusName; + + /** + * 流程激活状态(0挂起 1激活) + */ + private Integer activityStatus; + + /** + * 审批表单是否自定义(Y是 N否) + */ + private String formCustom; + + /** + * 审批表单路径 + */ + private String formPath; + + /** + * 扩展字段,预留给业务系统使用 + */ + private String ext; + + /** + * 流程定义版本 + */ + private String version; + + /** + * 创建者 + */ + private String createBy; + + /** + * 申请人 + */ + private String createByName; + + /** + * 流程分类id + */ + private String category; + + /** + * 流程分类名称 + */ + private String categoryName; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/domain/vo/FlowTaskVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/domain/vo/FlowTaskVO.java new file mode 100644 index 0000000..bbc68c2 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/domain/vo/FlowTaskVO.java @@ -0,0 +1,130 @@ +package net.lab1024.sa.admin.module.flow.domain.vo; + +import cn.hutool.json.JSONObject; +import org.dromara.warm.flow.orm.entity.FlowTask; + +import java.util.Map; + +/** + * 待办任务vo + * + * @author warm + */ +public class FlowTaskVO extends FlowTask { + + /** + * 计划审批人 + */ + private String initiator; + + /** + * 计划审批人 + */ + private String approver; + + /** + * 转办人 + */ + private String transferredBy; + + /** + * 委派人 + */ + private String delegate; + /** + * 流程编码 + */ + private String flowCode; + + /** + * 流程状态 + */ + private String flowStatus; + + private JSONObject businessFormMetaData; + + + private Map businessForm; + + /** + * 激活状态 + */ + private Integer activityStatus; + + public String getApprover() { + return approver; + } + + public FlowTaskVO setApprover(String approver) { + this.approver = approver; + return this; + } + + public String getTransferredBy() { + return transferredBy; + } + + public FlowTaskVO setTransferredBy(String transferredBy) { + this.transferredBy = transferredBy; + return this; + } + + public String getDelegate() { + return delegate; + } + + public FlowTaskVO setDelegate(String delegate) { + this.delegate = delegate; + return this; + } + + public String getFlowStatus() { + return flowStatus; + } + + public FlowTaskVO setFlowStatus(String flowStatus) { + this.flowStatus = flowStatus; + return this; + } + + public Integer getActivityStatus() { + return activityStatus; + } + + public FlowTaskVO setActivityStatus(Integer activityStatus) { + this.activityStatus = activityStatus; + return this; + } + + public String getFlowCode() { + return flowCode; + } + + public void setFlowCode(String flowCode) { + this.flowCode = flowCode; + } + + public JSONObject getBusinessFormMetaData() { + return businessFormMetaData; + } + + public void setBusinessFormMetaData(JSONObject businessFormMetaData) { + this.businessFormMetaData = businessFormMetaData; + } + + public Map getBusinessForm() { + return businessForm; + } + + public void setBusinessForm(Map businessForm) { + this.businessForm = businessForm; + } + + public String getInitiator() { + return initiator; + } + + public void setInitiator(String initiator) { + this.initiator = initiator; + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/domain/vo/StartProcessReturnVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/domain/vo/StartProcessReturnVO.java new file mode 100644 index 0000000..17117f1 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/domain/vo/StartProcessReturnVO.java @@ -0,0 +1,30 @@ +package net.lab1024.sa.admin.module.flow.domain.vo; + + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 启动流程返回对象 + * + * @author Lion Li + */ +@Data +public class StartProcessReturnVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 流程实例id + */ + private Long processInstanceId; + + /** + * 任务id + */ + private Long taskId; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/domain/vo/WarmFlowInteractiveTypeVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/domain/vo/WarmFlowInteractiveTypeVO.java new file mode 100644 index 0000000..313ab04 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/domain/vo/WarmFlowInteractiveTypeVO.java @@ -0,0 +1,88 @@ +package net.lab1024.sa.admin.module.flow.domain.vo; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +public class WarmFlowInteractiveTypeVO implements Serializable { + /** + * 任务id + */ + private Long taskId; + + /** + * 增加办理人 + */ + private List addHandlers; + + /** + * 操作类型[2:转办,6:加签,3:委派,7:减签] + */ + private Integer operatorType; + + /** + * 部门ID + */ + private Long departmentId; + + private List userIds; + + /** 请求参数 */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private Map params; + + public Long getTaskId() { + return taskId; + } + + public WarmFlowInteractiveTypeVO setTaskId(Long taskId) { + this.taskId = taskId; + return this; + } + + public List getAddHandlers() { + return addHandlers; + } + + public WarmFlowInteractiveTypeVO setAddHandlers(List addHandlers) { + this.addHandlers = addHandlers; + return this; + } + + public Integer getOperatorType() { + return operatorType; + } + + public WarmFlowInteractiveTypeVO setOperatorType(Integer operatorType) { + this.operatorType = operatorType; + return this; + } + + public Long getDepartmentId() { + return departmentId; + } + + public void setDepartmentId(Long departmentId) { + this.departmentId = departmentId; + } + + public List getUserIds() { + return userIds; + } + + public WarmFlowInteractiveTypeVO setUserIds(List userIds) { + this.userIds = userIds; + return this; + } + + public Map getParams() { + return params; + } + + public void setParams(Map params) { + this.params = params; + } + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/enums/BusinessStatusEnum.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/enums/BusinessStatusEnum.java new file mode 100644 index 0000000..4b77e35 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/enums/BusinessStatusEnum.java @@ -0,0 +1,214 @@ +package net.lab1024.sa.admin.module.flow.enums; + +import cn.hutool.core.util.StrUtil; +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.lab1024.sa.base.common.exception.BusinessException; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 业务状态枚举 + * + * @author may + */ +@Getter +@AllArgsConstructor +public enum BusinessStatusEnum { + + /** + * 已撤销 + */ + CANCEL("cancel", "已撤销"), + + /** + * 草稿 + */ + DRAFT("draft", "草稿"), + + /** + * 待审核 + */ + WAITING("waiting", "待审核"), + + /** + * 已完成 + */ + FINISH("finish", "已完成"), + + /** + * 已作废 + */ + INVALID("invalid", "已作废"), + + /** + * 已退回 + */ + BACK("back", "已退回"), + + /** + * 已终止 + */ + TERMINATION("termination", "已终止"); + + /** + * 状态 + */ + private final String status; + + /** + * 描述 + */ + private final String desc; + + private static final Map STATUS_MAP = Arrays.stream(BusinessStatusEnum.values()) + .collect(Collectors.toConcurrentMap(BusinessStatusEnum::getStatus, Function.identity())); + + /** + * 根据状态获取对应的 BusinessStatusEnum 枚举 + * + * @param status 业务状态码 + * @return 对应的 BusinessStatusEnum 枚举,如果找不到则返回 null + */ + public static BusinessStatusEnum getByStatus(String status) { + // 使用 STATUS_MAP 获取对应的枚举,若找不到则返回 null + return STATUS_MAP.get(status); + } + + /** + * 根据状态获取对应的业务状态描述信息 + * + * @param status 业务状态码 + * @return 返回业务状态描述,若状态码为空或未找到对应的枚举,返回空字符串 + */ + public static String findByStatus(String status) { + if (SmartStringUtil.isEmpty(status)) { + return StrUtil.EMPTY; + } + BusinessStatusEnum statusEnum = STATUS_MAP.get(status); + return (statusEnum != null) ? statusEnum.getDesc() : StrUtil.EMPTY; + } + + /** + * 判断是否为指定的状态之一:草稿、已撤销或已退回 + * + * @param status 要检查的状态 + * @return 如果状态为草稿、已撤销或已退回之一,则返回 true;否则返回 false + */ + public static boolean isDraftOrCancelOrBack(String status) { + return DRAFT.status.equals(status) || CANCEL.status.equals(status) || BACK.status.equals(status); + } + + /** + * 判断是否为撤销,退回,作废,终止 + * + * @param status status + * @return 结果 + */ + public static boolean initialState(String status) { + return CANCEL.status.equals(status) || BACK.status.equals(status) || INVALID.status.equals(status) || TERMINATION.status.equals(status); + } + + /** + * 获取运行中的实例状态列表 + * + * @return 包含运行中实例状态的不可变列表 + * (包含 DRAFT、WAITING、BACK 和 CANCEL 状态) + */ + public static List runningStatus() { + return Arrays.asList(DRAFT.status, WAITING.status, BACK.status, CANCEL.status); + } + + /** + * 获取结束实例的状态列表 + * + * @return 包含结束实例状态的不可变列表 + * (包含 FINISH、INVALID 和 TERMINATION 状态) + */ + public static List finishStatus() { + return Arrays.asList(FINISH.status, INVALID.status, TERMINATION.status); + } + + /** + * 启动流程校验 + * + * @param status 状态 + */ + public static void checkStartStatus(String status) { + if (WAITING.getStatus().equals(status)) { + throw new BusinessException("该单据已提交过申请,正在审批中!"); + } else if (FINISH.getStatus().equals(status)) { + throw new BusinessException("该单据已完成申请!"); + } else if (INVALID.getStatus().equals(status)) { + throw new BusinessException("该单据已作废!"); + } else if (TERMINATION.getStatus().equals(status)) { + throw new BusinessException("该单据已终止!"); + } else if (SmartStringUtil.isEmpty(status)) { + throw new BusinessException("流程状态为空!"); + } + } + + /** + * 撤销流程校验 + * + * @param status 状态 + */ + public static void checkCancelStatus(String status) { + if (CANCEL.getStatus().equals(status)) { + throw new BusinessException("该单据已撤销!"); + } else if (FINISH.getStatus().equals(status)) { + throw new BusinessException("该单据已完成申请!"); + } else if (INVALID.getStatus().equals(status)) { + throw new BusinessException("该单据已作废!"); + } else if (TERMINATION.getStatus().equals(status)) { + throw new BusinessException("该单据已终止!"); + } else if (BACK.getStatus().equals(status)) { + throw new BusinessException("该单据已退回!"); + } else if (SmartStringUtil.isEmpty(status)) { + throw new BusinessException("流程状态为空!"); + } + } + + /** + * 驳回流程校验 + * + * @param status 状态 + */ + public static void checkBackStatus(String status) { + if (BACK.getStatus().equals(status)) { + throw new BusinessException("该单据已退回!"); + } else if (FINISH.getStatus().equals(status)) { + throw new BusinessException("该单据已完成申请!"); + } else if (INVALID.getStatus().equals(status)) { + throw new BusinessException("该单据已作废!"); + } else if (TERMINATION.getStatus().equals(status)) { + throw new BusinessException("该单据已终止!"); + } else if (CANCEL.getStatus().equals(status)) { + throw new BusinessException("该单据已撤销!"); + } else if (SmartStringUtil.isEmpty(status)) { + throw new BusinessException("流程状态为空!"); + } + } + + /** + * 作废,终止流程校验 + * + * @param status 状态 + */ + public static void checkInvalidStatus(String status) { + if (FINISH.getStatus().equals(status)) { + throw new BusinessException("该单据已完成申请!"); + } else if (INVALID.getStatus().equals(status)) { + throw new BusinessException("该单据已作废!"); + } else if (TERMINATION.getStatus().equals(status)) { + throw new BusinessException("该单据已终止!"); + } else if (SmartStringUtil.isEmpty(status)) { + throw new BusinessException("流程状态为空!"); + } + } + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/enums/FlowStatusEnum.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/enums/FlowStatusEnum.java new file mode 100644 index 0000000..1618743 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/enums/FlowStatusEnum.java @@ -0,0 +1,52 @@ +package net.lab1024.sa.admin.module.flow.enums; + +public enum FlowStatusEnum { + + PENDING_SUBMIT(0, "待提交"), + APPROVING(1, "审批中"), + APPROVED(2, "审批通过"), + AUTO_APPROVED(3, "自动通过"), + TERMINATED(4, "终止"), + INVALID(5, "作废"), + REVOKED(6, "撤销"), + RETRIEVED(7, "取回"), + COMPLETED(8, "已完成"), + RETURNED(9, "已退回"), + EXPIRED(10, "失效"); + + private final int value; + private final String desc; + + FlowStatusEnum(int value, String desc) { + this.value = value; + this.desc = desc; + } + + public int getValue() { + return value; + } + + public String getDesc() { + return desc; + } + + // 通过数值获取枚举实例 + public static FlowStatusEnum fromValue(int value) { + for (FlowStatusEnum status : values()) { + if (status.value == value) { + return status; + } + } + throw new IllegalArgumentException("无效的状态值: " + value); + } + + // 通过描述获取枚举实例 + public static FlowStatusEnum fromDesc(String desc) { + for (FlowStatusEnum status : values()) { + if (status.desc.equals(desc)) { + return status; + } + } + throw new IllegalArgumentException("无效的状态描述: " + desc); + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/enums/TaskAssigneeEnum.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/enums/TaskAssigneeEnum.java new file mode 100644 index 0000000..22e0e79 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/enums/TaskAssigneeEnum.java @@ -0,0 +1,109 @@ +package net.lab1024.sa.admin.module.flow.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.lab1024.sa.base.common.exception.BusinessException; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 任务分配人枚举 + * + * @author AprilWind + */ +@Getter +@AllArgsConstructor +public enum TaskAssigneeEnum { + + /** + * 用户 + */ + USER("用户", ""), + + /** + * 角色 + */ + ROLE("角色", "role:"), + + /** + * 部门 + */ + DEPT("部门", "dept:"), + + /** + * 岗位 + */ + POST("岗位", "post:"); + + private final String desc; + private final String code; + + /** + * 根据描述获取对应的枚举类型 + *

+ * 通过传入描述,查找并返回匹配的枚举项。如果未找到匹配项,会抛出 {@link BusinessException}。 + *

+ * + * @param desc 描述,用于匹配对应的枚举项 + * @return TaskAssigneeEnum 返回对应的枚举类型 + * @throws BusinessException 如果未找到匹配的枚举项 + */ + public static TaskAssigneeEnum fromDesc(String desc) { + for (TaskAssigneeEnum type : values()) { + if (type.getDesc().equals(desc)) { + return type; + } + } + throw new BusinessException("未知的办理人类型: " + desc); + } + + /** + * 根据代码获取对应的枚举类型 + *

+ * 通过传入代码,查找并返回匹配的枚举项。如果未找到匹配项,会抛出 {@link BusinessException}。 + *

+ * + * @param code 代码,用于匹配对应的枚举项 + * @return TaskAssigneeEnum 返回对应的枚举类型 + * @throws IllegalArgumentException 如果未找到匹配的枚举项 + */ + public static TaskAssigneeEnum fromCode(String code) { + for (TaskAssigneeEnum type : values()) { + if (type.getCode().equals(code)) { + return type; + } + } + throw new BusinessException("未知的办理人类型代码: " + code); + } + + /** + * 获取所有办理人类型的描述列表 + *

+ * 获取当前枚举类所有项的描述字段列表,通常用于展示选择项。 + *

+ * + * @return List 返回所有办理人类型的描述列表 + */ + public static List getAssigneeTypeList() { + return Arrays.stream(values()) + .map(TaskAssigneeEnum::getDesc) + .collect(Collectors.toList()); + } + + /** + * 获取所有办理人类型的代码列表 + *

+ * 获取当前枚举类所有项的代码字段列表,通常用于程序内部逻辑的判断。 + *

+ * + * @return List 返回所有办理人类型的代码列表 + */ + public static List getAssigneeCodeList() { + return Arrays.stream(values()) + .map(TaskAssigneeEnum::getCode) + .collect(Collectors.toList()); + } +} + diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/enums/TaskAssigneeType.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/enums/TaskAssigneeType.java new file mode 100644 index 0000000..148b1e9 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/enums/TaskAssigneeType.java @@ -0,0 +1,49 @@ +package net.lab1024.sa.admin.module.flow.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 人员类型 + * + * @author AprilWind + */ +@Getter +@AllArgsConstructor +public enum TaskAssigneeType { + + /** + * 待办任务的审批人权限 + *

该权限表示用户是待办任务的审批人,负责审核任务的执行情况。

+ */ + APPROVER("1", "待办任务的审批人权限"), + + /** + * 待办任务的转办人权限 + *

该权限表示用户是待办任务的转办人,负责将任务分配给其他人员。

+ */ + TRANSFER("2", "待办任务的转办人权限"), + + /** + * 待办任务的委托人权限 + *

该权限表示用户是待办任务的委托人,能够委托其他人代为处理任务。

+ */ + DELEGATE("3", "待办任务的委托人权限"), + + /** + * 待办任务的抄送人权限 + *

该权限表示用户是待办任务的抄送人,仅接收任务信息的通知,不参与任务的审批或处理。

+ */ + COPY("4", "待办任务的抄送人权限"); + + /** + * 类型 + */ + private final String code; + + /** + * 描述 + */ + private final String description; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/enums/TaskStatusEnum.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/enums/TaskStatusEnum.java new file mode 100644 index 0000000..2ea4b3a --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/enums/TaskStatusEnum.java @@ -0,0 +1,104 @@ +package net.lab1024.sa.admin.module.flow.enums; + +import cn.hutool.core.util.StrUtil; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 任务状态枚举 + * + * @author may + */ +@Getter +@AllArgsConstructor +public enum TaskStatusEnum { + + /** + * 撤销 + */ + CANCEL("cancel", "撤销"), + + /** + * 通过 + */ + PASS("pass", "通过"), + + /** + * 待审核 + */ + WAITING("waiting", "待审核"), + + /** + * 作废 + */ + INVALID("invalid", "作废"), + + /** + * 退回 + */ + BACK("back", "退回"), + + /** + * 终止 + */ + TERMINATION("termination", "终止"), + + /** + * 转办 + */ + TRANSFER("transfer", "转办"), + + /** + * 委托 + */ + DEPUTE("depute", "委托"), + + /** + * 抄送 + */ + COPY("copy", "抄送"), + + /** + * 加签 + */ + SIGN("sign", "加签"), + + /** + * 减签 + */ + SIGN_OFF("sign_off", "减签"), + + /** + * 超时 + */ + TIMEOUT("timeout", "超时"); + + /** + * 状态 + */ + private final String status; + + /** + * 描述 + */ + private final String desc; + + private static final Map STATUS_DESC_MAP = Arrays.stream(values()) + .collect(Collectors.toConcurrentMap(TaskStatusEnum::getStatus, TaskStatusEnum::getDesc)); + + /** + * 任务业务状态 + * + * @param status 状态 + */ + public static String findByStatus(String status) { + // 从缓存中直接获取描述 + return STATUS_DESC_MAP.getOrDefault(status, StrUtil.EMPTY); + } + +} + diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/event/ProcessCreateTaskEvent.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/event/ProcessCreateTaskEvent.java new file mode 100644 index 0000000..1cd87a2 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/event/ProcessCreateTaskEvent.java @@ -0,0 +1,39 @@ +package net.lab1024.sa.admin.module.flow.event; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 流程创建任务监听 + * + * @author may + */ +@Data +public class ProcessCreateTaskEvent implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 流程定义编码 + */ + private String flowCode; + + /** + * 审批节点编码 + */ + private String nodeCode; + + /** + * 任务id + */ + private Long taskId; + + /** + * 业务id + */ + private String businessId; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/event/ProcessDeleteEvent.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/event/ProcessDeleteEvent.java new file mode 100644 index 0000000..942e414 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/event/ProcessDeleteEvent.java @@ -0,0 +1,29 @@ +package net.lab1024.sa.admin.module.flow.event; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 删除流程监听 + * + * @author AprilWind + */ +@Data +public class ProcessDeleteEvent implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 流程定义编码 + */ + private String flowCode; + + /** + * 业务id + */ + private String businessId; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/event/ProcessEvent.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/event/ProcessEvent.java new file mode 100644 index 0000000..f4f698a --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/event/ProcessEvent.java @@ -0,0 +1,45 @@ +package net.lab1024.sa.admin.module.flow.event; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Map; + +/** + * 总体流程监听 + * + * @author may + */ +@Data +public class ProcessEvent implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 流程定义编码 + */ + private String flowCode; + + /** + * 业务id + */ + private String businessId; + + /** + * 状态 + */ + private String status; + + /** + * 办理参数 + */ + private Map params; + + /** + * 当为true时为申请人节点办理 + */ + private boolean submit; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/handler/FlowProcessEventHandler.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/handler/FlowProcessEventHandler.java new file mode 100644 index 0000000..7a8ab77 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/handler/FlowProcessEventHandler.java @@ -0,0 +1,73 @@ +package net.lab1024.sa.admin.module.flow.handler; + +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.admin.module.flow.annoation.ConditionalOnEnable; +import net.lab1024.sa.admin.module.flow.event.ProcessCreateTaskEvent; +import net.lab1024.sa.admin.module.flow.event.ProcessDeleteEvent; +import net.lab1024.sa.admin.module.flow.event.ProcessEvent; +import org.springframework.stereotype.Component; +import net.lab1024.sa.base.common.util.SpringUtils; +import java.util.Map; + +/** + * 流程监听服务 + * + * @author may + * @date 2024-06-02 + */ +@Slf4j +@Component +public class FlowProcessEventHandler { + + /** + * 总体流程监听(例如: 草稿,撤销,退回,作废,终止,已完成,单任务完成等) + * + * @param flowCode 流程定义编码 + * @param businessId 业务id + * @param status 状态 + * @param submit 当为true时为申请人节点办理 + */ + public void processHandler(String flowCode, String businessId, String status, Map params, boolean submit) { + log.info("发布流程事件,租户ID: {}, 流程状态: {}, 流程编码: {}, 业务ID: {}, 是否申请人节点办理: {}", status, flowCode, businessId, submit); + ProcessEvent processEvent = new ProcessEvent(); + processEvent.setFlowCode(flowCode); + processEvent.setBusinessId(businessId); + processEvent.setStatus(status); + processEvent.setParams(params); + processEvent.setSubmit(submit); + SpringUtils.getApplicationContext().publishEvent(processEvent); + } + + /** + * 执行创建任务监听 + * + * @param flowCode 流程定义编码 + * @param nodeCode 审批节点编码 + * @param taskId 任务id + * @param businessId 业务id + */ + public void processCreateTaskHandler(String flowCode, String nodeCode, Long taskId, String businessId) { + log.info("发布流程任务事件, 租户ID: {}, 流程编码: {}, 节点编码: {}, 任务ID: {}, 业务ID: {}",flowCode, nodeCode, taskId, businessId); + ProcessCreateTaskEvent processCreateTaskEvent = new ProcessCreateTaskEvent(); + processCreateTaskEvent.setFlowCode(flowCode); + processCreateTaskEvent.setNodeCode(nodeCode); + processCreateTaskEvent.setTaskId(taskId); + processCreateTaskEvent.setBusinessId(businessId); + SpringUtils.getApplicationContext().publishEvent(processCreateTaskEvent); + } + + /** + * 删除流程监听 + * + * @param flowCode 流程定义编码 + * @param businessId 业务ID + */ + public void processDeleteHandler(String flowCode, String businessId) { + log.info("发布删除流程事件, 租户ID: {}, 流程编码: {}, 业务ID: {}", flowCode, businessId); + ProcessDeleteEvent processDeleteEvent = new ProcessDeleteEvent(); + processDeleteEvent.setFlowCode(flowCode); + processDeleteEvent.setBusinessId(businessId); + SpringUtils.getApplicationContext().publishEvent(processDeleteEvent); + } + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/handler/WorkflowPermissionHandler.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/handler/WorkflowPermissionHandler.java new file mode 100644 index 0000000..7b60f1d --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/handler/WorkflowPermissionHandler.java @@ -0,0 +1,99 @@ +package net.lab1024.sa.admin.module.flow.handler; + +import cn.hutool.core.util.ObjectUtil; +import jakarta.annotation.Resource; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.admin.module.flow.annoation.ConditionalOnEnable; +import net.lab1024.sa.admin.module.flow.enums.TaskAssigneeEnum; +import net.lab1024.sa.admin.module.system.employee.domain.entity.EmployeeEntity; +import net.lab1024.sa.admin.module.system.employee.service.EmployeeService; +import net.lab1024.sa.admin.module.system.position.service.PositionService; +import net.lab1024.sa.admin.module.system.role.domain.form.RoleQueryForm; +import net.lab1024.sa.admin.module.system.role.domain.vo.RoleVO; +import net.lab1024.sa.admin.module.system.role.service.RoleEmployeeService; +import net.lab1024.sa.admin.module.system.role.service.RoleService; +import net.lab1024.sa.base.common.util.SmartRequestUtil; +import org.dromara.warm.flow.core.dto.FlowParams; +import org.dromara.warm.flow.core.handler.PermissionHandler; +import org.dromara.warm.flow.core.service.impl.TaskServiceImpl; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 办理人权限处理器 + * + * @author AprilWind + */ +@RequiredArgsConstructor +@Component +@Slf4j +public class WorkflowPermissionHandler implements PermissionHandler { + @Resource + private EmployeeService userService; + @Resource + private RoleEmployeeService roleEmployeeService; + @Resource + private PositionService positionService; + /** + * 审批前获取当前办理人,办理时会校验的该权限集合 + * 返回当前用户权限集合 + */ + @Override + public List permissions() { + long userId = SmartRequestUtil.getRequestUserId(); + EmployeeEntity loginUser = userService.getById(userId); + if (ObjectUtil.isNull(loginUser)) { + return new ArrayList<>(); + } + List roleList = roleEmployeeService.getRoleIdList(userId); + // 使用一个流来构建权限列表 + return Stream.of( + // 角色权限前缀 + roleList.stream() + .map(role -> TaskAssigneeEnum.ROLE.getCode() + role.getRoleId()), + + // 岗位和部门权限前缀 + Stream.ofNullable(loginUser.getDepartmentId()) + .map(post -> TaskAssigneeEnum.POST.getCode() + post), + + // 用户和部门权限 + Stream.of(String.valueOf(loginUser.getEmployeeId()), + TaskAssigneeEnum.DEPT.getCode() + loginUser.getEmployeeId() + ), + // 诉求办理岗位权限(处理空指针) + Optional.ofNullable(loginUser.getPositionIdList()) + .filter(positionIds -> positionIds != null && !positionIds.isEmpty()) + .map(positionIds -> + Arrays.stream(positionIds.split(",")) + .map(positionId -> TaskAssigneeEnum.POST.getCode() + positionId) + ) + .orElse(Stream.empty()) + ) + .flatMap(stream -> stream) + .collect(Collectors.toList()); + } + + /** + * 获取当前办理人 + * + * @return 当前办理人 + */ + @Override + public String getHandler() { + return String.valueOf(SmartRequestUtil.getRequestUserId()); + } + + @Override + public List getHandlerList() { + return permissions(); + } + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/listener/WorkflowGlobalListener.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/listener/WorkflowGlobalListener.java new file mode 100644 index 0000000..85ca909 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/listener/WorkflowGlobalListener.java @@ -0,0 +1,135 @@ +package net.lab1024.sa.admin.module.flow.listener; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import jakarta.annotation.Resource; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.admin.module.flow.annoation.ConditionalOnEnable; +import net.lab1024.sa.admin.module.flow.enums.BusinessStatusEnum; +import net.lab1024.sa.admin.module.flow.handler.FlowProcessEventHandler; +import net.lab1024.sa.admin.module.flow.service.FlowInstanceService; +import net.lab1024.sa.admin.module.flow.service.FlowTaskService; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import org.dromara.warm.flow.core.dto.FlowParams; +import org.dromara.warm.flow.core.entity.Definition; +import org.dromara.warm.flow.core.entity.Instance; +import org.dromara.warm.flow.core.entity.Task; +import org.dromara.warm.flow.core.listener.GlobalListener; +import org.dromara.warm.flow.core.listener.ListenerVariable; +import org.dromara.warm.flow.core.service.InsService; +import org.dromara.warm.flow.orm.entity.FlowTask; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 全局任务办理监听 + * + * @author may + */ +@Component +@Slf4j +@RequiredArgsConstructor +public class WorkflowGlobalListener implements GlobalListener { + + @Resource + private FlowTaskService flowTaskService; + + @Resource + private FlowInstanceService flowInstanceService; + + private final FlowProcessEventHandler flowProcessEventHandler; + + /** + * 创建监听器,任务创建时执行 + * + * @param listenerVariable 监听器变量 + */ + @Override + public void create(ListenerVariable listenerVariable) { + Instance instance = listenerVariable.getInstance(); + Definition definition = listenerVariable.getDefinition(); + String businessId = instance.getBusinessId(); + String flowStatus = instance.getFlowStatus(); + Task task = listenerVariable.getTask(); + if (task != null && BusinessStatusEnum.WAITING.getStatus().equals(flowStatus)) { + // 判断流程状态(发布审批中事件) + flowProcessEventHandler.processCreateTaskHandler(definition.getFlowCode(), task.getNodeCode(), task.getId(), businessId); + } + } + + /** + * 开始监听器,任务开始办理时执行 + * + * @param listenerVariable 监听器变量 + */ + @Override + public void start(ListenerVariable listenerVariable) { + } + + /** + * 分派监听器,动态修改代办任务信息 + * + * @param listenerVariable 监听器变量 + */ + @Override + public void assignment(ListenerVariable listenerVariable) { + } + + /** + * 完成监听器,当前任务完成后执行 + * + * @param listenerVariable 监听器变量 + */ + @Override + public void finish(ListenerVariable listenerVariable) { + Instance instance = listenerVariable.getInstance(); + Definition definition = listenerVariable.getDefinition(); + String businessId = instance.getBusinessId(); + String flowStatus = instance.getFlowStatus(); + Map params = new HashMap<>(); + FlowParams flowParams = listenerVariable.getFlowParams(); + if (ObjectUtil.isNotNull(flowParams)) { + // 历史任务扩展(通常为附件) + params.put("hisTaskExt", flowParams.getHisTaskExt()); + // 办理人 + params.put("handler", flowParams.getHandler()); + // 办理意见 + params.put("message", flowParams.getMessage()); + } + // 判断流程状态(发布:撤销,退回,作废,终止,已完成事件) + String status = determineFlowStatus(instance, flowStatus); + if (SmartStringUtil.isNotBlank(status)) { + flowProcessEventHandler.processHandler(definition.getFlowCode(), businessId, status, params, false); + } + } + + /** + * 根据流程实例和当前流程状态确定最终状态 + * + * @param instance 流程实例 + * @param flowStatus 流程实例当前状态 + * @return 流程最终状态 + */ + private String determineFlowStatus(Instance instance, String flowStatus) { + if (SmartStringUtil.isNotBlank(flowStatus) && BusinessStatusEnum.initialState(flowStatus)) { + log.info("流程实例当前状态: {}", flowStatus); + return flowStatus; + } else { + Long instanceId = instance.getId(); + List flowTasks = flowTaskService.selectByInstId(instanceId); +// if (CollUtil.isEmpty(flowTasks)) { +//// String status = BusinessStatusEnum.FINISH.getStatus(); +// // 更新流程状态为已完成 +//// flowInstanceService.updateStatus(instanceId, status); +// log.info("流程已结束,状态更新为: {}", status); +// return status; +// } + return null; + } + } + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/mapper/WarmFlowMapper.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/mapper/WarmFlowMapper.java new file mode 100644 index 0000000..428ee9d --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/mapper/WarmFlowMapper.java @@ -0,0 +1,54 @@ +package net.lab1024.sa.admin.module.flow.mapper; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import net.lab1024.sa.admin.module.flow.domain.vo.FlowTaskVO; +import net.lab1024.sa.admin.module.system.employee.domain.entity.EmployeeEntity; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.dromara.warm.flow.core.entity.HisTask; +import org.dromara.warm.flow.core.entity.Task; +import org.dromara.warm.flow.orm.entity.FlowHisTask; +import org.dromara.warm.flow.orm.entity.FlowTask; + +import java.util.List; + +/** + * warm-flow工作流Mapper接口 + * + * @author ruoyi + * @date 2024-03-07 + */ +@Mapper +public interface WarmFlowMapper { + /** + * 分页查询待办任务 + * + * @param task 条件实体 + */ + IPage toDoPage(IPage page, @Param("task") Task task); + + /** + * 获取最新的已办任务 + * + * @param hisTask + * @return + */ + IPage donePage(IPage page, @Param("hisTask") HisTask hisTask); + + /** + * 分页获取抄送任务 + * @param flowTask + * @return + */ + IPage copyPage(IPage page, @Param("task") FlowTask flowTask); + + /** + * 根据ID反显姓名 + * + * @param ids 需要反显姓名的用户ID + * @return {@link List} + * @author liangli + * @date 2024/8/21 17:11 + **/ + List idReverseDisplayName(@Param("ids") Long[] ids); +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/service/FlowDefinitionService.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/service/FlowDefinitionService.java new file mode 100644 index 0000000..9294b8c --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/service/FlowDefinitionService.java @@ -0,0 +1,132 @@ +package net.lab1024.sa.admin.module.flow.service; + +import cn.hutool.core.convert.Convert; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import jakarta.annotation.Resource; +import net.lab1024.sa.admin.module.flow.dao.FlowDefinitionDao; +import net.lab1024.sa.admin.module.flow.domain.form.FlowDefinitionAddForm; +import net.lab1024.sa.admin.module.flow.domain.form.FlowDefinitionQueryForm; +import net.lab1024.sa.admin.module.flow.domain.form.FlowDefinitionUpdateForm; +import net.lab1024.sa.admin.module.flow.domain.vo.FlowDefinitionVO; +import net.lab1024.sa.admin.module.flow.domain.vo.FlowTaskVO; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.dromara.warm.flow.core.entity.Definition; +import org.dromara.warm.flow.core.enums.PublishStatus; +import org.dromara.warm.flow.core.service.DefService; +import org.dromara.warm.flow.core.utils.page.Page; +import org.dromara.warm.flow.orm.entity.FlowDefinition; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 职务表 Service + * + * @Author kaiyun + * @Date 2024-06-23 23:31:38 + * @Copyright 1024创新实验室 + */ + +@Service +public class FlowDefinitionService { + + @Resource + private DefService defService; + + @Resource + private FlowDefinitionDao flowDefinitionDao; + + /** + * 分页查询 + * + * @param queryForm + * @return + */ + public PageResult queryPage(FlowDefinitionQueryForm queryForm) { + FlowDefinition flowDefinition = SmartBeanUtil.copy(queryForm, FlowDefinition.class); + Page page = Page.pageOf(queryForm.getPageNum().intValue(), queryForm.getPageSize().intValue()); + page = defService.orderByCreateTime().desc().page(flowDefinition, page); + // 转换为自定义分页响应对象 + PageResult pageResult = new PageResult<>(); + pageResult.setList(SmartBeanUtil.copyList(page.getList(), FlowDefinitionVO.class)); + pageResult.setTotal(page.getTotal()); + pageResult.setPageNum( (long)page.getPageNum()); + pageResult.setPageSize( page.getTotal()); + pageResult.setPages( (long)page.getPageSize()); + return pageResult; + } + + /** + * 添加 + */ + public ResponseDTO add(FlowDefinitionAddForm addForm) { + FlowDefinition flowDefinition = SmartBeanUtil.copy(addForm, FlowDefinition.class); + flowDefinitionDao.insert(flowDefinition); + return ResponseDTO.ok(); + } + + /** + * 更新 + * + * @param updateForm + * @return + */ + public ResponseDTO update(FlowDefinitionUpdateForm updateForm) { + FlowDefinition flowDefinition = SmartBeanUtil.copy(updateForm, FlowDefinition.class); + flowDefinitionDao.updateById(flowDefinition); + return ResponseDTO.ok(); + } + + /** + * 批量删除 + * + * @param idList + * @return + */ + public ResponseDTO batchDelete(List idList) { + if (CollectionUtils.isEmpty(idList)){ + return ResponseDTO.ok(); + } + + flowDefinitionDao.deleteBatchIds(idList); + return ResponseDTO.ok(); + } + + /** + * 单个删除 + */ + public ResponseDTO delete(Long positionId) { + if (null == positionId){ + return ResponseDTO.ok(); + } + + flowDefinitionDao.deleteById(positionId); + return ResponseDTO.ok(); + } + + /** + * 分页查询 + * + * @return + */ + public List queryList() { + List list = flowDefinitionDao.queryList(Boolean.FALSE); + return list; + } + + private LambdaQueryWrapper buildQueryWrapper(FlowDefinition flowDefinition) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.like(StringUtils.isNotBlank(flowDefinition.getFlowCode()), FlowDefinition::getFlowCode, flowDefinition.getFlowCode()); + wrapper.like(StringUtils.isNotBlank(flowDefinition.getFlowName()), FlowDefinition::getFlowName, flowDefinition.getFlowName()); + wrapper.like(StringUtils.isNotBlank(flowDefinition.getCategory()), FlowDefinition::getCategory, flowDefinition.getCategory()); + + wrapper.orderByDesc(FlowDefinition::getCreateTime); + return wrapper; + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/service/FlowInstanceService.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/service/FlowInstanceService.java new file mode 100644 index 0000000..1f65def --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/service/FlowInstanceService.java @@ -0,0 +1,78 @@ +package net.lab1024.sa.admin.module.flow.service; +import net.lab1024.sa.admin.module.flow.domain.vo.FlowInstanceVO; +import org.dromara.warm.flow.orm.entity.FlowInstance; +import java.util.List; +import java.util.Map; + +/** + * 流程实例 服务层 + * + * @author may + */ +public interface FlowInstanceService { + + + /** + * 根据业务id查询流程实例详细信息 + * + * @param businessId 业务id + * @return 结果 + */ + FlowInstanceVO queryByBusinessId(Long businessId); + + /** + * 按照业务id查询流程实例 + * + * @param businessId 业务id + * @return 结果 + */ + FlowInstance selectInstByBusinessId(String businessId); + + /** + * 按照实例id查询流程实例 + * + * @param instanceId 实例id + * @return 结果 + */ + FlowInstance selectInstById(Long instanceId); + + /** + * 按照实例id查询流程实例 + * + * @param instanceIds 实例id + * @return 结果 + */ + List selectInstListByIdList(List instanceIds); + + /** + * 按照业务id删除流程实例 + * + * @param businessIds 业务id + * @return 结果 + */ + boolean deleteByBusinessIds(List businessIds); + + /** + * 按照实例id删除流程实例 + * + * @param instanceIds 实例id + * @return 结果 + */ + boolean deleteByInstanceIds(List instanceIds); + + /** + * 按照实例id更新状态 + * + * @param instanceId 实例id + * @param status 状态 + */ + void updateStatus(Long instanceId, String status); + + /** + * 设置流程变量 + * + * @param instanceId 实例id + * @param variable 流程变量 + */ + void setVariable(Long instanceId, Map variable); +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/service/FlowTaskService.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/service/FlowTaskService.java new file mode 100644 index 0000000..439a9b9 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/service/FlowTaskService.java @@ -0,0 +1,72 @@ +package net.lab1024.sa.admin.module.flow.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import net.lab1024.sa.admin.module.flow.domain.form.ApproveTaskInsForm; +import net.lab1024.sa.admin.module.flow.domain.form.CompleteTaskForm; +import net.lab1024.sa.admin.module.flow.domain.form.StartProcessForm; +import net.lab1024.sa.admin.module.flow.domain.vo.FlowTaskVO; +import net.lab1024.sa.admin.module.flow.domain.vo.StartProcessReturnVO; +import net.lab1024.sa.admin.module.system.employee.domain.entity.EmployeeEntity; +import org.dromara.warm.flow.core.dto.FlowParams; +import org.dromara.warm.flow.core.entity.HisTask; +import org.dromara.warm.flow.core.entity.Task; +import org.dromara.warm.flow.orm.entity.FlowHisTask; +import org.dromara.warm.flow.orm.entity.FlowTask; + +import java.util.List; + +/** + * 流程执行service + * + * @author warm + * @since 2023/5/29 13:09 + */ +public interface FlowTaskService { + + /** + * 分页查询待办任务 + * + * @param task 条件实体 + * @return + */ + IPage toDoPage(IPage page, Task task); + + /** + * 获取已办任务 + * + * @param hisTask + * @return + */ + IPage donePage(IPage page, HisTask hisTask); + + IPage copyPage(IPage page, FlowTask flowTask); + + /** + * 根据ID反显姓名 + * + * @param ids 需要反显姓名的用户ID + * @return {@link List} + * @author liangli + * @date 2024/8/21 17:11 + **/ + List idReverseDisplayName(Long[] ids); + + StartProcessReturnVO startWorkFlow(StartProcessForm startProcessForm); + + boolean completeTask(CompleteTaskForm completeTaskForm); + + boolean rejectLastTask(Long taskId, FlowParams flowParams); + + List selectByInstId(Long instanceId); + + boolean approveTaskByInstance(ApproveTaskInsForm approveTaskInsForm); + + + FlowTaskVO getTaskDetailById(Long taskId); + + FlowTaskVO getDoneTaskDetailById(Long taskId); + + List queryActiveTask(Long instanceId); + + int setNextApprUser(Long instanceId, String employeeId); +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/service/HhDefService.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/service/HhDefService.java new file mode 100644 index 0000000..d1f18a9 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/service/HhDefService.java @@ -0,0 +1,14 @@ +package net.lab1024.sa.admin.module.flow.service; + +import net.lab1024.sa.admin.module.flow.domain.vo.WarmFlowInteractiveTypeVO; + +/** + * 流程定义service + * + * @author warm + * @since 2023/5/29 13:09 + */ +public interface HhDefService { + + Boolean interactiveType(WarmFlowInteractiveTypeVO warmFlowInteractiveTypeVo); +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/service/impl/FlowInstanceServiceImpl.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/service/impl/FlowInstanceServiceImpl.java new file mode 100644 index 0000000..5a2f83a --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/service/impl/FlowInstanceServiceImpl.java @@ -0,0 +1,189 @@ +package net.lab1024.sa.admin.module.flow.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.admin.module.flow.annoation.ConditionalOnEnable; +import net.lab1024.sa.admin.module.flow.domain.vo.FlowInstanceVO; +import net.lab1024.sa.admin.module.flow.handler.FlowProcessEventHandler; +import net.lab1024.sa.admin.module.flow.service.FlowInstanceService; +import net.lab1024.sa.admin.module.flow.service.FlowTaskService; +import net.lab1024.sa.base.common.exception.BusinessException; +import org.dromara.warm.flow.core.FlowEngine; +import org.dromara.warm.flow.core.constant.ExceptionCons; +import org.dromara.warm.flow.core.dto.FlowParams; +import org.dromara.warm.flow.core.entity.Definition; +import org.dromara.warm.flow.core.entity.Instance; +import org.dromara.warm.flow.core.entity.Task; +import org.dromara.warm.flow.core.enums.NodeType; +import org.dromara.warm.flow.core.service.ChartService; +import org.dromara.warm.flow.core.service.DefService; +import org.dromara.warm.flow.core.service.InsService; +import org.dromara.warm.flow.core.service.TaskService; +import org.dromara.warm.flow.core.utils.StreamUtils; +import org.dromara.warm.flow.orm.entity.FlowHisTask; +import org.dromara.warm.flow.orm.entity.FlowInstance; +import org.dromara.warm.flow.orm.entity.FlowTask; +import org.dromara.warm.flow.orm.mapper.FlowHisTaskMapper; +import org.dromara.warm.flow.orm.mapper.FlowInstanceMapper; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * 流程实例 服务层实现 + * + * @author may + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class FlowInstanceServiceImpl implements FlowInstanceService { + + private final InsService insService; + private final DefService defService; + private final ChartService chartService; + private final TaskService taskService; + private final FlowHisTaskMapper flowHisTaskMapper; + private final FlowInstanceMapper flowInstanceMapper; + private final FlowProcessEventHandler flowProcessEventHandler; + private final FlowTaskService flowTaskService; + + + /** + * 根据业务id查询流程实例详细信息 + * + * @param businessId 业务id + * @return 结果 + */ + @Override + public FlowInstanceVO queryByBusinessId(Long businessId) { + FlowInstance instance = this.selectInstByBusinessId(String.valueOf(businessId)); + FlowInstanceVO instanceVo = BeanUtil.toBean(instance, FlowInstanceVO.class); + Definition definition = defService.getById(instanceVo.getDefinitionId()); + instanceVo.setFlowName(definition.getFlowName()); + instanceVo.setFlowCode(definition.getFlowCode()); + instanceVo.setVersion(definition.getVersion()); + instanceVo.setFormCustom(definition.getFormCustom()); + instanceVo.setFormPath(definition.getFormPath()); + instanceVo.setCategory(definition.getCategory()); + return instanceVo; + } + + /** + * 根据业务id查询流程实例 + * + * @param businessId 业务id + */ + @Override + public FlowInstance selectInstByBusinessId(String businessId) { + return flowInstanceMapper.selectOne(new LambdaQueryWrapper().eq(FlowInstance::getBusinessId, businessId)); + } + + /** + * 按照实例id查询流程实例 + * + * @param instanceId 实例id + */ + @Override + public FlowInstance selectInstById(Long instanceId) { + return flowInstanceMapper.selectById(instanceId); + } + + /** + * 按照实例id查询流程实例 + * + * @param instanceIds 实例id + */ + @Override + public List selectInstListByIdList(List instanceIds) { + return flowInstanceMapper.selectBatchIds(instanceIds); + } + + /** + * 按照业务id删除流程实例 + * + * @param businessIds 业务id + */ + @Override + @Transactional(rollbackFor = Exception.class) + public boolean deleteByBusinessIds(List businessIds) { + List flowInstances = flowInstanceMapper.selectList(new LambdaQueryWrapper().in(FlowInstance::getBusinessId, StreamUtils.toList(businessIds,Convert::toStr))); + if (CollUtil.isEmpty(flowInstances)) { + log.warn("未找到对应的流程实例信息,无法执行删除操作。"); + return false; + } + return insService.remove(StreamUtils.toList(flowInstances, FlowInstance::getId)); + } + + /** + * 按照实例id删除流程实例 + * + * @param instanceIds 实例id + */ + @Override + @Transactional(rollbackFor = Exception.class) + public boolean deleteByInstanceIds(List instanceIds) { + // 获取实例信息 + List instances = insService.getByIds(instanceIds); + if (CollUtil.isEmpty(instances)) { + log.warn("未找到对应的流程实例信息,无法执行删除操作。"); + return false; + } + // 获取定义信息 + Map definitionMap = defService.getByIds( + StreamUtils.toList(instances, Instance::getDefinitionId) + ).stream().collect(Collectors.toMap(Definition::getId, definition -> definition)); + + // 逐一触发删除事件 + instances.forEach(instance -> { + Definition definition = definitionMap.get(instance.getDefinitionId()); + if (ObjectUtil.isNull(definition)) { + log.warn("实例 ID: {} 对应的流程定义信息未找到,跳过删除事件触发。", instance.getId()); + return; + } + flowProcessEventHandler.processDeleteHandler(definition.getFlowCode(), instance.getBusinessId()); + }); + + // 删除实例 + return insService.remove(instanceIds); + } + + /** + * 按照实例id更新状态 + * + * @param instanceId 实例id + * @param status 状态 + */ + @Override + public void updateStatus(Long instanceId, String status) { + LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); + wrapper.set(FlowInstance::getFlowStatus, status); + wrapper.eq(FlowInstance::getId, instanceId); + flowInstanceMapper.update(wrapper); + } + /** + * 设置流程变量 + * + * @param instanceId 实例id + * @param variable 流程变量 + */ + @Override + public void setVariable(Long instanceId, Map variable) { + Instance instance = insService.getById(instanceId); + if (instance != null) { + taskService.mergeVariable(instance, variable); + insService.updateById(instance); + } + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/service/impl/FlowTaskServiceImpl.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/service/impl/FlowTaskServiceImpl.java new file mode 100644 index 0000000..72905d5 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/service/impl/FlowTaskServiceImpl.java @@ -0,0 +1,375 @@ +package net.lab1024.sa.admin.module.flow.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.json.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import jakarta.annotation.Resource; +import lombok.RequiredArgsConstructor; +import net.lab1024.sa.admin.module.flow.domain.form.ApproveTaskInsForm; +import net.lab1024.sa.admin.module.flow.domain.form.CompleteTaskForm; +import net.lab1024.sa.admin.module.flow.domain.form.StartProcessForm; +import net.lab1024.sa.admin.module.flow.domain.vo.FlowCopyVO; +import net.lab1024.sa.admin.module.flow.domain.vo.FlowTaskVO; +import net.lab1024.sa.admin.module.flow.domain.vo.StartProcessReturnVO; +import net.lab1024.sa.admin.module.flow.enums.BusinessStatusEnum; +import net.lab1024.sa.admin.module.flow.handler.FlowProcessEventHandler; +import net.lab1024.sa.admin.module.flow.mapper.WarmFlowMapper; +import net.lab1024.sa.admin.module.flow.service.FlowTaskService; +import net.lab1024.sa.admin.module.system.employee.domain.entity.EmployeeEntity; +import net.lab1024.sa.admin.module.system.employee.service.EmployeeService; +import net.lab1024.sa.base.common.exception.BusinessException; +import net.lab1024.sa.base.common.util.*; +import org.dromara.warm.flow.core.dto.FlowParams; +import org.dromara.warm.flow.core.entity.Definition; +import org.dromara.warm.flow.core.entity.HisTask; +import org.dromara.warm.flow.core.entity.Instance; +import org.dromara.warm.flow.core.entity.Task; +import org.dromara.warm.flow.core.enums.SkipType; +import org.dromara.warm.flow.core.service.DefService; +import org.dromara.warm.flow.core.service.InsService; +import org.dromara.warm.flow.core.service.TaskService; +import org.dromara.warm.flow.orm.entity.FlowHisTask; +import org.dromara.warm.flow.orm.entity.FlowInstance; +import org.dromara.warm.flow.orm.entity.FlowTask; +import org.dromara.warm.flow.orm.mapper.FlowHisTaskMapper; +import org.dromara.warm.flow.orm.mapper.FlowInstanceMapper; +import org.dromara.warm.flow.orm.mapper.FlowTaskMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static net.lab1024.sa.admin.module.flow.contstant.FlowConstant.*; + +/** + * 流程执行SERVICEIMPL + * + * @author WARM + * @since 2023/5/29 13:09 + */ +@Service +@RequiredArgsConstructor +public class FlowTaskServiceImpl implements FlowTaskService { + + @Resource + private WarmFlowMapper flowMapper; + @Resource + private TaskService taskService; + @Resource + private InsService insService; + @Resource + private DefService defService; + @Resource + private FlowInstanceMapper flowInstanceMapper; + @Resource + private FlowTaskMapper flowTaskMapper; + @Resource + private FlowProcessEventHandler flowProcessEventHandler; + @Resource + private EmployeeService employeeService; + @Autowired + private FlowHisTaskMapper flowHisTaskMapper; + + @Override + public IPage toDoPage(IPage page, Task task) { + return flowMapper.toDoPage(page,task); + } + + @Override + public IPage donePage(IPage page, HisTask hisTask) { + return flowMapper.donePage(page,hisTask); + } + + @Override + public IPage copyPage(IPage page, FlowTask flowTask) { + return flowMapper.copyPage(page,flowTask); + } + + /** + * 根据ID反显姓名 + * + * @param ids 需要反显姓名的用户ID + * @return {@link List} + * @author liangli + * @date 2024/8/21 17:11 + **/ + @Override + public List idReverseDisplayName(Long[] ids) { + if (Objects.isNull(ids) || ids.length == 0) { + return null; + } + return flowMapper.idReverseDisplayName(ids); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public StartProcessReturnVO startWorkFlow(StartProcessForm startProcessForm) { + String businessId = startProcessForm.getBusinessId(); + if (SmartStringUtil.isEmpty(businessId)) { + throw new BusinessException("启动工作流时必须包含业务ID"); + } + // 启动流程实例(提交申请) + Map variables = startProcessForm.getVariables(); + // 流程发起人 + long userId = SmartRequestUtil.getRequestUserId(); + variables.put(INITIATOR, String.valueOf(userId)); + // 业务id + variables.put(BUSINESS_ID, businessId); + variables.put(BUSINESS_TABLE,startProcessForm.getFlowCode()); + FlowInstance flowInstance = flowInstanceMapper.selectOne(new LambdaQueryWrapper<>(FlowInstance.class) + .eq(FlowInstance::getBusinessId, businessId)); + if (ObjectUtil.isNotNull(flowInstance)) { + BusinessStatusEnum.checkStartStatus(flowInstance.getFlowStatus()); + List taskList = taskService.list(new FlowTask().setInstanceId(flowInstance.getId())); + taskService.mergeVariable(flowInstance, variables); + insService.updateById(flowInstance); + StartProcessReturnVO vo = new StartProcessReturnVO(); + vo.setProcessInstanceId(taskList.get(0).getInstanceId()); + vo.setTaskId(taskList.get(0).getId()); + return vo; + } + FlowParams flowParams = new FlowParams(); + flowParams.flowCode(startProcessForm.getFlowCode()); + flowParams.variable(startProcessForm.getVariables()); +// flowParams.flowStatus(String.valueOf(FlowStatusEnum.PENDING_SUBMIT.getValue())); + Instance instance; + try { + instance = insService.start(businessId, flowParams); + } catch (Exception e) { + throw new BusinessException(e.getMessage()); + } + //更新business表中状态 +// String name = flowParams.getFlowCode(); +// BaseMapper bizMapper = SpringUtils.getBean(flowParams.getFlowCode()+"Dao"); +// if (ObjectUtil.isNotNull(bizMapper)) { +// UpdateWrapper updateWrapper = new UpdateWrapper<>(); +// // 动态设置更新条件(假设字段名与实体属性对应) +// updateWrapper.eq(name.toLowerCase() +"_id", instance.getBusinessId()) // 主键条件 +// .set("instance_id", instance.getId()) +// .set("node_name",instance.getNodeName())// 更新字段 +// .set("flow_status", instance.getFlowStatus()); +// bizMapper.update(null, updateWrapper); +// } + // 申请人执行流程 + List taskList = taskService.list(new FlowTask().setInstanceId(instance.getId())); + if (taskList.size() > 1) { + throw new BusinessException("请检查流程第一个环节是否为申请人!"); + } + //自动提交申请人流程 + ApproveTaskInsForm approveTaskInsForm = new ApproveTaskInsForm(); + approveTaskInsForm.setInstanceId(instance.getId()); + approveTaskInsForm.setVariables(variables); + approveTaskByInstance( approveTaskInsForm); + //返回执行结果 + StartProcessReturnVO vo = new StartProcessReturnVO(); + vo.setProcessInstanceId(instance.getId()); + vo.setTaskId(taskList.get(0).getId()); + return vo; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean completeTask(CompleteTaskForm completeTaskForm) { + return skipTask(completeTaskForm, SkipType.PASS.getKey()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean rejectLastTask(Long taskId, FlowParams flowParams) { + try { + flowParams.skipType(SkipType.REJECT.getKey()); + Map variables = flowParams.getVariable(); + taskService.rejectLast(taskId, flowParams); + }catch (Exception e){ + return false; + } + return true; + } + + + public boolean skipTask(CompleteTaskForm completeTaskForm, String skipType) { + try { + // 获取任务ID并查询对应的流程任务和实例信息 + Long taskId = completeTaskForm.getTaskId(); + List messageType = completeTaskForm.getMessageType(); + String notice = completeTaskForm.getNotice(); + // 获取抄送人 + List flowCopyList = completeTaskForm.getFlowCopyList(); + FlowTask flowTask = flowTaskMapper.selectById(taskId); + if (ObjectUtil.isNull(flowTask)) { + throw new BusinessException("流程任务不存在或任务已审批!"); + } + Instance ins = insService.getById(flowTask.getInstanceId()); + // 获取流程定义信息 + Definition definition = defService.getById(flowTask.getDefinitionId()); + // 检查流程状态是否为草稿、已撤销或已退回状态,若是则执行流程提交监听 + if (BusinessStatusEnum.isDraftOrCancelOrBack(ins.getFlowStatus())) { + flowProcessEventHandler.processHandler(definition.getFlowCode(), ins.getBusinessId(), ins.getFlowStatus(), null, true); + } + // 构建流程参数,包括变量、跳转类型、消息、处理人、权限等信息 + FlowParams flowParams = new FlowParams(); + flowParams.skipType(skipType); + flowParams.message(completeTaskForm.getMessage()); + flowParams.hisTaskExt(completeTaskForm.getFileId()); + Map variables = completeTaskForm.getVariables(); + flowParams.variable(variables); + flowParams.flowCode(variables.get("flowCode").toString()); + // 执行任务跳转,并根据返回的处理人设置下一步处理人 + Instance instance = taskService.skip(taskId, flowParams); +// this.setHandler(instance, flowTask, flowCopyList); + + //设置下一环节处理人 +// setNextHandler(ins.getId()); + //更新business表中状态 +// String name = flowParams.getFlowCode(); +// BaseMapper bizMapper = SpringUtils.getBean(flowParams.getFlowCode()+"Dao"); +// if (ObjectUtil.isNotNull(bizMapper)) { +// UpdateWrapper updateWrapper = new UpdateWrapper<>(); +// // 动态设置更新条件(假设字段名与实体属性对应) +// updateWrapper.eq(name.toLowerCase() +"_id", instance.getBusinessId()) // 主键条件 +// .set("node_name",instance.getNodeName())// 更新字段 +// .set("flow_status", instance.getFlowStatus()); +// bizMapper.update(null, updateWrapper); +// } + return true; + } catch (Exception e) { + throw new BusinessException(e.getMessage()); + } + } + /** + * 按照实例id审批 + * + * @param approveTaskInsForm 流程审批实例审批 + */ + @Override + public boolean approveTaskByInstance(ApproveTaskInsForm approveTaskInsForm){ + List flowTaskList =selectByInstId(approveTaskInsForm.getInstanceId()); + CompleteTaskForm completeTaskForm = new CompleteTaskForm(); + SmartBeanUtil.copyProperties(approveTaskInsForm,completeTaskForm); + + if(flowTaskList.size()>0){ + completeTaskForm.setTaskId(flowTaskList.get(0).getId()); + } + return completeTask(completeTaskForm); + } + + /** + * 按照实例id查询任务 + * + * @param instanceId 流程实例id + */ + @Override + public List selectByInstId(Long instanceId) { + return flowTaskMapper.selectList(new LambdaQueryWrapper<>(FlowTask.class) + .eq(FlowTask::getInstanceId, instanceId)); + } + /** + * 按照任务id查询任务详情 + * + * @param taskId 流程实例id + */ + @Override + public FlowTaskVO getTaskDetailById(Long taskId){ + FlowTask flowTask = flowTaskMapper.selectById(taskId); + Definition definition = defService.getById(flowTask.getDefinitionId()); + Instance flowInstance = insService.getById(flowTask.getInstanceId()); + Map variable = flowInstance.getVariableMap(); + String flowCode = variable.get("flowCode").toString(); + String initiatorId = variable.get("initiator").toString(); + String initiator = employeeService.getById(Long.parseLong(initiatorId)).getActualName(); + + // 暂时不获取表元数据,避免复杂的依赖问题 + JSONObject businessFormMetaData = new JSONObject(); + try { + String tableName = getTableNameByFlowCode(flowCode); + if (tableName != null) { + businessFormMetaData = SmartTableMetadataUtil.getTableMetadata(tableName); + } + } catch (Exception e) { + System.err.println("获取表元数据失败: " + e.getMessage()); + } + + FlowTaskVO flowTaskVO = new FlowTaskVO(); + SmartBeanUtil.copyProperties(flowTask,flowTaskVO); + flowTaskVO.setFlowCode(definition.getFlowCode()); + flowTaskVO.setFlowName(definition.getFlowName()); + flowTaskVO.setBusinessId(flowInstance.getBusinessId()); + flowTaskVO.setInitiator(initiator); + flowTaskVO.setBusinessFormMetaData(businessFormMetaData); + + return flowTaskVO; + } + + /** + * 按照任务id查询任务详情 + * + * @param taskId 流程实例id + */ + @Override + public FlowTaskVO getDoneTaskDetailById(Long taskId){ + FlowHisTask flowHisTask = flowHisTaskMapper.selectById(taskId); + Definition definition = defService.getById(flowHisTask.getDefinitionId()); + Instance flowInstance = insService.getById(flowHisTask.getInstanceId()); + Map variable = flowInstance.getVariableMap(); + String flowCode = variable.get("flowCode").toString(); + String initiatorId = variable.get("initiator").toString(); + String initiator = employeeService.getById(Long.parseLong(initiatorId)).getActualName(); + + BaseMapper bizMapper = SpringUtils.getBean(flowCode+"Dao"); + Map businessForm = new HashMap<>(); + if (ObjectUtil.isNotNull(bizMapper)) { + Object businessEntity = bizMapper.selectById(flowInstance.getBusinessId()); + if (ObjectUtil.isNotNull(businessEntity)) { + // 将业务实体转换为Map + businessForm = BeanUtil.beanToMap(businessEntity); + } + } + // 暂时不获取表元数据,避免复杂的依赖问题 + JSONObject businessFormMetaData = new JSONObject(); + try { + String tableName = getTableNameByFlowCode(flowCode); + if (tableName != null) { + businessFormMetaData = SmartTableMetadataUtil.getTableMetadata(tableName); + } + } catch (Exception e) { + System.err.println("获取表元数据失败: " + e.getMessage()); + } + + FlowTaskVO flowTaskVO = new FlowTaskVO(); + SmartBeanUtil.copyProperties(flowHisTask,flowTaskVO); + flowTaskVO.setFlowName(definition.getFlowName()); + flowTaskVO.setBusinessId(flowInstance.getBusinessId()); + flowTaskVO.setInitiator(initiator); + flowTaskVO.setBusinessFormMetaData(businessFormMetaData); + flowTaskVO.setBusinessForm( businessForm); + + return flowTaskVO; + } + + @Override + public List queryActiveTask(Long instanceId) { + return flowTaskMapper.queryActiveTask(instanceId); + } + + @Override + public int setNextApprUser(Long instanceId, String employeeId){ + return flowTaskMapper.setNextApprUser(instanceId, employeeId); + } + + /** + * 根据flowCode映射到对应的表名 + * @param flowCode 流程代码 + * @return 表名 + */ + private String getTableNameByFlowCode(String flowCode) { + return "t_"+flowCode; + + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/service/impl/HandlerSelectServiceImpl.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/service/impl/HandlerSelectServiceImpl.java new file mode 100644 index 0000000..603896d --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/service/impl/HandlerSelectServiceImpl.java @@ -0,0 +1,183 @@ +package net.lab1024.sa.admin.module.flow.service.impl; + +import jakarta.annotation.Resource; +import net.lab1024.sa.admin.module.flow.enums.TaskAssigneeEnum; +import net.lab1024.sa.admin.module.system.department.domain.form.DepartmentQueryForm; +import net.lab1024.sa.admin.module.system.department.domain.vo.DepartmentVO; +import net.lab1024.sa.admin.module.system.department.service.DepartmentService; +import net.lab1024.sa.admin.module.system.employee.domain.form.EmployeeQueryForm; +import net.lab1024.sa.admin.module.system.employee.domain.vo.EmployeeVO; +import net.lab1024.sa.admin.module.system.employee.service.EmployeeService; +import net.lab1024.sa.admin.module.system.role.domain.form.RoleQueryForm; +import net.lab1024.sa.admin.module.system.role.domain.vo.RoleVO; +import net.lab1024.sa.admin.module.system.role.service.RoleService; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.util.SmartDateFormatterEnum; +import net.lab1024.sa.base.common.util.SmartLocalDateUtil; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import org.dromara.warm.flow.core.utils.MathUtil; +import org.dromara.warm.flow.ui.dto.HandlerFunDto; +import org.dromara.warm.flow.ui.dto.HandlerQuery; +import org.dromara.warm.flow.ui.dto.TreeFunDto; +import org.dromara.warm.flow.ui.service.HandlerSelectService; +import org.dromara.warm.flow.ui.vo.HandlerSelectVo; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.List; + +/** + * 流程设计器-获取办理人权限设置列表接口实现类 + * + * @author warm + */ +@Component +public class HandlerSelectServiceImpl implements HandlerSelectService { + @Resource + private EmployeeService employeeService; + + @Resource + private RoleService roleService; + + @Resource + private DepartmentService departmentService; + + /** + * 获取办理人权限设置列表tabs页签,如:用户、角色和部门等,可以返回其中一种或者多种,按业务需求决定 + * + * @return tabs页签 + */ + @Override + public List getHandlerType() { + return Arrays.asList("用户", "角色", "部门"); + } + + /** + * 获取用户列表、角色列表、部门列表等,可以返回其中一种或者多种,按业务需求决定 + * + * @param query 查询参数 + * @return 结果 + */ + @Override + public HandlerSelectVo getHandlerSelect(HandlerQuery query) { + + if ("角色".equals(query.getHandlerType())) { + return getRole(query); + } + + if ("部门".equals(query.getHandlerType())) { + return getDept(query); + } + + if ("用户".equals(query.getHandlerType())) { + return getUser(query); + } + + return new HandlerSelectVo(); + } + + /** + * 获取角色列表 + * + * @param query 查询条件 + * @return HandlerSelectVo + */ + private HandlerSelectVo getRole(HandlerQuery query) { + RoleQueryForm queryForm = new RoleQueryForm(); + queryForm.setRoleCode(query.getHandlerCode()); + queryForm.setRoleName(query.getHandlerName()); + if(SmartStringUtil.isNotEmpty(query.getBeginTime())) + queryForm.setStartTime(SmartLocalDateUtil.parseDate(query.getBeginTime(), SmartDateFormatterEnum.YMD)); + if(SmartStringUtil.isNotEmpty(query.getEndTime())) + queryForm.setEndTime(SmartLocalDateUtil.parseDate(query.getEndTime(), SmartDateFormatterEnum.YMD)); + // 处理分页参数,避免空指针异常 + queryForm.setPageNum(query.getPageNum() != null ? (long)query.getPageNum() : 1L); + queryForm.setPageSize(query.getPageSize() != null ? (long)query.getPageSize() : 10L); + // 查询角色列表 + PageResult pageResult = roleService.queryPage(queryForm); + + // 业务系统数据,转成组件内部能够显示的数据, total是业务数据总数,用于分页显示 + HandlerFunDto handlerFunDto = new HandlerFunDto<>(pageResult.getList(), pageResult.getTotal()) + // 以下设置获取内置变量的Function + .setStorageId(role -> TaskAssigneeEnum.ROLE.getCode() + role.getRoleId()) // 前面拼接role: 是为了防止用户、角色的主键重复 + .setHandlerCode(RoleVO::getRoleCode) // 权限编码 + .setHandlerName(RoleVO::getRoleName) // 权限名称 + .setCreateTime(role -> SmartLocalDateUtil.format(role.getCreateTime(), SmartDateFormatterEnum.YMD)); + + return getHandlerSelectVo(handlerFunDto); + } + + /** + * 获取用户列表 + * + * @param query 查询条件 + * @return HandlerSelectVo + */ + private HandlerSelectVo getDept(HandlerQuery query) { + DepartmentQueryForm queryForm = new DepartmentQueryForm(); + queryForm.setName(query.getHandlerName()); + if(SmartStringUtil.isNotEmpty(query.getBeginTime())) + queryForm.setStartTime(SmartLocalDateUtil.parseDate(query.getBeginTime(), SmartDateFormatterEnum.YMD)); + if(SmartStringUtil.isNotEmpty(query.getEndTime())) + queryForm.setEndTime(SmartLocalDateUtil.parseDate(query.getEndTime(), SmartDateFormatterEnum.YMD)); + // 处理分页参数,避免空指针异常 + queryForm.setPageNum(query.getPageNum() != null ? (long)query.getPageNum() : 1L); + queryForm.setPageSize(query.getPageSize() != null ? (long)query.getPageSize() : 10L); + // 查询部门列表 + PageResult deptPage = departmentService.queryPage(queryForm); + // 业务系统数据,转成组件内部能够显示的数据, total是业务数据总数,用于分页显示 + HandlerFunDto handlerFunDto = new HandlerFunDto<>(deptPage.getList(), deptPage.getTotal()) + .setStorageId(dept ->TaskAssigneeEnum.DEPT.getCode() + dept.getDepartmentId()) // 前面拼接dept: 是为了防止用户、部门的主键重复 + .setHandlerName(DepartmentVO::getName) // 权限名称 + .setCreateTime(dept -> SmartLocalDateUtil.format(dept.getCreateTime(), SmartDateFormatterEnum.YMD)); + + return getHandlerSelectVo(handlerFunDto); + + } + + /** + * 获取用户列表, 同时构建左侧部门树状结构 + * + * @param query 查询条件 + * @return HandlerSelectVo + */ + private HandlerSelectVo getUser(HandlerQuery query) { + EmployeeQueryForm employeeQueryForm = new EmployeeQueryForm(); + if (SmartStringUtil.isNotEmpty(query.getHandlerCode())) + employeeQueryForm.setKeyword(query.getHandlerCode()); + else if (SmartStringUtil.isNotEmpty(query.getHandlerName())) + employeeQueryForm.setKeyword(query.getHandlerName()); + // 办理人用户选择列表,需要展示左侧树状部门,所以可能会通过部门id + if (MathUtil.isNumeric(query.getGroupId())) { + employeeQueryForm.setDepartmentId(Long.valueOf(query.getGroupId())); + } + if(SmartStringUtil.isNotEmpty(query.getBeginTime())) + employeeQueryForm.setStartTime(SmartLocalDateUtil.parseDate(query.getBeginTime(), SmartDateFormatterEnum.YMD)); + if(SmartStringUtil.isNotEmpty(query.getEndTime())) + employeeQueryForm.setEndTime(SmartLocalDateUtil.parseDate(query.getEndTime(), SmartDateFormatterEnum.YMD)); + // 处理分页参数,避免空指针异常 + employeeQueryForm.setPageNum(query.getPageNum() != null ? (long)query.getPageNum() : 1L); + employeeQueryForm.setPageSize(query.getPageSize() != null ? (long)query.getPageSize() : 10L); + // 查询用户列表 + PageResult employeeVOPageResult = employeeService.queryEmployeePage(employeeQueryForm); + + // 查询部门列表,构建树状结构 + List deptList = departmentService.listAll(); + + // 业务系统数据,转成组件内部能够显示的数据, total是业务数据总数,用于分页显示 + HandlerFunDto handlerFunDto = new HandlerFunDto<>(employeeVOPageResult.getList(), employeeVOPageResult.getTotal()) + .setStorageId(employee -> employee.getEmployeeId().toString()) + .setHandlerCode(EmployeeVO::getLoginName) // 权限编码 + .setHandlerName(EmployeeVO::getActualName) // 权限名称 + .setCreateTime(employee -> SmartLocalDateUtil.format(employee.getCreateTime(), SmartDateFormatterEnum.YMD_HMS)) + .setGroupName(employee -> employee.getDepartmentName() != null ? employee.getDepartmentName() : ""); + + // 业务系统机构,转成组件内部左侧树列表能够显示的数据 + TreeFunDto treeFunDto = new TreeFunDto<>(deptList) + .setId(dept -> dept.getDepartmentId().toString()) // 左侧树ID + .setName(DepartmentVO::getName) // 左侧树名称 + .setParentId(dept -> dept.getParentId().toString()); // 左侧树父级ID + + return getHandlerSelectVo(handlerFunDto, treeFunDto); + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/service/impl/HhDefServiceImpl.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/service/impl/HhDefServiceImpl.java new file mode 100644 index 0000000..bb34c5b --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/service/impl/HhDefServiceImpl.java @@ -0,0 +1,59 @@ +package net.lab1024.sa.admin.module.flow.service.impl; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; +import net.lab1024.sa.admin.module.flow.adapter.WarmFlowAdapter; +import net.lab1024.sa.admin.module.flow.service.HhDefService; +import net.lab1024.sa.admin.module.flow.domain.vo.WarmFlowInteractiveTypeVO; +import net.lab1024.sa.base.common.util.SpringUtils; +import org.dromara.warm.flow.core.service.DefService; +import org.dromara.warm.flow.core.service.NodeService; +import org.dromara.warm.flow.core.service.SkipService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * 流程定义serviceImpl + * + * @author warm + * @since 2023/5/29 13:09 + */ +@Service +public class HhDefServiceImpl implements HhDefService { + + @Resource + private DefService defService; + + @Resource + private NodeService nodeService; + + @Resource + private SkipService skipService; + + private static final List WARM_FLOW_ADAPTERS = new ArrayList<>(); + + /** + * 初始化方法 + */ + @PostConstruct + public void initMethod() { + Map beanNamesForType = SpringUtils.getBeanNamesForType(WarmFlowAdapter.class); + WARM_FLOW_ADAPTERS.addAll(beanNamesForType.values()); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public Boolean interactiveType(WarmFlowInteractiveTypeVO warmFlowInteractiveTypeVo) { + Integer operatorType = warmFlowInteractiveTypeVo.getOperatorType(); + for (WarmFlowAdapter warmFlowAdapter : WARM_FLOW_ADAPTERS) { + if (warmFlowAdapter.isAdapter(operatorType)) { + return warmFlowAdapter.adapter(warmFlowInteractiveTypeVo); + } + } + return false; + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/service/impl/NodeExtServiceImpl.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/service/impl/NodeExtServiceImpl.java new file mode 100644 index 0000000..7d7e2fb --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/flow/service/impl/NodeExtServiceImpl.java @@ -0,0 +1,136 @@ +package net.lab1024.sa.admin.module.flow.service.impl; + +import org.dromara.warm.flow.ui.service.NodeExtService; +import org.dromara.warm.flow.ui.vo.NodeExt; +import org.dromara.warm.flow.ui.vo.NodeExt.ChildNode; +import org.dromara.warm.flow.ui.vo.NodeExt.DictItem; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +/** + * 流程设计器-节点扩展属性 + * + * @author warm + */ +@Component +public class NodeExtServiceImpl implements NodeExtService { + + @Override + public List getNodeExt() { + List nodeExts = new ArrayList<>(); + +// // 第一个 NodeExt 对象 +// NodeExt nodeExt1 = new NodeExt(); +// nodeExt1.setCode("base"); +// nodeExt1.setDesc("基础设置扩展属性"); +// nodeExt1.setType(1); +// +// List childs1 = new ArrayList<>(); +// +// // 第一个 ChildNode 对象 +// ChildNode childNode1 = new ChildNode(); +// childNode1.setCode("base1"); +// childNode1.setLabel("输入框"); +// childNode1.setDesc("基础设置扩展属性1"); +// childNode1.setType(1); +// childNode1.setMust(false); +// childs1.add(childNode1); +// +// // 第二个 ChildNode 对象 +// ChildNode childNode2 = new ChildNode(); +// childNode2.setCode("base2"); +// childNode2.setLabel("文本域"); +// childNode2.setDesc("基础设置扩展属性2"); +// childNode2.setType(2); +// childNode2.setMust(false); +// childs1.add(childNode2); +// +// // 第三个 ChildNode 对象 +// ChildNode childNode3 = new ChildNode(); +// childNode3.setCode("base3"); +// childNode3.setLabel("下一步角色"); +// childNode3.setDesc("基础设置扩展属性3"); +// childNode3.setType(3); +// childNode3.setMust(false); +// +// List dictItems3 = new ArrayList<>(); +// dictItems3.add(new DictItem("普通角色", "common", true)); +// dictItems3.add(new DictItem("领导", "leader")); +// dictItems3.add(new DictItem("员工", "yuangong")); +// childNode3.setDict(dictItems3); +// childs1.add(childNode3); + +// // 第四个 ChildNode 对象 +// ChildNode childNode4 = new ChildNode(); +// childNode4.setCode("base4"); +// childNode4.setLabel("单选框"); +// childNode4.setDesc("基础设置扩展属性4"); +// childNode4.setType(4); +// childNode4.setMust(false); +// +// List dictItems4 = new ArrayList<>(); +// dictItems4.add(new DictItem("是否弹窗选人", "1", true)); +// dictItems4.add(new DictItem("是否能委托", "2", true)); +// dictItems4.add(new DictItem("是否能转办", "3")); +// dictItems4.add(new DictItem("是否能抄送", "4")); +// dictItems4.add(new DictItem("是否显示退回", "5")); +// dictItems4.add(new DictItem("是否能加签", "6")); +// dictItems4.add(new DictItem("是否能减签", "7")); +// childNode4.setDict(dictItems4); +// childs1.add(childNode4); +// +// nodeExt1.setChilds(childs1); +// nodeExts.add(nodeExt1); + + // 第二个 NodeExt 对象 + NodeExt nodeExt2 = new NodeExt(); + nodeExt2.setCode("new_tabs"); + nodeExt2.setName("按钮权限"); + nodeExt2.setDesc("按钮权限设置"); + nodeExt2.setType(2); + + List childs2 = new ArrayList<>(); + + // 第一个 ChildNode 对象 + ChildNode childNode5 = new ChildNode(); + childNode5.setCode("new_tabs1"); + childNode5.setLabel("复选框"); + childNode5.setDesc("按钮权限1"); + childNode5.setType(4); + childNode5.setMust(false); + childNode5.setMultiple(true); + + List dictItems5 = new ArrayList<>(); + dictItems5.add(new DictItem("是否弹窗选人", "1")); + dictItems5.add(new DictItem("是否能委托", "2")); + dictItems5.add(new DictItem("是否能转办", "3", true)); + dictItems5.add(new DictItem("是否能抄送", "4", true)); + dictItems5.add(new DictItem("是否显示退回", "5")); + dictItems5.add(new DictItem("是否能加签", "6")); + dictItems5.add(new DictItem("是否能减签", "7")); + childNode5.setDict(dictItems5); + childs2.add(childNode5); + + ChildNode childNode6 = new ChildNode(); + childNode6.setCode("new_tabs2"); + childNode6.setLabel("下拉选-多选"); + childNode6.setDesc("基础设置扩展属性3"); + childNode6.setType(3); + childNode6.setMust(false); + childNode6.setMultiple(true); + + List dictItems6 = new ArrayList<>(); + dictItems6.add(new DictItem("选项A", "1", true)); + dictItems6.add(new DictItem("选项B", "2")); + dictItems6.add(new DictItem("选项C", "3")); + childNode6.setDict(dictItems6); + childs2.add(childNode6); + + nodeExt2.setChilds(childs2); + nodeExts.add(nodeExt2); + + return nodeExts; + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/DataScope.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/DataScope.java new file mode 100644 index 0000000..3f7307d --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/DataScope.java @@ -0,0 +1,50 @@ +package net.lab1024.sa.admin.module.system.datascope; + + +import net.lab1024.sa.admin.module.system.datascope.constant.DataScopeTypeEnum; +import net.lab1024.sa.admin.module.system.datascope.constant.DataScopeWhereInTypeEnum; +import net.lab1024.sa.admin.module.system.datascope.strategy.AbstractDataScopeStrategy; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 数据范围 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022-03-18 20:59:17 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface DataScope { + + DataScopeTypeEnum dataScopeType() ; + + DataScopeWhereInTypeEnum whereInType() default DataScopeWhereInTypeEnum.EMPLOYEE; + + /** + * DataScopeWhereInTypeEnum.CUSTOM_STRATEGY类型 才可使用joinSqlImplClazz属性 + */ + Class joinSqlImplClazz() default AbstractDataScopeStrategy.class; + + /** + * 多个参数已逗号分隔,本属性主要用于joinSqlImplClazz 实现类跟进参数进行不同的范围控制,如不使用CUSTOM_STRATEGY,可不做配置 + */ + String paramName() default ""; + /** + * + * 第几个where 条件 从0开始 + */ + int whereIndex() default 0; + + /** + * DataScopeWhereInTypeEnum为CUSTOM_STRATEGY类型时,此属性无效 + */ + String joinSql() default ""; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/DataScopeController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/DataScopeController.java new file mode 100644 index 0000000..8f2fec8 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/DataScopeController.java @@ -0,0 +1,38 @@ +package net.lab1024.sa.admin.module.system.datascope; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import net.lab1024.sa.admin.constant.AdminSwaggerTagConst; +import net.lab1024.sa.admin.module.system.datascope.domain.DataScopeAndViewTypeVO; +import net.lab1024.sa.admin.module.system.datascope.service.DataScopeService; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * 查询支持的数据范围类型 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022-03-18 20:59:17 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@RestController +@Tag(name = AdminSwaggerTagConst.System.SYSTEM_DATA_SCOPE) +public class DataScopeController { + + @Resource + private DataScopeService dataScopeService; + + @Operation(summary = "获取当前系统所配置的所有数据范围 @author 罗伊") + @GetMapping("/dataScope/list") + public ResponseDTO> dataScopeList() { + return dataScopeService.dataScopeList(); + } + + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/MyBatisPlugin.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/MyBatisPlugin.java new file mode 100644 index 0000000..70cfd92 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/MyBatisPlugin.java @@ -0,0 +1,201 @@ +package net.lab1024.sa.admin.module.system.datascope; + +import cn.hutool.core.util.StrUtil; +import com.google.common.collect.Maps; +import jakarta.annotation.Resource; +import net.lab1024.sa.admin.module.system.datascope.domain.DataScopeSqlConfig; +import net.lab1024.sa.admin.module.system.datascope.service.DataScopeSqlConfigService; +import net.lab1024.sa.base.common.domain.DataScopePlugin; +import org.apache.commons.lang3.StringUtils; +import org.apache.ibatis.mapping.*; +import org.apache.ibatis.plugin.Intercepts; +import org.apache.ibatis.plugin.Invocation; +import org.apache.ibatis.plugin.Plugin; +import org.apache.ibatis.plugin.Signature; +import org.apache.ibatis.session.ResultHandler; +import org.apache.ibatis.session.RowBounds; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; +import java.util.Properties; + +/** + * mybaits sql 拦截 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022-03-18 20:59:17 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Intercepts({@Signature(type = org.apache.ibatis.executor.Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})}) +@Component +public class MyBatisPlugin extends DataScopePlugin { + + @Resource + private ApplicationContext applicationContext; + + @Override + public Object intercept(Invocation invocation) throws Throwable { + + MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0]; + Object parameter = invocation.getArgs()[1]; + + // 如果参数是 LambdaQueryWrapper 类型,跳过数据范围处理,避免 getSqlFirst 异常 + if (parameter instanceof com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper) { + return invocation.proceed(); + } + + BoundSql boundSql; + try { + boundSql = mappedStatement.getBoundSql(parameter); + } catch (Exception e) { + // 如果是 getSqlFirst 相关的异常,直接跳过不处理 + if (e.getMessage() != null && e.getMessage().contains("getSqlFirst")) { + return invocation.proceed(); + } + throw e; + } + + String originalSql = boundSql.getSql().trim(); + String id = mappedStatement.getId(); + List methodStrList = StrUtil.split(id, "."); + String path = methodStrList.get(methodStrList.size() - 2) + "." + methodStrList.get(methodStrList.size() - 1); + DataScopeSqlConfigService dataScopeSqlConfigService = this.dataScopeSqlConfigService(); + if (dataScopeSqlConfigService == null) { + return invocation.proceed(); + } + DataScopeSqlConfig sqlConfigDTO = dataScopeSqlConfigService.getSqlConfig(path); + if (sqlConfigDTO != null) { + Map paramMap = this.getParamList(sqlConfigDTO.getParamName(), parameter); + BoundSql newBoundSql = copyFromBoundSql(mappedStatement, boundSql, this.joinSql(originalSql, paramMap, sqlConfigDTO)); + ParameterMap map = mappedStatement.getParameterMap(); + MappedStatement newMs = copyFromMappedStatement(mappedStatement, new BoundSqlSqlSource(newBoundSql), map); + invocation.getArgs()[0] = newMs; + } + + Object obj = invocation.proceed(); + return obj; + } + + + private Map getParamList(String paramName, Object parameter) { + Map paramMap = Maps.newHashMap(); + if (StringUtils.isEmpty(paramName)) { + return paramMap; + } + if (parameter == null) { + return paramMap; + } + if (parameter instanceof Map) { + String[] paramNameArray = paramName.split(","); + Map parameterMap = (Map) parameter; + for (String param : paramNameArray) { + if(parameterMap.containsKey(param)){ + paramMap.put(param, parameterMap.get(param)); + } + } + } + return paramMap; + } + + private String joinSql(String sql, Map paramMap, DataScopeSqlConfig sqlConfigDTO) { + if (null == sqlConfigDTO) { + return sql; + } + String appendSql = this.dataScopeSqlConfigService().getJoinSql(paramMap, sqlConfigDTO); + if (StringUtils.isEmpty(appendSql)) { + return sql; + } + Integer appendSqlWhereIndex = sqlConfigDTO.getWhereIndex(); + String where = "where"; + String order = "order by"; + String group = "group by"; + int whereIndex = StringUtils.ordinalIndexOf(sql.toLowerCase(), where, appendSqlWhereIndex + 1); + int orderIndex = sql.toLowerCase().indexOf(order); + int groupIndex = sql.toLowerCase().indexOf(group); + if (whereIndex > -1) { + String subSql = sql.substring(0, whereIndex + where.length() + 1); + subSql = subSql + " " + appendSql + " AND " + sql.substring(whereIndex + where.length() + 1); + return subSql; + } + + if (groupIndex > -1) { + String subSql = sql.substring(0, groupIndex); + subSql = subSql + " where " + appendSql + " " + sql.substring(groupIndex); + return subSql; + } + if (orderIndex > -1) { + String subSql = sql.substring(0, orderIndex); + subSql = subSql + " where " + appendSql + " " + sql.substring(orderIndex); + return subSql; + } + sql += " where " + appendSql; + return sql; + } + + public DataScopeSqlConfigService dataScopeSqlConfigService() { + return (DataScopeSqlConfigService) applicationContext.getBean("dataScopeSqlConfigService"); + } + + public class BoundSqlSqlSource implements SqlSource { + + BoundSql boundSql; + + public BoundSqlSqlSource(BoundSql boundSql) { + this.boundSql = boundSql; + } + + @Override + public BoundSql getBoundSql(Object parameterObject) { + return boundSql; + } + } + + /** + * 复制MappedStatement对象 + */ + private MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource, ParameterMap parameterMap) { + + MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType()); + builder.resource(ms.getResource()); + builder.fetchSize(ms.getFetchSize()); + builder.statementType(ms.getStatementType()); + builder.keyGenerator(ms.getKeyGenerator()); + builder.timeout(ms.getTimeout()); + builder.parameterMap(parameterMap); + builder.resultMaps(ms.getResultMaps()); + builder.resultSetType(ms.getResultSetType()); + builder.cache(ms.getCache()); + builder.flushCacheRequired(ms.isFlushCacheRequired()); + builder.useCache(ms.isUseCache()); + return builder.build(); + } + + /** + * 复制BoundSql对象 + */ + private BoundSql copyFromBoundSql(MappedStatement ms, BoundSql boundSql, String sql) { + BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), sql, boundSql.getParameterMappings(), boundSql.getParameterObject()); + for (ParameterMapping mapping : boundSql.getParameterMappings()) { + String prop = mapping.getProperty(); + if (boundSql.hasAdditionalParameter(prop)) { + newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop)); + } + } + return newBoundSql; + } + + @Override + public Object plugin(Object arg0) { + return Plugin.wrap(arg0, this); + } + + @Override + public void setProperties(Properties arg0) { + + } + +} \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/constant/DataScopeTypeEnum.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/constant/DataScopeTypeEnum.java new file mode 100644 index 0000000..9a43a63 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/constant/DataScopeTypeEnum.java @@ -0,0 +1,56 @@ +package net.lab1024.sa.admin.module.system.datascope.constant; + +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 数据范围 类型 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2020/11/28 20:59:17 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public enum DataScopeTypeEnum implements BaseEnum { + + /** + * 系统通知 + */ + NOTICE(1, 20, "部门管理", "查看部门员工数据范围"), + ; + + private final Integer value; + + private final Integer sort; + + private final String name; + + private final String desc; + + DataScopeTypeEnum(Integer value, Integer sort, String name, String desc) { + this.value = value; + this.sort = sort; + this.name = name; + this.desc = desc; + } + + @Override + public Integer getValue() { + return value; + } + + public Integer getSort() { + return sort; + } + + @Override + public String getDesc() { + return desc; + } + + public String getName() { + return name; + } + + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/constant/DataScopeViewTypeEnum.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/constant/DataScopeViewTypeEnum.java new file mode 100644 index 0000000..e32ddb0 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/constant/DataScopeViewTypeEnum.java @@ -0,0 +1,65 @@ +package net.lab1024.sa.admin.module.system.datascope.constant; + + +import net.lab1024.sa.base.common.enumeration.BaseEnum; + + +/** + * 数据可见范围类型 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2020/11/28 20:59:17 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public enum DataScopeViewTypeEnum implements BaseEnum { + + /** + * 本人 + */ + ME(0, 0, "本部门"), + + /** + * 部门 + */ + DEPARTMENT(1, 5, "本部门及下属子部门"), + + /** + * 本部门及下属子部门 + */ + DEPARTMENT_AND_SUB(2, 10, "本区域"), + + /** + * 全部 + */ + ALL(10, 100, "全部"); + + + + private final Integer value; + private final Integer level; + private final String desc; + + DataScopeViewTypeEnum(Integer value, Integer level, String desc) { + this.value = value; + this.level = level; + this.desc = desc; + } + + @Override + public Integer getValue() { + return value; + } + + public Integer getLevel() { + return level; + } + + @Override + public String getDesc() { + return desc; + } + + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/constant/DataScopeWhereInTypeEnum.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/constant/DataScopeWhereInTypeEnum.java new file mode 100644 index 0000000..9692a4a --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/constant/DataScopeWhereInTypeEnum.java @@ -0,0 +1,51 @@ +package net.lab1024.sa.admin.module.system.datascope.constant; + + +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 数据范围 sql where + * + * @Author 1024创新实验室: 罗伊 + * @Date 2020/11/28 20:59:17 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public enum DataScopeWhereInTypeEnum implements BaseEnum { + + /** + * 以员工IN + */ + EMPLOYEE(0, "以员工IN"), + + /** + * 以部门IN + */ + DEPARTMENT(1, "以部门IN"), + + /** + * 自定义策略 + */ + CUSTOM_STRATEGY(2, "自定义策略"); + + private final Integer value; + private final String desc; + + DataScopeWhereInTypeEnum(Integer value, String desc) { + this.value = value; + this.desc = desc; + } + + @Override + public Integer getValue() { + return value; + } + + @Override + public String getDesc() { + return desc; + } + + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/domain/DataScopeAndViewTypeVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/domain/DataScopeAndViewTypeVO.java new file mode 100644 index 0000000..5787f79 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/domain/DataScopeAndViewTypeVO.java @@ -0,0 +1,35 @@ +package net.lab1024.sa.admin.module.system.datascope.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +/** + * 数据范围 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2020/11/28 20:59:17 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class DataScopeAndViewTypeVO { + + @Schema(description = "数据范围类型") + private Integer dataScopeType; + + @Schema(description = "数据范围名称") + private String dataScopeTypeName; + + @Schema(description = "描述") + private String dataScopeTypeDesc; + + @Schema(description = "顺序") + private Integer dataScopeTypeSort; + + @Schema(description = "可见范围列表") + private List viewTypeList; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/domain/DataScopeDTO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/domain/DataScopeDTO.java new file mode 100644 index 0000000..b0c7ad9 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/domain/DataScopeDTO.java @@ -0,0 +1,32 @@ +package net.lab1024.sa.admin.module.system.datascope.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Data; + +/** + * 数据范围 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2020/11/28 20:59:17 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@Builder +public class DataScopeDTO { + + @Schema(description = "数据范围类型") + private Integer dataScopeType; + + @Schema(description = "数据范围名称") + private String dataScopeTypeName; + + @Schema(description = "描述") + private String dataScopeTypeDesc; + + @Schema(description = "顺序") + private Integer dataScopeTypeSort; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/domain/DataScopeSqlConfig.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/domain/DataScopeSqlConfig.java new file mode 100644 index 0000000..3ba5c42 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/domain/DataScopeSqlConfig.java @@ -0,0 +1,41 @@ +package net.lab1024.sa.admin.module.system.datascope.domain; + +import lombok.Data; +import net.lab1024.sa.admin.module.system.datascope.constant.DataScopeTypeEnum; +import net.lab1024.sa.admin.module.system.datascope.constant.DataScopeWhereInTypeEnum; + +/** + * 数据范围 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2020/11/28 20:59:17 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class DataScopeSqlConfig { + + /** + * 数据范围类型 + * {@link DataScopeTypeEnum} + */ + private DataScopeTypeEnum dataScopeType; + + /** + * join sql 具体实现类 + */ + private Class joinSqlImplClazz; + + private String joinSql; + + private Integer whereIndex; + + private String paramName; + + /** + * whereIn类型 + * {@link DataScopeWhereInTypeEnum} + */ + private DataScopeWhereInTypeEnum dataScopeWhereInType; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/domain/DataScopeViewTypeVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/domain/DataScopeViewTypeVO.java new file mode 100644 index 0000000..106a2fc --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/domain/DataScopeViewTypeVO.java @@ -0,0 +1,28 @@ +package net.lab1024.sa.admin.module.system.datascope.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Data; + +/** + * 数据可见范围 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2020/11/28 20:59:17 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@Builder +public class DataScopeViewTypeVO { + + @Schema(description = "可见范围") + private Integer viewType; + + @Schema(description = "可见范围名称") + private String viewTypeName; + + @Schema(description = "级别,用于表示范围大小") + private Integer viewTypeLevel; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/service/DataScopeService.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/service/DataScopeService.java new file mode 100644 index 0000000..dbad387 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/service/DataScopeService.java @@ -0,0 +1,71 @@ +package net.lab1024.sa.admin.module.system.datascope.service; + +import com.google.common.collect.Lists; +import net.lab1024.sa.admin.module.system.datascope.constant.DataScopeTypeEnum; +import net.lab1024.sa.admin.module.system.datascope.constant.DataScopeViewTypeEnum; +import net.lab1024.sa.admin.module.system.datascope.domain.DataScopeAndViewTypeVO; +import net.lab1024.sa.admin.module.system.datascope.domain.DataScopeDTO; +import net.lab1024.sa.admin.module.system.datascope.domain.DataScopeViewTypeVO; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import org.springframework.stereotype.Service; + +import java.util.Comparator; +import java.util.List; + +/** + * 数据范围 保存 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2020/11/28 20:59:17 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class DataScopeService { + + /** + * 获取所有可以进行数据范围配置的信息 + */ + public ResponseDTO> dataScopeList() { + List dataScopeList = this.getDataScopeType(); + List dataScopeAndTypeList = SmartBeanUtil.copyList(dataScopeList, DataScopeAndViewTypeVO.class); + List typeList = this.getViewType(); + dataScopeAndTypeList.forEach(e -> { + e.setViewTypeList(typeList); + }); + return ResponseDTO.ok(dataScopeAndTypeList); + } + + /** + * 获取当前系统存在的数据可见范围 + */ + public List getViewType() { + List viewTypeList = Lists.newArrayList(); + DataScopeViewTypeEnum[] enums = DataScopeViewTypeEnum.class.getEnumConstants(); + DataScopeViewTypeVO dataScopeViewTypeDTO; + for (DataScopeViewTypeEnum viewTypeEnum : enums) { + dataScopeViewTypeDTO = DataScopeViewTypeVO.builder().viewType(viewTypeEnum.getValue()).viewTypeLevel(viewTypeEnum.getLevel()).viewTypeName(viewTypeEnum.getDesc()).build(); + viewTypeList.add(dataScopeViewTypeDTO); + } + Comparator comparator = (h1, h2) -> h1.getViewTypeLevel().compareTo(h2.getViewTypeLevel()); + viewTypeList.sort(comparator); + return viewTypeList; + } + + public List getDataScopeType() { + List dataScopeTypeList = Lists.newArrayList(); + DataScopeTypeEnum[] enums = DataScopeTypeEnum.class.getEnumConstants(); + DataScopeDTO dataScopeDTO; + for (DataScopeTypeEnum typeEnum : enums) { + dataScopeDTO = + DataScopeDTO.builder().dataScopeType(typeEnum.getValue()).dataScopeTypeDesc(typeEnum.getDesc()).dataScopeTypeName(typeEnum.getName()).dataScopeTypeSort(typeEnum.getSort()).build(); + dataScopeTypeList.add(dataScopeDTO); + } + Comparator comparator = (h1, h2) -> h1.getDataScopeTypeSort().compareTo(h2.getDataScopeTypeSort()); + dataScopeTypeList.sort(comparator); + return dataScopeTypeList; + } + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/service/DataScopeSqlConfigService.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/service/DataScopeSqlConfigService.java new file mode 100644 index 0000000..03523b7 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/service/DataScopeSqlConfigService.java @@ -0,0 +1,149 @@ +package net.lab1024.sa.admin.module.system.datascope.service; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.AdminApplication; +import net.lab1024.sa.admin.module.system.datascope.DataScope; +import net.lab1024.sa.admin.module.system.datascope.constant.DataScopeTypeEnum; +import net.lab1024.sa.admin.module.system.datascope.constant.DataScopeViewTypeEnum; +import net.lab1024.sa.admin.module.system.datascope.constant.DataScopeWhereInTypeEnum; +import net.lab1024.sa.admin.module.system.datascope.domain.DataScopeSqlConfig; +import net.lab1024.sa.admin.module.system.datascope.strategy.AbstractDataScopeStrategy; +import net.lab1024.sa.base.common.util.SmartRequestUtil; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.reflections.Reflections; +import org.reflections.scanners.MethodAnnotationsScanner; +import org.reflections.util.ClasspathHelper; +import org.reflections.util.ConfigurationBuilder; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Service; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * sql配置 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2020/11/28 20:59:17 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@Service +public class DataScopeSqlConfigService { + + /** + * 注解joinsql 参数 + */ + private static final String EMPLOYEE_PARAM = "#employeeIds"; + + private static final String DEPARTMENT_PARAM = "#departmentIds"; + + /** + * 用于拼接查看本人数据范围的 SQL + */ + private static final String CREATE_USER_ID_EQUALS = "create_user_id = "; + + private final ConcurrentHashMap dataScopeMethodMap = new ConcurrentHashMap<>(); + + @Resource + private DataScopeViewService dataScopeViewService; + + @Resource + private ApplicationContext applicationContext; + + + @PostConstruct + private void initDataScopeMethodMap() { + this.refreshDataScopeMethodMap(); + } + + /** + * 刷新 所有添加数据范围注解的接口方法配置 + */ + private Map refreshDataScopeMethodMap() { + Reflections reflections = new Reflections(new ConfigurationBuilder().setUrls(ClasspathHelper.forPackage(AdminApplication.COMPONENT_SCAN)).setScanners(new MethodAnnotationsScanner())); + Set methods = reflections.getMethodsAnnotatedWith(DataScope.class); + for (Method method : methods) { + DataScope dataScopeAnnotation = method.getAnnotation(DataScope.class); + if (dataScopeAnnotation != null) { + DataScopeSqlConfig configDTO = new DataScopeSqlConfig(); + configDTO.setDataScopeType(dataScopeAnnotation.dataScopeType()); + configDTO.setJoinSql(dataScopeAnnotation.joinSql()); + configDTO.setWhereIndex(dataScopeAnnotation.whereIndex()); + configDTO.setDataScopeWhereInType(dataScopeAnnotation.whereInType()); + configDTO.setParamName(dataScopeAnnotation.paramName()); + configDTO.setJoinSqlImplClazz(dataScopeAnnotation.joinSqlImplClazz()); + dataScopeMethodMap.put(method.getDeclaringClass().getSimpleName() + "." + method.getName(), configDTO); + } + } + return dataScopeMethodMap; + } + + /** + * 根据调用的方法获取,此方法的配置信息 + */ + public DataScopeSqlConfig getSqlConfig(String method) { + return this.dataScopeMethodMap.get(method); + } + + /** + * 组装需要拼接的sql + */ + public String getJoinSql(Map paramMap, DataScopeSqlConfig sqlConfigDTO) { + Long employeeId = SmartRequestUtil.getRequestUserId(); + if (employeeId == null) { + return ""; + } + + DataScopeTypeEnum dataScopeTypeEnum = sqlConfigDTO.getDataScopeType(); + DataScopeViewTypeEnum viewTypeEnum = dataScopeViewService.getEmployeeDataScopeViewType(dataScopeTypeEnum, employeeId); + + // 数据权限设置为仅本人可见时 直接返回 create_user_id = employeeId + if (DataScopeViewTypeEnum.ME == viewTypeEnum) { + return CREATE_USER_ID_EQUALS + employeeId; + } + + String joinSql = sqlConfigDTO.getJoinSql(); + + if (DataScopeWhereInTypeEnum.CUSTOM_STRATEGY == sqlConfigDTO.getDataScopeWhereInType()) { + Class strategyClass = sqlConfigDTO.getJoinSqlImplClazz(); + if (strategyClass == null) { + log.warn("data scope custom strategy class is null"); + return ""; + } + AbstractDataScopeStrategy powerStrategy = (AbstractDataScopeStrategy) applicationContext.getBean(sqlConfigDTO.getJoinSqlImplClazz()); + if (powerStrategy == null) { + log.warn("data scope custom strategy class:{} ,bean is null", sqlConfigDTO.getJoinSqlImplClazz()); + return ""; + } + return powerStrategy.getCondition(viewTypeEnum, paramMap, sqlConfigDTO); + } + if (DataScopeWhereInTypeEnum.EMPLOYEE == sqlConfigDTO.getDataScopeWhereInType()) { + List canViewEmployeeIds = dataScopeViewService.getCanViewEmployeeId(viewTypeEnum, employeeId); + if (CollectionUtils.isEmpty(canViewEmployeeIds)) { + return ""; + } + String employeeIds = StringUtils.join(canViewEmployeeIds, ","); + String sql = joinSql.replaceAll(EMPLOYEE_PARAM, employeeIds); + return sql; + } + if (DataScopeWhereInTypeEnum.DEPARTMENT == sqlConfigDTO.getDataScopeWhereInType()) { + List canViewDepartmentIds = dataScopeViewService.getCanViewDepartmentId(viewTypeEnum, employeeId); + if (CollectionUtils.isEmpty(canViewDepartmentIds)) { + return ""; + } + String departmentIds = StringUtils.join(canViewDepartmentIds, ","); + String sql = joinSql.replaceAll(DEPARTMENT_PARAM, departmentIds); + return sql; + } + return ""; + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/service/DataScopeViewService.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/service/DataScopeViewService.java new file mode 100644 index 0000000..1558573 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/service/DataScopeViewService.java @@ -0,0 +1,146 @@ +package net.lab1024.sa.admin.module.system.datascope.service; + +import com.google.common.collect.Lists; +import jakarta.annotation.Resource; +import net.lab1024.sa.admin.module.system.datascope.constant.DataScopeTypeEnum; +import net.lab1024.sa.admin.module.system.datascope.constant.DataScopeViewTypeEnum; +import net.lab1024.sa.admin.module.system.department.service.DepartmentService; +import net.lab1024.sa.admin.module.system.employee.dao.EmployeeDao; +import net.lab1024.sa.admin.module.system.employee.domain.entity.EmployeeEntity; +import net.lab1024.sa.admin.module.system.role.dao.RoleDataScopeDao; +import net.lab1024.sa.admin.module.system.role.dao.RoleEmployeeDao; +import net.lab1024.sa.admin.module.system.role.domain.entity.RoleDataScopeEntity; +import net.lab1024.sa.base.common.util.SmartEnumUtil; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; + +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 数据范围 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2020/11/28 20:59:17 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class DataScopeViewService { + + @Resource + private RoleEmployeeDao roleEmployeeDao; + + @Resource + private RoleDataScopeDao roleDataScopeDao; + + @Resource + private EmployeeDao employeeDao; + + @Resource + private DepartmentService departmentService; + + /** + * 获取某人可以查看的所有人员数据 + */ + public List getCanViewEmployeeId(DataScopeViewTypeEnum viewType, Long employeeId) { + if (DataScopeViewTypeEnum.ME == viewType) { + return this.getMeEmployeeIdList(employeeId); + } + if (DataScopeViewTypeEnum.DEPARTMENT == viewType) { + return this.getDepartmentEmployeeIdList(employeeId); + } + if (DataScopeViewTypeEnum.DEPARTMENT_AND_SUB == viewType) { + return this.getDepartmentAndSubEmployeeIdList(employeeId); + } + // 可以查看所有员工数据 + return Lists.newArrayList(); + } + + /** + * 获取某人可以查看的所有部门数据 + */ + public List getCanViewDepartmentId(DataScopeViewTypeEnum viewType, Long employeeId) { + if (DataScopeViewTypeEnum.ME == viewType) { + // 数据可见范围类型为本人时 不可以查看任何部门数据 + return Lists.newArrayList(0L); + } + if (DataScopeViewTypeEnum.DEPARTMENT == viewType) { + return this.getMeDepartmentIdList(employeeId); + } + if (DataScopeViewTypeEnum.DEPARTMENT_AND_SUB == viewType) { + return this.getDepartmentAndSubIdList(employeeId); + } + // 可以查看所有部门数据 + return Lists.newArrayList(); + } + + public List getMeDepartmentIdList(Long employeeId) { + EmployeeEntity employeeEntity = employeeDao.selectById(employeeId); + return Lists.newArrayList(employeeEntity.getDepartmentId()); + } + + public List getDepartmentAndSubIdList(Long employeeId) { + EmployeeEntity employeeEntity = employeeDao.selectById(employeeId); + return departmentService.selfAndChildrenIdList(employeeEntity.getDepartmentId()); + } + + /** + * 根据员工id 获取各数据范围最大的可见范围 map + */ + public DataScopeViewTypeEnum getEmployeeDataScopeViewType(DataScopeTypeEnum dataScopeTypeEnum, Long employeeId) { + EmployeeEntity employeeEntity = employeeDao.selectById(employeeId); + if (employeeEntity == null || employeeEntity.getEmployeeId() == null) { + return DataScopeViewTypeEnum.ME; + } + + // 如果是超级管理员 则可查看全部 + if (employeeEntity.getAdministratorFlag()) { + return DataScopeViewTypeEnum.ALL; + } + + List roleIdList = roleEmployeeDao.selectRoleIdByEmployeeId(employeeId); + //未设置角色 默认本人 + if (CollectionUtils.isEmpty(roleIdList)) { + return DataScopeViewTypeEnum.ME; + } + //未设置角色数据范围 默认本人 + List dataScopeRoleList = roleDataScopeDao.listByRoleIdList(roleIdList); + if (CollectionUtils.isEmpty(dataScopeRoleList)) { + return DataScopeViewTypeEnum.ME; + } + Map> listMap = dataScopeRoleList.stream().collect(Collectors.groupingBy(RoleDataScopeEntity::getDataScopeType)); + List viewLevelList = listMap.getOrDefault(dataScopeTypeEnum.getValue(), Lists.newArrayList()); + if (CollectionUtils.isEmpty(viewLevelList)) { + return DataScopeViewTypeEnum.ME; + } + RoleDataScopeEntity maxLevel = viewLevelList.stream().max(Comparator.comparing(e -> SmartEnumUtil.getEnumByValue(e.getViewType(), DataScopeViewTypeEnum.class).getLevel())).get(); + return SmartEnumUtil.getEnumByValue(maxLevel.getViewType(), DataScopeViewTypeEnum.class); + } + + /** + * 获取本人相关 可查看员工id + */ + private List getMeEmployeeIdList(Long employeeId) { + return Lists.newArrayList(employeeId); + } + + /** + * 获取本部门相关 可查看员工id + */ + private List getDepartmentEmployeeIdList(Long employeeId) { + EmployeeEntity employeeEntity = employeeDao.selectById(employeeId); + return employeeDao.getEmployeeIdByDepartmentId(employeeEntity.getDepartmentId(), false); + } + + /** + * 获取本部门及下属子部门相关 可查看员工id + */ + private List getDepartmentAndSubEmployeeIdList(Long employeeId) { + List allDepartmentIds = getDepartmentAndSubIdList(employeeId); + return employeeDao.getEmployeeIdByDepartmentIdList(allDepartmentIds, false); + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/strategy/AbstractDataScopeStrategy.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/strategy/AbstractDataScopeStrategy.java new file mode 100644 index 0000000..152bb1e --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/datascope/strategy/AbstractDataScopeStrategy.java @@ -0,0 +1,23 @@ +package net.lab1024.sa.admin.module.system.datascope.strategy; + +import net.lab1024.sa.admin.module.system.datascope.constant.DataScopeViewTypeEnum; +import net.lab1024.sa.admin.module.system.datascope.domain.DataScopeSqlConfig; + +import java.util.Map; + +/** + * 数据范围策略 ,使用DataScopeWhereInTypeEnum.CUSTOM_STRATEGY类型,DataScope注解的joinSql属性无用 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2020/11/28 20:59:17 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public abstract class AbstractDataScopeStrategy { + + /** + * 获取joinsql 字符串 + */ + public abstract String getCondition(DataScopeViewTypeEnum viewTypeEnum, Map paramMap, DataScopeSqlConfig sqlConfigDTO); +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/department/controller/DepartmentController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/department/controller/DepartmentController.java new file mode 100644 index 0000000..2dc9e5c --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/department/controller/DepartmentController.java @@ -0,0 +1,68 @@ +package net.lab1024.sa.admin.module.system.department.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import net.lab1024.sa.admin.constant.AdminSwaggerTagConst; +import net.lab1024.sa.admin.module.system.department.domain.form.DepartmentAddForm; +import net.lab1024.sa.admin.module.system.department.domain.form.DepartmentUpdateForm; +import net.lab1024.sa.admin.module.system.department.domain.vo.DepartmentTreeVO; +import net.lab1024.sa.admin.module.system.department.domain.vo.DepartmentVO; +import net.lab1024.sa.admin.module.system.department.service.DepartmentService; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 部门 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-01-12 20:37:48 + * @Wechat 卓大1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@RestController +@Tag(name = AdminSwaggerTagConst.System.SYSTEM_DEPARTMENT) +public class DepartmentController { + + @Resource + private DepartmentService departmentService; + + @Operation(summary = "查询部门树形列表 @author 卓大") + @GetMapping("/department/treeList") + public ResponseDTO> departmentTree() { + return departmentService.departmentTree(); + } + + @Operation(summary = "添加部门 @author 卓大") + @PostMapping("/department/add") + @SaCheckPermission("system:department:add") + public ResponseDTO addDepartment(@Valid @RequestBody DepartmentAddForm createDTO) { + return departmentService.addDepartment(createDTO); + } + + @Operation(summary = "更新部门 @author 卓大") + @PostMapping("/department/update") + @SaCheckPermission("system:department:update") + public ResponseDTO updateDepartment(@Valid @RequestBody DepartmentUpdateForm updateDTO) { + return departmentService.updateDepartment(updateDTO); + } + + @Operation(summary = "删除部门 @author 卓大") + @GetMapping("/department/delete/{departmentId}") + @SaCheckPermission("system:department:delete") + public ResponseDTO deleteDepartment(@PathVariable Long departmentId) { + return departmentService.deleteDepartment(departmentId); + } + + @Operation(summary = "查询部门列表 @author 卓大") + @GetMapping("/department/listAll") + public ResponseDTO> listAll() { + return ResponseDTO.ok(departmentService.listAll()); + } + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/department/dao/DepartmentDao.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/department/dao/DepartmentDao.java new file mode 100644 index 0000000..dd74bda --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/department/dao/DepartmentDao.java @@ -0,0 +1,51 @@ +package net.lab1024.sa.admin.module.system.department.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.admin.module.system.department.domain.entity.DepartmentEntity; +import net.lab1024.sa.admin.module.system.department.domain.form.DepartmentQueryForm; +import net.lab1024.sa.admin.module.system.department.domain.vo.DepartmentVO; +import net.lab1024.sa.admin.module.system.position.domain.form.PositionQueryForm; +import net.lab1024.sa.admin.module.system.position.domain.vo.PositionVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 部门 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-01-12 20:37:48 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface DepartmentDao extends BaseMapper { + + /** + * 根据部门id,查询此部门直接子部门的数量 + * + */ + Integer countSubDepartment(@Param("departmentId") Long departmentId); + + /** + * 分页 查询 + * + * @param page + * @param queryForm + * @return + */ + List queryPage(Page page, @Param("queryForm") DepartmentQueryForm queryForm); + + + /** + * 获取全部部门列表 + */ + List listAll(); + + List listMyAll(@Param("areaCode") String areaCode); + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/department/domain/entity/DepartmentEntity.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/department/domain/entity/DepartmentEntity.java new file mode 100644 index 0000000..497598a --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/department/domain/entity/DepartmentEntity.java @@ -0,0 +1,79 @@ +package net.lab1024.sa.admin.module.system.department.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.FieldStrategy; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 部门实体类 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-01-12 20:37:48 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName(value = "t_department") +public class DepartmentEntity { + + /** + * 主键id + */ + @TableId(type = IdType.AUTO) + private Long departmentId; + + /** + * 部门名称 + */ + private String name; + + /** + * 部门编码 + */ + private String code; + + /** + * 负责人员工 id + */ + @TableField(updateStrategy = FieldStrategy.IGNORED) + private Long managerId; + + /** + * 部门父级id + */ + private Long parentId; + + /** + * 排序 + */ + private Integer sort; + + /** + * 警种(字典表 t_dict_data 中 dict_id=2) + */ + private String policeType; + + /** + * 维权区域编码 + */ + private String areaCode; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/department/domain/form/DepartmentAddForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/department/domain/form/DepartmentAddForm.java new file mode 100644 index 0000000..0965ba7 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/department/domain/form/DepartmentAddForm.java @@ -0,0 +1,45 @@ +package net.lab1024.sa.admin.module.system.department.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +/** + * 部门 添加表单 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-01-12 20:37:48 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class DepartmentAddForm { + + @Schema(description = "部门名称") + @Length(min = 1, max = 50, message = "请输入正确的部门名称 (1-50 个字符)") + @NotNull(message = "请输入正确的部门名称 (1-50 个字符)") + private String name; + + @Schema(description = "部门编码") + @Length(max = 50, message = "部门编码不能大于 50 个字符") + private String code; + + @Schema(description = "排序") + @NotNull(message = "排序值") + private Integer sort; + + @Schema(description = "部门负责人id") + private Long managerId; + + @Schema(description = "上级部门id (可选)") + private Long parentId; + + @Schema(description = "警种(字典表 t_dict_data 中 dict_id=2)") + private String policeType; + + @Schema(description = "维权区域编码") + private String areaCode; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/department/domain/form/DepartmentQueryForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/department/domain/form/DepartmentQueryForm.java new file mode 100644 index 0000000..38f4043 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/department/domain/form/DepartmentQueryForm.java @@ -0,0 +1,27 @@ +package net.lab1024.sa.admin.module.system.department.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Size; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; +import org.hibernate.validator.constraints.Length; + +import java.time.LocalDate; +import java.util.List; + +@Data +public class DepartmentQueryForm extends PageParam { + + @Schema(description = "部门名称") + @Length(max = 20, message = "搜索词最多20字符") + private String name; + + @Schema(description = "开始时间") + private LocalDate startTime; + + @Schema(description = "结束时间") + private LocalDate endTime; + + + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/department/domain/form/DepartmentUpdateForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/department/domain/form/DepartmentUpdateForm.java new file mode 100644 index 0000000..a65d232 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/department/domain/form/DepartmentUpdateForm.java @@ -0,0 +1,23 @@ +package net.lab1024.sa.admin.module.system.department.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 部门 更新表单 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-01-12 20:37:48 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class DepartmentUpdateForm extends DepartmentAddForm { + + @Schema(description = "部门id") + @NotNull(message = "部门id不能为空") + private Long departmentId; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/department/domain/vo/DepartmentEmployeeTreeVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/department/domain/vo/DepartmentEmployeeTreeVO.java new file mode 100644 index 0000000..94bfb71 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/department/domain/vo/DepartmentEmployeeTreeVO.java @@ -0,0 +1,27 @@ +package net.lab1024.sa.admin.module.system.department.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.admin.module.system.employee.domain.vo.EmployeeVO; + +import java.util.List; + +/** + * 部门 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-01-12 20:37:48 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class DepartmentEmployeeTreeVO extends DepartmentVO { + + @Schema(description = "部门员工列表") + private List employees; + + @Schema(description = "子部门") + private List children; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/department/domain/vo/DepartmentTreeVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/department/domain/vo/DepartmentTreeVO.java new file mode 100644 index 0000000..7728f22 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/department/domain/vo/DepartmentTreeVO.java @@ -0,0 +1,32 @@ +package net.lab1024.sa.admin.module.system.department.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +/** + * 部门 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-01-12 20:37:48 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class DepartmentTreeVO extends DepartmentVO { + + @Schema(description = "同级上一个元素id") + private Long preId; + + @Schema(description = "同级下一个元素id") + private Long nextId; + + @Schema(description = "子部门") + private List children; + + @Schema(description = "自己和所有递归子部门的id集合") + private List selfAndAllChildrenIdList; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/department/domain/vo/DepartmentVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/department/domain/vo/DepartmentVO.java new file mode 100644 index 0000000..ea826c5 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/department/domain/vo/DepartmentVO.java @@ -0,0 +1,59 @@ +package net.lab1024.sa.admin.module.system.department.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 部门 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-01-12 20:37:48 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class DepartmentVO implements Serializable { + + private static final long serialVersionUID = 1L; + + @Schema(description = "部门id") + private Long departmentId; + + @Schema(description = "部门名称") + private String name; + + @Schema(description = "部门编码") + private String code; + + @Schema(description = "部门负责人姓名") + private String managerName; + + @Schema(description = "部门负责人id") + private Long managerId; + + @Schema(description = "父级部门id") + private Long parentId; + + @Schema(description = "排序") + private Integer sort; + + @Schema(description = "警种(字典表 t_dict_data 中 dict_id=2)") + private String policeType; + + @Schema(description = "维权区域编码") + private String areaCode; + + @Schema(description = "维权区域名称") + private String areaCodeName; + + @Schema(description = "更新时间") + private LocalDateTime updateTime; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/department/manager/DepartmentCacheManager.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/department/manager/DepartmentCacheManager.java new file mode 100644 index 0000000..16fd194 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/department/manager/DepartmentCacheManager.java @@ -0,0 +1,282 @@ +package net.lab1024.sa.admin.module.system.department.manager; + +import cn.dev33.satoken.stp.StpUtil; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.admin.constant.AdminCacheConst; +import net.lab1024.sa.admin.module.system.department.dao.DepartmentDao; +import net.lab1024.sa.admin.module.system.department.domain.vo.DepartmentTreeVO; +import net.lab1024.sa.admin.module.system.department.domain.vo.DepartmentVO; +import net.lab1024.sa.admin.module.system.login.domain.LoginResultVO; +import net.lab1024.sa.admin.module.system.login.service.LoginService; +import net.lab1024.sa.admin.module.system.region.service.RegionService; +import net.lab1024.sa.admin.util.AdminRequestUtil; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.math.NumberUtils; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 部门 缓存相关 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-01-12 20:37:48 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@Service +public class DepartmentCacheManager { + + @Resource + private DepartmentDao departmentDao; + + @Resource + private RegionService regionService; + + @Resource + @Lazy + private LoginService loginService; + + private void logClearInfo(String cache) { + log.info("clear " + cache); + } + + @CacheEvict(value = {AdminCacheConst.Department.DEPARTMENT_LIST_CACHE, AdminCacheConst.Department.DEPARTMENT_MAP_CACHE, AdminCacheConst.Department.DEPARTMENT_SELF_CHILDREN_CACHE, AdminCacheConst.Department.DEPARTMENT_TREE_CACHE, AdminCacheConst.Department.DEPARTMENT_PATH_CACHE,}, allEntries = true) + public void clearCache() { + logClearInfo(AdminCacheConst.Department.DEPARTMENT_LIST_CACHE); + } + + + /** + * 部门列表 + */ + public List getDepartmentList() { + String areaCode = null; + if(AdminRequestUtil.getRequestUser() != null){ + LoginResultVO loginResult = loginService.getLoginResult(AdminRequestUtil.getRequestUser(), StpUtil.getTokenValue()); + areaCode = loginResult.getDepartmentCode().substring(0, 6).replaceAll("00+$",""); + + } + List departmentList = departmentDao.listMyAll(areaCode); + // 填充区域名称 + if (CollectionUtils.isNotEmpty(departmentList)) { + for (DepartmentVO department : departmentList) { + if (department.getAreaCode() != null) { + String regionName = regionService.getRegionNameByCode(department.getAreaCode()); + department.setAreaCodeName(regionName); + } + } + } + return departmentList; + } + + /** + * 部门map + * + */ + @Cacheable(AdminCacheConst.Department.DEPARTMENT_MAP_CACHE) + public Map getDepartmentMap() { + return departmentDao.listAll().stream().collect(Collectors.toMap(DepartmentVO::getDepartmentId, Function.identity())); + } + + + /** + * 缓存部门树结构 + * + */ + public List getDepartmentTree() { + LoginResultVO loginResult = loginService.getLoginResult(AdminRequestUtil.getRequestUser(), StpUtil.getTokenValue()); + String areaCode = loginResult.getDepartmentCode().substring(0, 6).replaceAll("00+$",""); + List departmentVOList = departmentDao.listMyAll(areaCode); + return this.buildTree(departmentVOList); + } + + /** + * 缓存某个部门的下级id列表 + * + */ + @Cacheable(AdminCacheConst.Department.DEPARTMENT_SELF_CHILDREN_CACHE) + public List getDepartmentSelfAndChildren(Long departmentId) { + List departmentVOList = departmentDao.listAll(); + return this.selfAndChildrenIdList(departmentId, departmentVOList); + } + + + /** + * 部门的路径名称 + * + */ + @Cacheable(AdminCacheConst.Department.DEPARTMENT_PATH_CACHE) + public Map getDepartmentPathMap() { + List departmentVOList = departmentDao.listAll(); + Map departmentMap = departmentVOList.stream().collect(Collectors.toMap(DepartmentVO::getDepartmentId, Function.identity())); + + Map pathNameMap = Maps.newHashMap(); + for (DepartmentVO departmentVO : departmentVOList) { + String pathName = this.buildDepartmentPath(departmentVO, departmentMap); + pathNameMap.put(departmentVO.getDepartmentId(), pathName); + } + + return pathNameMap; + } + + /** + * 构建父级考点路径 + */ + private String buildDepartmentPath(DepartmentVO departmentVO, Map departmentMap) { + // 如果没有父节点,直接返回当前部门名称 + if (departmentVO.getParentId() == null) { + return departmentVO.getName(); + } + + // 查找父节点 + DepartmentVO parentDepartment = departmentMap.get(departmentVO.getParentId()); + if (parentDepartment == null) { + // 父节点不存在(可能是根节点或数据不一致),返回当前部门名称 + return departmentVO.getName(); + } + + // 递归构建父路径 + String pathName = buildDepartmentPath(parentDepartment, departmentMap); + return pathName + "/" + departmentVO.getName(); + } + // ---------------------- 构造树的一些方法 ------------------------------ + + /** + * 构建部门树结构 + * + */ + public List buildTree(List voList) { + if (CollectionUtils.isEmpty(voList)) { + return Lists.newArrayList(); + } + + // 找出所有可能的根节点(没有父节点或父节点不在列表中的节点) + Set allIds = voList.stream() + .map(DepartmentVO::getDepartmentId) + .collect(Collectors.toSet()); + + List rootList = voList.stream() + .filter(e -> e.getParentId() == null + || !allIds.contains(e.getParentId())) + .collect(Collectors.toList()); + + if (CollectionUtils.isEmpty(rootList)) { + return Lists.newArrayList(); + } + List treeVOList = SmartBeanUtil.copyList(rootList, DepartmentTreeVO.class); + this.recursiveBuildTree(treeVOList, voList); + return treeVOList; + } + + /** + * 构建所有根节点的下级树形结构 + * 返回值为层序遍历结果 + * [由于departmentDao中listAll给出数据根据Sort降序 所以同一层中Sort值较大的优先遍历] + */ + private List recursiveBuildTree(List nodeList, List allDepartmentList) { + int nodeSize = nodeList.size(); + List childIdList = new ArrayList<>(); + for(int i = 0; i < nodeSize; i++) { + int preIndex = i - 1; + int nextIndex = i + 1; + DepartmentTreeVO node = nodeList.get(i); + if (preIndex > -1) { + node.setPreId(nodeList.get(preIndex).getDepartmentId()); + } + if (nextIndex < nodeSize) { + node.setNextId(nodeList.get(nextIndex).getDepartmentId()); + } + + List children = getChildren(node.getDepartmentId(), allDepartmentList); + + List tempChildIdList = new ArrayList<>(); + if (CollectionUtils.isNotEmpty(children)) { + node.setChildren(children); + tempChildIdList = this.recursiveBuildTree(children, allDepartmentList); + } + + if(CollectionUtils.isEmpty(node.getSelfAndAllChildrenIdList())) { + node.setSelfAndAllChildrenIdList( + new ArrayList<>() + ); + } + node.getSelfAndAllChildrenIdList().add(node.getDepartmentId()); + + if(CollectionUtils.isNotEmpty(tempChildIdList)) { + node.getSelfAndAllChildrenIdList().addAll(tempChildIdList); + childIdList.addAll(tempChildIdList); + } + + } + + // 保证本层遍历顺序 + for(int i = nodeSize - 1; i >= 0; i--) { + childIdList.add(0, nodeList.get(i).getDepartmentId()); + } + + return childIdList; + } + + + /** + * 获取子元素 + * + */ + private List getChildren(Long departmentId, List voList) { + List childrenEntityList = voList.stream().filter(e -> departmentId.equals(e.getParentId())).collect(Collectors.toList()); + if (CollectionUtils.isEmpty(childrenEntityList)) { + return Lists.newArrayList(); + } + return SmartBeanUtil.copyList(childrenEntityList, DepartmentTreeVO.class); + } + + + /** + * 通过部门id,获取当前以及下属部门 + * + */ + public List selfAndChildrenIdList(Long departmentId, List voList) { + List selfAndChildrenIdList = Lists.newArrayList(); + if (CollectionUtils.isEmpty(voList)) { + return selfAndChildrenIdList; + } + selfAndChildrenIdList.add(departmentId); + List children = this.getChildren(departmentId, voList); + if (CollectionUtils.isEmpty(children)) { + return selfAndChildrenIdList; + } + List childrenIdList = children.stream().map(DepartmentTreeVO::getDepartmentId).collect(Collectors.toList()); + selfAndChildrenIdList.addAll(childrenIdList); + for (Long childId : childrenIdList) { + this.selfAndChildrenRecursion(selfAndChildrenIdList, childId, voList); + } + return selfAndChildrenIdList; + } + + /** + * 递归查询 + */ + public void selfAndChildrenRecursion(List selfAndChildrenIdList, Long departmentId, List voList) { + List children = this.getChildren(departmentId, voList); + if (CollectionUtils.isEmpty(children)) { + return; + } + List childrenIdList = children.stream().map(DepartmentTreeVO::getDepartmentId).collect(Collectors.toList()); + selfAndChildrenIdList.addAll(childrenIdList); + for (Long childId : childrenIdList) { + this.selfAndChildrenRecursion(selfAndChildrenIdList, childId, voList); + } + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/department/service/DepartmentService.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/department/service/DepartmentService.java new file mode 100644 index 0000000..96390ff --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/department/service/DepartmentService.java @@ -0,0 +1,216 @@ +package net.lab1024.sa.admin.module.system.department.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.annotation.Resource; +import net.lab1024.sa.admin.constant.AdminCacheConst; +import net.lab1024.sa.admin.module.system.department.dao.DepartmentDao; +import net.lab1024.sa.admin.module.system.department.domain.entity.DepartmentEntity; +import net.lab1024.sa.admin.module.system.department.domain.form.DepartmentAddForm; +import net.lab1024.sa.admin.module.system.department.domain.form.DepartmentQueryForm; +import net.lab1024.sa.admin.module.system.department.domain.form.DepartmentUpdateForm; +import net.lab1024.sa.admin.module.system.department.domain.vo.DepartmentTreeVO; +import net.lab1024.sa.admin.module.system.department.domain.vo.DepartmentVO; +import net.lab1024.sa.admin.module.system.department.manager.DepartmentCacheManager; +import net.lab1024.sa.admin.module.system.employee.dao.EmployeeDao; +import net.lab1024.sa.admin.module.system.position.domain.form.PositionQueryForm; +import net.lab1024.sa.admin.module.system.position.domain.vo.PositionVO; +import net.lab1024.sa.base.common.code.UserErrorCode; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * 部门 service + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-01-12 20:37:48 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class DepartmentService { + + @Resource + private DepartmentDao departmentDao; + + @Resource + private EmployeeDao employeeDao; + + @Resource + private DepartmentCacheManager departmentCacheManager; + + // ---------------------------- 增加、修改、删除 ---------------------------- + + /** + * 新增添加部门 + * + */ + + public ResponseDTO addDepartment(DepartmentAddForm departmentAddForm) { + DepartmentEntity departmentEntity = SmartBeanUtil.copy(departmentAddForm, DepartmentEntity.class); + departmentDao.insert(departmentEntity); + this.clearCache(); + return ResponseDTO.ok(); + } + + + /** + * 更新部门信息 + * + */ + public ResponseDTO updateDepartment(DepartmentUpdateForm updateDTO) { + if (updateDTO.getParentId() == null) { + return ResponseDTO.userErrorParam("父级部门id不能为空"); + } + DepartmentEntity entity = departmentDao.selectById(updateDTO.getDepartmentId()); + if (entity == null) { + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST); + } + DepartmentEntity departmentEntity = SmartBeanUtil.copy(updateDTO, DepartmentEntity.class); + departmentEntity.setSort(updateDTO.getSort()); + departmentDao.updateById(departmentEntity); + this.clearCache(); + return ResponseDTO.ok(); + } + + /** + * 根据id删除部门 + * 1、需要判断当前部门是否有子部门,有子部门则不允许删除 + * 2、需要判断当前部门是否有员工,有员工则不能删除 + * + */ + public ResponseDTO deleteDepartment(Long departmentId) { + DepartmentEntity departmentEntity = departmentDao.selectById(departmentId); + if (null == departmentEntity) { + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST); + } + // 是否有子级部门 + int subDepartmentNum = departmentDao.countSubDepartment(departmentId); + if (subDepartmentNum > 0) { + return ResponseDTO.userErrorParam("请先删除子级部门"); + } + + // 是否有未删除员工 + int employeeNum = employeeDao.countByDepartmentId(departmentId, Boolean.FALSE); + if (employeeNum > 0) { + return ResponseDTO.userErrorParam("请先删除部门员工"); + } + departmentDao.deleteById(departmentId); + // 清除缓存 + this.clearCache(); + return ResponseDTO.ok(); + } + + /** + * 清除自身以及下级的id列表缓存 + */ + private void clearCache() { + departmentCacheManager.clearCache(); + } + + // ---------------------------- 查询 ---------------------------- + + /** + * 分页查询 + * + * @param queryForm + * @return + */ + public PageResult queryPage(DepartmentQueryForm queryForm) { + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List list = departmentDao.queryPage(page, queryForm); + PageResult pageResult = SmartPageUtil.convert2PageResult(page, list); + return pageResult; + } + + /** + * 获取部门树形结构 + */ + public ResponseDTO> departmentTree() { + List treeVOList = departmentCacheManager.getDepartmentTree(); + return ResponseDTO.ok(treeVOList); + } + + + /** + * 自身以及所有下级的部门id列表 + * + */ + public List selfAndChildrenIdList(Long departmentId) { + return departmentCacheManager.getDepartmentSelfAndChildren(departmentId); + } + + + /** + * 获取所有部门 + * + */ + @Cacheable(AdminCacheConst.Department.DEPARTMENT_LIST_CACHE) + public List listAllDept() { + return departmentCacheManager.getDepartmentList(); + } + + public List listAll() { + return departmentCacheManager.getDepartmentList(); + } + + + /** + * 获取部门 + * + */ + public DepartmentVO getDepartmentById(Long departmentId) { + return departmentCacheManager.getDepartmentMap().get(departmentId); + } + + /** + * 获取部门路径:/公司/研发部/产品组 + */ + public String getDepartmentPath(Long departmentId) { + return departmentCacheManager.getDepartmentPathMap().get(departmentId); + } + + /** + * 查询全部父级部门(不包含自己) + * + */ + public List queryAllParentDepartment(Long departmentId) { + List list = new ArrayList<>(); + + Map departmentMap = departmentCacheManager.getDepartmentMap(); + DepartmentVO departmentVO = departmentMap.get(departmentId); + while (departmentVO != null) { + list.add(departmentVO); + departmentVO = departmentMap.get(departmentVO.getParentId()); + } + Collections.reverse(list); + return list; + } + + /** + * 查询全部父级部门(不包含自己) + * + */ + public List queryAllParentDepartmentIdList(Long departmentId) { + List list = new ArrayList<>(); + + Map departmentMap = departmentCacheManager.getDepartmentMap(); + DepartmentVO departmentVO = departmentMap.get(departmentId); + while (departmentVO != null) { + list.add(departmentVO.getDepartmentId()); + departmentVO = departmentMap.get(departmentVO.getParentId()); + } + Collections.reverse(list); + return list; + } + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/config/EmployeeConfig.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/config/EmployeeConfig.java new file mode 100644 index 0000000..e64959b --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/config/EmployeeConfig.java @@ -0,0 +1,23 @@ +package net.lab1024.sa.admin.module.system.employee.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 员工模块配置 + * + * @author 1024创新实验室 + */ +@Component +@ConfigurationProperties(prefix = EmployeeConfig.CONFIG_PREFIX) +@Data +public class EmployeeConfig { + + public static final String CONFIG_PREFIX = "smart.employee"; + + /** + * 默认密码,若未配置则使用随机密码 + */ + private String defaultPassword; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/controller/EmployeeController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/controller/EmployeeController.java new file mode 100644 index 0000000..96569bb --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/controller/EmployeeController.java @@ -0,0 +1,151 @@ +package net.lab1024.sa.admin.module.system.employee.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import net.lab1024.sa.admin.constant.AdminSwaggerTagConst; +import net.lab1024.sa.admin.module.system.employee.domain.form.*; +import net.lab1024.sa.admin.module.system.employee.domain.vo.EmployeeVO; +import net.lab1024.sa.admin.module.system.employee.service.EmployeeService; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartRequestUtil; +import net.lab1024.sa.base.module.support.apiencrypt.annotation.ApiDecrypt; +import net.lab1024.sa.base.module.support.securityprotect.service.Level3ProtectConfigService; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 员工 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021-12-09 22:57:49 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@RestController +@Tag(name = AdminSwaggerTagConst.System.SYSTEM_EMPLOYEE) +public class EmployeeController { + + @Resource + private EmployeeService employeeService; + + @Resource + private Level3ProtectConfigService level3ProtectConfigService; + + @PostMapping("/employee/query") + @Operation(summary = "员工管理查询 @author 卓大") + public ResponseDTO> query(@Valid @RequestBody EmployeeQueryForm query) { + return employeeService.queryEmployee(query); + } + + @PostMapping("/employee/queryUser") + @Operation(summary = "查询员工(用于被侵权人选择)@author AI") + public ResponseDTO> queryUser(@Valid @RequestBody EmployeeQueryForm query) { + return employeeService.queryUser(query); + } + + @Operation(summary = "添加员工(返回添加员工的密码) @author 卓大") + @PostMapping("/employee/add") + @SaCheckPermission("system:employee:add") + public ResponseDTO addEmployee(@Valid @RequestBody EmployeeAddForm employeeAddForm) { + return employeeService.addEmployee(employeeAddForm); + } + + @Operation(summary = "更新员工 @author 卓大") + @PostMapping("/employee/update") + @SaCheckPermission("system:employee:update") + public ResponseDTO updateEmployee(@Valid @RequestBody EmployeeUpdateForm employeeUpdateForm) { + return employeeService.updateEmployee(employeeUpdateForm); + } + + @Operation(summary = "更新员工个人中心信息 @author 善逸") + @PostMapping("/employee/update/center") + public ResponseDTO updateCenter(@Valid @RequestBody EmployeeUpdateCenterForm updateCenterForm) { + updateCenterForm.setEmployeeId(SmartRequestUtil.getRequestUserId()); + return employeeService.updateCenter(updateCenterForm); + } + + @Operation(summary = "更新登录人头像 @author 善逸") + @PostMapping("/employee/update/avatar") + public ResponseDTO updateAvatar(@Valid @RequestBody EmployeeUpdateAvatarForm employeeUpdateAvatarForm) { + employeeUpdateAvatarForm.setEmployeeId(SmartRequestUtil.getRequestUserId()); + return employeeService.updateAvatar(employeeUpdateAvatarForm); + } + + @Operation(summary = "更新员工禁用/启用状态 @author 卓大") + @GetMapping("/employee/update/disabled/{employeeId}") + @SaCheckPermission("system:employee:disabled") + public ResponseDTO updateDisableFlag(@PathVariable Long employeeId) { + return employeeService.updateDisableFlag(employeeId); + } + + @Operation(summary = "批量删除员工 @author 卓大") + @PostMapping("/employee/update/batch/delete") + @SaCheckPermission("system:employee:delete") + public ResponseDTO batchUpdateDeleteFlag(@RequestBody List employeeIdList) { + return employeeService.batchUpdateDeleteFlag(employeeIdList); + } + + @Operation(summary = "批量调整员工部门 @author 卓大") + @PostMapping("/employee/update/batch/department") + @SaCheckPermission("system:employee:department:update") + public ResponseDTO batchUpdateDepartment(@Valid @RequestBody EmployeeBatchUpdateDepartmentForm batchUpdateDepartmentForm) { + return employeeService.batchUpdateDepartment(batchUpdateDepartmentForm); + } + + @Operation(summary = "修改密码 @author 卓大") + @PostMapping("/employee/update/password") + @ApiDecrypt + public ResponseDTO updatePassword(@Valid @RequestBody EmployeeUpdatePasswordForm updatePasswordForm) { + updatePasswordForm.setEmployeeId(SmartRequestUtil.getRequestUserId()); + return employeeService.updatePassword(SmartRequestUtil.getRequestUser(), updatePasswordForm); + } + + @Operation(summary = "获取密码复杂度 @author 卓大") + @GetMapping("/employee/getPasswordComplexityEnabled") + @ApiDecrypt + public ResponseDTO getPasswordComplexityEnabled() { + return ResponseDTO.ok(level3ProtectConfigService.isPasswordComplexityEnabled()); + } + + @Operation(summary = "重置员工密码 @author 卓大") + @GetMapping("/employee/update/password/reset/{employeeId}") + @SaCheckPermission("system:employee:password:reset") + public ResponseDTO resetPassword(@PathVariable Long employeeId) { + return employeeService.resetPassword(employeeId); + } + + @Operation(summary = "查询员工-根据部门id @author 卓大") + @GetMapping("/employee/getAllEmployeeByDepartmentId/{departmentId}") + public ResponseDTO> getAllEmployeeByDepartmentId(@PathVariable Long departmentId) { + return employeeService.getAllEmployeeByDepartmentId(departmentId); + } + + @Operation(summary = "查询所有员工 @author 卓大") + @GetMapping("/employee/queryAll") + public ResponseDTO> queryAllEmployee(@RequestParam(value = "disabledFlag", required = false) Boolean disabledFlag) { + return employeeService.queryAllEmployee(disabledFlag); + } + + @Operation(summary = "获取当前用户信息(用于被侵权人选择) @author AI") + @GetMapping("/employee/getCurrentUserInfo") + public ResponseDTO getCurrentUserInfo() { + Long employeeId = SmartRequestUtil.getRequestUserId(); + if (employeeId == null) { + return ResponseDTO.ok(null); + } + return employeeService.getEmployeeById(employeeId); + } + + @Operation(summary = "根据警号查询员工信息 @author AI") + @GetMapping("/employee/getByPoliceNo/{policeNo}") + public ResponseDTO getByPoliceNo(@PathVariable String policeNo) { + return employeeService.getByPoliceNo(policeNo); + } + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/dao/EmployeeDao.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/dao/EmployeeDao.java new file mode 100644 index 0000000..8ac289c --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/dao/EmployeeDao.java @@ -0,0 +1,126 @@ +package net.lab1024.sa.admin.module.system.employee.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.admin.module.flow.domain.vo.WarmFlowInteractiveTypeVO; +import net.lab1024.sa.admin.module.system.employee.domain.entity.EmployeeEntity; +import net.lab1024.sa.admin.module.system.employee.domain.form.EmployeeQueryForm; +import net.lab1024.sa.admin.module.system.employee.domain.vo.EmployeeVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.Collection; +import java.util.List; + +/** + * 员工 dao + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021-12-09 22:57:49 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface EmployeeDao extends BaseMapper { + /** + * 查询员工列表 + */ + List queryEmployee(Page page, @Param("queryForm") EmployeeQueryForm queryForm, @Param("departmentIdList") List departmentIdList); + + /** + * 查询员工 + */ + List selectEmployeeByDisabledAndDeleted(@Param("disabledFlag") Boolean disabledFlag, @Param("deletedFlag") Boolean deletedFlag); + + List getNameByPostAndArea(@Param("areaCode")String areaCode, @Param("positionId")String positionId, @Param("roleId")String roleId, @Param("deptId")String deptId); + + /** + * 更新单个 + */ + void updateDisableFlag(@Param("employeeId") Long employeeId, @Param("disabledFlag") Boolean disabledFlag); + + /** + * 通过登录名查询 + */ + EmployeeEntity getByLoginName(@Param("loginName") String loginName, @Param("deletedFlag") Boolean deletedFlag); + + /** + * 通过姓名查询 + */ + EmployeeEntity getByActualName(@Param("actualName") String actualName, @Param("deletedFlag") Boolean deletedFlag); + + /** + * 通过手机号查询 + */ + EmployeeEntity getByPhone(@Param("phone") String phone, @Param("deletedFlag") Boolean deletedFlag); + + /** + * 通过邮箱账号查询 + */ + EmployeeEntity getByEmail(@Param("email") String email, @Param("deletedFlag") Boolean deletedFlag); + + /** + * 获取所有员工 + */ + List listAll(); + + /** + * 获取某个部门员工数 + */ + Integer countByDepartmentId(@Param("departmentId") Long departmentId, @Param("deletedFlag") Boolean deletedFlag); + + /** + * 获取一批员工 + */ + List getEmployeeByIds(@Param("employeeIds") Collection employeeIds); + + /** + * 查询单个员工信息 + */ + EmployeeVO getEmployeeById(@Param("employeeId") Long employeeId); + + /** + * 获取某个部门的员工 + */ + List selectByDepartmentId(@Param("departmentId") Long departmentId, @Param("deletedFlag") Boolean deletedFlag); + + /** + * 查询某些部门下用户名是xxx的员工 + */ + List selectByActualName(@Param("departmentIdList") List departmentIdList, @Param("actualName") String actualName, @Param("deletedFlag") Boolean deletedFlag); + + /** + * 获取某批部门的员工Id + */ + List getEmployeeIdByDepartmentIdList(@Param("departmentIds") List departmentIds, @Param("deletedFlag") Boolean deletedFlag); + + /** + * 获取所有 + */ + List getEmployeeId(@Param("leaveFlag") Boolean leaveFlag, @Param("deletedFlag") Boolean deletedFlag); + + /** + * 获取某个部门的员工Id + */ + List getEmployeeIdByDepartmentId(@Param("departmentId") Long departmentId, @Param("deletedFlag") Boolean deletedFlag); + + /** + * 员工重置密码 + */ + Integer updatePassword(@Param("employeeId") Long employeeId, @Param("password") String password); + + Page selectUserPage(@Param("page") Page page, @Param("warmFlowInteractiveTypeVo") WarmFlowInteractiveTypeVO warmFlowInteractiveTypeVo); + + Page selectNotUserPage(@Param("page") Page page, @Param("warmFlowInteractiveTypeVo") WarmFlowInteractiveTypeVO warmFlowInteractiveTypeVo); + + /** + * 根据警号查询员工信息 + * + * @param policeNo 警号 + * @return 员工信息 + */ + EmployeeVO getByPoliceNo(@Param("policeNo") String policeNo); +} \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/entity/EmployeeEntity.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/entity/EmployeeEntity.java new file mode 100644 index 0000000..b833220 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/entity/EmployeeEntity.java @@ -0,0 +1,125 @@ +package net.lab1024.sa.admin.module.system.employee.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 员工 实体表 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021-12-09 22:57:49 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName("t_employee") +public class EmployeeEntity { + + @TableId(type = IdType.AUTO) + private Long employeeId; + + /** + * 登录账号 + */ + private String loginName; + + /** + * 登录密码 + */ + private String loginPwd; + + /** + * 员工名称 + */ + private String actualName; + + /** + * 头像 + */ + private String avatar; + + /** + * 性别 + */ + private Integer gender; + + /** + * 手机号码 + */ + private String phone; + + /** + * 邮箱 + */ + private String email; + + /** + * 身份证号 + */ + private String idNo; + + /** + * 出生时间 + */ + private LocalDateTime birthday; + + /** + * 警官号 + */ + private String policeNo; + + /** + * 部门 id + */ + private Long departmentId; + + @TableField(exist = false) + private String deptName; + + /** + * 职务级别 ID + */ + private Long positionId; + + /** + * 诉求办理职位 ID 列表(多选,逗号分隔存储) + */ + private String positionIdList; + + /** + * 是否为超级管理员:0 不是,1 是 + */ + private Boolean administratorFlag; + + /** + * 是否被禁用 0否1是 + */ + private Boolean disabledFlag; + + /** + * 是否删除0否 1是 + */ + private Boolean deletedFlag; + + /** + * 备注 + */ + private String remark; + + /** + * 人员类型(1:民警;2:辅警) + */ + private Integer userType; + + private LocalDateTime updateTime; + + private LocalDateTime createTime; + + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/form/EmployeeAddForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/form/EmployeeAddForm.java new file mode 100644 index 0000000..42e3b43 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/form/EmployeeAddForm.java @@ -0,0 +1,89 @@ +package net.lab1024.sa.admin.module.system.employee.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import lombok.Data; +import net.lab1024.sa.admin.module.system.employee.enumeration.EmployeeUserTypeEnum; +import net.lab1024.sa.base.common.enumeration.GenderEnum; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.common.util.SmartVerificationUtil; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; +import org.hibernate.validator.constraints.Length; + +import java.util.List; + +/** + * 添加员工 + * + * @Author 1024创新实验室: 开云 + * @Date 2021-12-20 21:06:49 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class EmployeeAddForm { + + @Schema(description = "姓名") + @NotNull(message = "姓名不能为空") + @Length(max = 30, message = "姓名最多30字符") + private String actualName; + + @Schema(description = "登录账号") + @NotNull(message = "登录账号不能为空") + @Length(max = 30, message = "登录账号最多30字符") + private String loginName; + + @SchemaEnum(GenderEnum.class) + @CheckEnum(value = GenderEnum.class, message = "性别错误") + private Integer gender; + + @Schema(description = "部门id") + @NotNull(message = "部门id不能为空") + private Long departmentId; + + @Schema(description = "是否启用") + @NotNull(message = "是否被禁用不能为空") + private Boolean disabledFlag; + + @Schema(description = "手机号") + @NotNull(message = "手机号不能为空") + @Pattern(regexp = SmartVerificationUtil.PHONE_REGEXP, message = "手机号格式不正确") + private String phone; + + @Schema(description = "邮箱账号") + // @NotNull(message = "邮箱账号不能为空") + // @Pattern(regexp = SmartVerificationUtil.EMAIL, message = "邮箱账号格式不正确") + private String email; + + @Schema(description = "身份证号") + @Length(max = 18, message = "身份证号最多 18 字符") + private String idNo; + + @Schema(description = "出生时间") + private java.time.LocalDateTime birthday; + + @Schema(description = "警官号") + @Length(max = 50, message = "警官号最多 50 字符") + private String policeNo; + + @Schema(description = "职务级别 ID") + private Long positionId; + + @Schema(description = "诉求办理职位 ID 列表(多选)") + private List positionIdList; + + @Schema(description = "角色列表") + private List roleIdList; + + @Schema(description = "备注") + @Length(max = 200, message = "备注最多200字符") + private String remark; + + @SchemaEnum(EmployeeUserTypeEnum.class) + @NotNull(message = "人员类型不能为空") + @CheckEnum(value = EmployeeUserTypeEnum.class, message = "人员类型错误") + private Integer userType; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/form/EmployeeBatchUpdateDepartmentForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/form/EmployeeBatchUpdateDepartmentForm.java new file mode 100644 index 0000000..737f2c3 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/form/EmployeeBatchUpdateDepartmentForm.java @@ -0,0 +1,31 @@ +package net.lab1024.sa.admin.module.system.employee.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +import java.util.List; + +/** + * 员工更新部门 + * + * @Author 1024创新实验室: 开云 + * @Date 2021-12-20 21:06:49 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class EmployeeBatchUpdateDepartmentForm { + + @Schema(description = "员工id") + @NotEmpty(message = "员工id不能为空") + @Size(max = 99, message = "一次最多调整99个员工") + private List employeeIdList; + + @Schema(description = "部门ID") + @NotNull(message = "部门ID不能为空") + private Long departmentId; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/form/EmployeeQueryForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/form/EmployeeQueryForm.java new file mode 100644 index 0000000..7d89c45 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/form/EmployeeQueryForm.java @@ -0,0 +1,56 @@ +package net.lab1024.sa.admin.module.system.employee.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Size; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; +import org.hibernate.validator.constraints.Length; + +import java.time.LocalDate; +import java.util.List; + +/** + * 员工列表 + * + * @Author 1024创新实验室: 开云 + * @Date 2021-12-20 21:06:49 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class EmployeeQueryForm extends PageParam { + + @Schema(description = "搜索词") + @Length(max = 20, message = "搜索词最多20字符") + private String keyword; + + @Schema(description = "人员类型") + @Length(max = 20, message = "人员类型最多20字符") + private String policeType; + + @Schema(description = "部门id") + private Long departmentId; + + @Schema(description = "开始时间") + private LocalDate startTime; + + @Schema(description = "结束时间") + private LocalDate endTime; + + @Schema(description = "是否禁用") + private Boolean disabledFlag; + + @Schema(description = "员工id集合") + @Size(max = 99, message = "最多查询99个员工") + private List employeeIdList; + + @Schema(description = "机构编码前6位(用于被侵权人选择时限制同机构用户)") + private String institutionCodePrefix; + + @Schema(description = "删除标识", hidden = true) + private Boolean deletedFlag; + + + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/form/EmployeeUpdateAvatarForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/form/EmployeeUpdateAvatarForm.java new file mode 100644 index 0000000..03feb56 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/form/EmployeeUpdateAvatarForm.java @@ -0,0 +1,25 @@ +package net.lab1024.sa.admin.module.system.employee.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +/** + * 修改登录人头像 + * + * @Author 1024创新实验室: 善逸 + * @Date 2024年6月30日00:26:35 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class EmployeeUpdateAvatarForm { + + @Schema(hidden = true) + private Long employeeId; + + @Schema(description = "头像") + @NotBlank(message = "头像不能为空哦") + private String avatar; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/form/EmployeeUpdateCenterForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/form/EmployeeUpdateCenterForm.java new file mode 100644 index 0000000..ad4e88b --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/form/EmployeeUpdateCenterForm.java @@ -0,0 +1,63 @@ +package net.lab1024.sa.admin.module.system.employee.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import lombok.Data; +import net.lab1024.sa.base.common.enumeration.GenderEnum; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.common.util.SmartVerificationUtil; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; +import org.hibernate.validator.constraints.Length; + +/** + * 更新员工个人中心信息 + * + * @Author 1024创新实验室: 开云 + * @Date 2021-12-20 21:06:49 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class EmployeeUpdateCenterForm { + + @Schema(hidden = true) + private Long employeeId; + + @Schema(description = "姓名") + @NotNull(message = "姓名不能为空") + @Length(max = 30, message = "姓名最多30字符") + private String actualName; + + @SchemaEnum(GenderEnum.class) + @CheckEnum(value = GenderEnum.class, message = "性别错误") + private Integer gender; + + @Schema(description = "手机号") + @NotNull(message = "手机号不能为空") + @Pattern(regexp = SmartVerificationUtil.PHONE_REGEXP, message = "手机号格式不正确") + private String phone; + + @Schema(description = "邮箱账号") + @Pattern(regexp = SmartVerificationUtil.EMAIL, message = "邮箱账号格式不正确") + private String email; + + @Schema(description = "身份证号") + @Length(max = 18, message = "身份证号最多 18 字符") + private String idNo; + + @Schema(description = "警官号") + @Length(max = 50, message = "警官号最多 50 字符") + private String policeNo; + + @Schema(description = "职务级别 ID") + private Long positionId; + + @Schema(description = "头像") + private String avatar; + + @Schema(description = "备注") + @Length(max = 200, message = "备注最多200字符") + private String remark; +} \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/form/EmployeeUpdateForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/form/EmployeeUpdateForm.java new file mode 100644 index 0000000..8a72c79 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/form/EmployeeUpdateForm.java @@ -0,0 +1,22 @@ +package net.lab1024.sa.admin.module.system.employee.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 更新员工 + * + * @Author 1024创新实验室: 开云 + * @Date 2021-12-20 21:06:49 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class EmployeeUpdateForm extends EmployeeAddForm { + + @Schema(description = "员工id") + @NotNull(message = "员工id不能为空") + private Long employeeId; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/form/EmployeeUpdatePasswordForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/form/EmployeeUpdatePasswordForm.java new file mode 100644 index 0000000..f3d96ba --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/form/EmployeeUpdatePasswordForm.java @@ -0,0 +1,31 @@ +package net.lab1024.sa.admin.module.system.employee.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.Data; +import net.lab1024.sa.base.common.util.SmartVerificationUtil; + +/** + * 修改密码所需参数 + * + * @Author 1024创新实验室: 开云 + * @Date 2021-12-20 21:06:49 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class EmployeeUpdatePasswordForm { + + @Schema(hidden = true) + private Long employeeId; + + @Schema(description = "原密码") + @NotBlank(message = "原密码不能为空哦") + private String oldPassword; + + @Schema(description = "新密码") + @NotBlank(message = "新密码不能为空哦") + private String newPassword; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/form/EmployeeUpdateRoleForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/form/EmployeeUpdateRoleForm.java new file mode 100644 index 0000000..1f3dc2c --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/form/EmployeeUpdateRoleForm.java @@ -0,0 +1,30 @@ +package net.lab1024.sa.admin.module.system.employee.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +import java.util.List; + +/** + * 员工更新角色 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2021-12-20 20:55:13 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class EmployeeUpdateRoleForm { + + @Schema(description = "员工id") + @NotNull(message = "员工id不能为空") + private Long employeeId; + + @Schema(description = "角色ids") + @Size(max = 99, message = "角色最多99") + private List roleIdList; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/vo/EmployeeVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/vo/EmployeeVO.java new file mode 100644 index 0000000..baccb6c --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/domain/vo/EmployeeVO.java @@ -0,0 +1,88 @@ +package net.lab1024.sa.admin.module.system.employee.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.admin.module.system.employee.enumeration.EmployeeUserTypeEnum; +import net.lab1024.sa.base.common.enumeration.GenderEnum; +import net.lab1024.sa.base.common.swagger.SchemaEnum; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 员工信息 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2021-12-21 23:05:56 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class EmployeeVO { + + @Schema(description = "主键id") + private Long employeeId; + + @Schema(description = "登录账号") + private String loginName; + + @SchemaEnum(GenderEnum.class) + private Integer gender; + + @Schema(description = "员工名称") + private String actualName; + + @Schema(description = "手机号码") + private String phone; + + + @Schema(description = "身份证号") + private String idNo; + + @Schema(description = "出生时间") + private LocalDateTime birthday; + + @Schema(description = "警官号") + private String policeNo; + + @Schema(description = "部门 id") + private Long departmentId; + + @Schema(description = "是否被禁用") + private Boolean disabledFlag; + + @Schema(description = "是否 超级管理员") + private Boolean administratorFlag; + + @Schema(description = "部门名称") + private String departmentName; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "角色列表") + private List roleIdList; + + @Schema(description = "角色名称列表") + private List roleNameList; + + @Schema(description = "职务 ID") + private Long positionId; + + @Schema(description = "职务名称") + private String positionName; + + @Schema(description = "诉求办理职位 ID 列表(多选)") + private String positionIdList; + + @Schema(description = "邮箱") + private String email; + + @SchemaEnum(EmployeeUserTypeEnum.class) + private Integer userType; + + @Schema(description = "机构警种(用于被侵权人选择时显示)") + private String institutionPoliceType; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/enumeration/EmployeeUserTypeEnum.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/enumeration/EmployeeUserTypeEnum.java new file mode 100644 index 0000000..b1c02c3 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/enumeration/EmployeeUserTypeEnum.java @@ -0,0 +1,23 @@ +package net.lab1024.sa.admin.module.system.employee.enumeration; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 员工人员类型枚举 + * 1:民警;2:辅警 + * + * @author 1024创新实验室 + */ +@AllArgsConstructor +@Getter +public enum EmployeeUserTypeEnum implements BaseEnum { + + POLICE_OFFICER(1, "民警"), + AUXILIARY_POLICE(2, "辅警"); + + private final Integer value; + + private final String desc; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/manager/EmployeeManager.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/manager/EmployeeManager.java new file mode 100644 index 0000000..23d05d1 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/manager/EmployeeManager.java @@ -0,0 +1,86 @@ +package net.lab1024.sa.admin.module.system.employee.manager; + + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import jakarta.annotation.Resource; +import net.lab1024.sa.admin.module.system.employee.dao.EmployeeDao; +import net.lab1024.sa.admin.module.system.employee.domain.entity.EmployeeEntity; +import net.lab1024.sa.admin.module.system.role.dao.RoleEmployeeDao; +import net.lab1024.sa.admin.module.system.role.domain.entity.RoleEmployeeEntity; +import net.lab1024.sa.admin.module.system.role.service.RoleEmployeeService; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * 员工 manager + * + * @Author 1024创新实验室: 胡克 + * @Date 2021-12-29 21:52:46 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class EmployeeManager extends ServiceImpl { + + @Resource + private EmployeeDao employeeDao; + + @Resource + private RoleEmployeeService roleEmployeeService; + + @Resource + private RoleEmployeeDao roleEmployeeDao; + + /** + * 保存员工 + * + */ + @Transactional(rollbackFor = Throwable.class) + public void saveEmployee(EmployeeEntity employee, List roleIdList) { + // 保存员工 获得id + employeeDao.insert(employee); + + if (CollectionUtils.isNotEmpty(roleIdList)) { + List roleEmployeeList = roleIdList.stream().map(e -> new RoleEmployeeEntity(e, employee.getEmployeeId())).collect(Collectors.toList()); + roleEmployeeService.batchInsert(roleEmployeeList); + } + } + + /** + * 更新员工 + * + */ + @Transactional(rollbackFor = Throwable.class) + public void updateEmployee(EmployeeEntity employee, List roleIdList) { + // 保存员工 获得id + employeeDao.updateById(employee); + + // 若为空,则删除所有角色 + if (CollectionUtils.isEmpty(roleIdList)) { + roleEmployeeDao.deleteByEmployeeId(employee.getEmployeeId()); + return; + } + + List roleEmployeeList = roleIdList.stream().map(e -> new RoleEmployeeEntity(e, employee.getEmployeeId())).collect(Collectors.toList()); + this.updateEmployeeRole(employee.getEmployeeId(), roleEmployeeList); + } + + /** + * 更新员工角色 + */ + @Transactional(rollbackFor = Throwable.class) + public void updateEmployeeRole(Long employeeId, List roleEmployeeList) { + + roleEmployeeDao.deleteByEmployeeId(employeeId); + + if (CollectionUtils.isNotEmpty(roleEmployeeList)) { + roleEmployeeService.batchInsert(roleEmployeeList); + } + } + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/service/EmployeeService.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/service/EmployeeService.java new file mode 100644 index 0000000..fdd4cb4 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/employee/service/EmployeeService.java @@ -0,0 +1,583 @@ +package net.lab1024.sa.admin.module.system.employee.service; + +import cn.dev33.satoken.stp.StpUtil; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.google.common.collect.Lists; +import jakarta.annotation.Resource; +import net.lab1024.sa.admin.constant.AdminCacheConst; +import net.lab1024.sa.admin.module.flow.domain.vo.WarmFlowInteractiveTypeVO; +import net.lab1024.sa.admin.module.system.datascope.constant.DataScopeViewTypeEnum; +import net.lab1024.sa.admin.module.system.department.dao.DepartmentDao; +import net.lab1024.sa.admin.module.system.department.domain.entity.DepartmentEntity; +import net.lab1024.sa.admin.module.system.department.domain.vo.DepartmentVO; +import net.lab1024.sa.admin.module.system.department.service.DepartmentService; +import net.lab1024.sa.admin.module.system.employee.config.EmployeeConfig; +import net.lab1024.sa.admin.module.system.employee.dao.EmployeeDao; +import net.lab1024.sa.admin.module.system.employee.domain.entity.EmployeeEntity; +import net.lab1024.sa.admin.module.system.employee.domain.form.*; +import net.lab1024.sa.admin.module.system.employee.domain.vo.EmployeeVO; +import net.lab1024.sa.admin.module.system.employee.manager.EmployeeManager; +import net.lab1024.sa.admin.module.system.login.domain.LoginResultVO; +import net.lab1024.sa.admin.module.system.login.service.LoginService; +import net.lab1024.sa.admin.module.system.position.dao.PositionDao; +import net.lab1024.sa.admin.module.system.position.domain.entity.PositionEntity; +import net.lab1024.sa.admin.module.system.role.dao.RoleDataScopeDao; +import net.lab1024.sa.admin.module.system.role.dao.RoleEmployeeDao; +import net.lab1024.sa.admin.module.system.role.domain.entity.RoleDataScopeEntity; +import net.lab1024.sa.admin.module.system.role.domain.vo.RoleEmployeeVO; +import net.lab1024.sa.admin.module.system.role.manager.RoleDataScopeManager; +import net.lab1024.sa.admin.util.AdminRequestUtil; +import net.lab1024.sa.base.common.code.UserErrorCode; +import net.lab1024.sa.base.common.constant.StringConst; +import net.lab1024.sa.base.common.domain.PageParam; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.RequestUser; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.enumeration.UserTypeEnum; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.constant.CacheKeyConst; +import net.lab1024.sa.base.module.support.securityprotect.service.SecurityPasswordService; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * 员工 service + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021-12-29 21:52:46 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class EmployeeService { + + @Resource + private EmployeeDao employeeDao; + + @Resource + private DepartmentDao departmentDao; + + @Resource + private EmployeeManager employeeManager; + + @Resource + private RoleEmployeeDao roleEmployeeDao; + + @Resource + private DepartmentService departmentService; + + @Resource + private SecurityPasswordService securityPasswordService; + + @Resource + @Lazy + private LoginService loginService; + + @Resource + private PositionDao positionDao; + + @Resource + private EmployeeConfig employeeConfig; + + @Resource + private RoleDataScopeDao roleDataScopeDao; + + public EmployeeEntity getById(Long employeeId) { + return employeeDao.selectById(employeeId); + } + + + /** + * 查询员工列表 + */ + public ResponseDTO> queryEmployee(EmployeeQueryForm employeeQueryForm) { + employeeQueryForm.setDeletedFlag(false); + Page pageParam = SmartPageUtil.convert2PageQuery(employeeQueryForm); + + List departmentIdList = new ArrayList<>(); + if (employeeQueryForm.getDepartmentId() != null) { + departmentIdList.addAll(departmentService.selfAndChildrenIdList(employeeQueryForm.getDepartmentId())); + } + + LoginResultVO loginResult = loginService.getLoginResult(AdminRequestUtil.getRequestUser(), StpUtil.getTokenValue()); + String areaCode = loginResult.getDepartmentCode().substring(0, 6).replaceAll("00+$",""); + employeeQueryForm.setInstitutionCodePrefix(areaCode); + List employeeList = employeeDao.queryEmployee(pageParam, employeeQueryForm, departmentIdList); + if (CollectionUtils.isEmpty(employeeList)) { + PageResult pageResult = SmartPageUtil.convert2PageResult(pageParam, employeeList); + return ResponseDTO.ok(pageResult); + } + + // 查询员工角色 + List employeeIdList = employeeList.stream().map(EmployeeVO::getEmployeeId).collect(Collectors.toList()); + List roleEmployeeEntityList = employeeIdList.isEmpty() ? Collections.emptyList() : roleEmployeeDao.selectRoleByEmployeeIdList(employeeIdList); + Map> employeeRoleIdListMap = roleEmployeeEntityList.stream().collect(Collectors.groupingBy(RoleEmployeeVO::getEmployeeId, Collectors.mapping(RoleEmployeeVO::getRoleId, Collectors.toList()))); + Map> employeeRoleNameListMap = roleEmployeeEntityList.stream().collect(Collectors.groupingBy(RoleEmployeeVO::getEmployeeId, Collectors.mapping(RoleEmployeeVO::getRoleName, Collectors.toList()))); + + // 查询员工职位 +// List positionIdList = employeeList.stream().map(EmployeeVO::getPositionId).filter(Objects::nonNull).collect(Collectors.toList()); +// List positionEntityList = positionIdList.isEmpty() ? Collections.emptyList() : positionDao.selectBatchIds(positionIdList); +// Map positionNameMap = positionEntityList.stream().collect(Collectors.toMap(PositionEntity::getPositionId, PositionEntity::getPositionName)); + + employeeList.forEach(e -> { + e.setRoleIdList(employeeRoleIdListMap.getOrDefault(e.getEmployeeId(), Lists.newArrayList())); + e.setRoleNameList(employeeRoleNameListMap.getOrDefault(e.getEmployeeId(), Lists.newArrayList())); + e.setDepartmentName(departmentService.getDepartmentPath(e.getDepartmentId())); +// e.setPositionName(positionNameMap.get(e.getPositionId())); + // 注意:此时 e.positionIdList 已经是 List 类型,由 MyBatis 自动映射 + }); + PageResult pageResult = SmartPageUtil.convert2PageResult(pageParam, employeeList); + return ResponseDTO.ok(pageResult); + } + + /** + * 查询员工列表 + */ + public PageResult queryEmployeePage(EmployeeQueryForm employeeQueryForm) { + employeeQueryForm.setDeletedFlag(false); + Page pageParam = SmartPageUtil.convert2PageQuery(employeeQueryForm); + + List departmentIdList = new ArrayList<>(); + if (employeeQueryForm.getDepartmentId() != null) { + departmentIdList.addAll(departmentService.selfAndChildrenIdList(employeeQueryForm.getDepartmentId())); + } + + List employeeList = employeeDao.queryEmployee(pageParam, employeeQueryForm, departmentIdList); + if (CollectionUtils.isEmpty(employeeList)) { + PageResult pageResult = SmartPageUtil.convert2PageResult(pageParam, employeeList); + return pageResult; + } + + // 查询员工角色 + List employeeIdList = employeeList.stream().map(EmployeeVO::getEmployeeId).collect(Collectors.toList()); + List roleEmployeeEntityList = employeeIdList.isEmpty() ? Collections.emptyList() : roleEmployeeDao.selectRoleByEmployeeIdList(employeeIdList); + Map> employeeRoleIdListMap = roleEmployeeEntityList.stream().collect(Collectors.groupingBy(RoleEmployeeVO::getEmployeeId, Collectors.mapping(RoleEmployeeVO::getRoleId, Collectors.toList()))); + Map> employeeRoleNameListMap = roleEmployeeEntityList.stream().collect(Collectors.groupingBy(RoleEmployeeVO::getEmployeeId, Collectors.mapping(RoleEmployeeVO::getRoleName, Collectors.toList()))); + + // 查询员工职位 + List positionIdList = employeeList.stream().map(EmployeeVO::getPositionId).filter(Objects::nonNull).collect(Collectors.toList()); + List positionEntityList = positionIdList.isEmpty() ? Collections.emptyList() : positionDao.selectBatchIds(positionIdList); + Map positionNameMap = positionEntityList.stream().collect(Collectors.toMap(PositionEntity::getPositionId, PositionEntity::getPositionName)); + + employeeList.forEach(e -> { + e.setRoleIdList(employeeRoleIdListMap.getOrDefault(e.getEmployeeId(), Lists.newArrayList())); + e.setRoleNameList(employeeRoleNameListMap.getOrDefault(e.getEmployeeId(), Lists.newArrayList())); + e.setDepartmentName(departmentService.getDepartmentPath(e.getDepartmentId())); + e.setPositionName(positionNameMap.get(e.getPositionId())); + }); + PageResult pageResult = SmartPageUtil.convert2PageResult(pageParam, employeeList); + return pageResult; + } + + /** + * 新增员工 + */ + public synchronized ResponseDTO addEmployee(EmployeeAddForm employeeAddForm) { + // 校验登录名是否重复 + EmployeeEntity employeeEntity = employeeDao.getByLoginName(employeeAddForm.getLoginName(), null); + if (null != employeeEntity) { + return ResponseDTO.userErrorParam("登录名重复"); + } + // 校验电话是否存在 + employeeEntity = employeeDao.getByPhone(employeeAddForm.getPhone(), null); + if (null != employeeEntity) { + return ResponseDTO.userErrorParam("手机号已存在"); + } + // 部门是否存在 + Long departmentId = employeeAddForm.getDepartmentId(); + DepartmentEntity department = departmentDao.selectById(departmentId); + if (department == null) { + return ResponseDTO.userErrorParam("部门不存在"); + } + + EmployeeEntity entity = SmartBeanUtil.copy(employeeAddForm, EmployeeEntity.class); + + // 处理诉求办理职位列表,将 List 转换为逗号分隔的字符串 + if (employeeAddForm.getPositionIdList() != null && !employeeAddForm.getPositionIdList().isEmpty()) { + entity.setPositionIdList(String.join(",", employeeAddForm.getPositionIdList().stream().map(String::valueOf).toArray(String[]::new))); + } + + // 设置密码:优先使用配置文件的默认密码,若未配置则使用随机密码 + String password = employeeConfig.getDefaultPassword(); + if (password == null || password.isEmpty()) { + password = securityPasswordService.randomPassword(); + } + entity.setLoginPwd(SecurityPasswordService.getEncryptPwd(password)); + + // 保存数据 + entity.setDeletedFlag(Boolean.FALSE); + employeeManager.saveEmployee(entity, employeeAddForm.getRoleIdList()); + + return ResponseDTO.ok(password); + } + + /** + * 更新员工 + */ + public synchronized ResponseDTO updateEmployee(EmployeeUpdateForm employeeUpdateForm) { + + Long employeeId = employeeUpdateForm.getEmployeeId(); + EmployeeEntity employeeEntity = employeeDao.selectById(employeeId); + if (null == employeeEntity) { + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST); + } + + // 部门是否存在 + Long departmentId = employeeUpdateForm.getDepartmentId(); + DepartmentEntity departmentEntity = departmentDao.selectById(departmentId); + if (departmentEntity == null) { + return ResponseDTO.userErrorParam("部门不存在"); + } + + // 检查唯一性 + ResponseDTO checkResponse = checkUniqueness(employeeId, employeeUpdateForm.getLoginName(), employeeUpdateForm.getPhone(), employeeUpdateForm.getEmail()); + if (!checkResponse.getOk()) { + return checkResponse; + } + + EmployeeEntity entity = SmartBeanUtil.copy(employeeUpdateForm, EmployeeEntity.class); + // 不更新密码 + entity.setLoginPwd(null); + + // 处理诉求办理职位列表,将 List 转换为逗号分隔的字符串 + if (employeeUpdateForm.getPositionIdList() != null) { + if (employeeUpdateForm.getPositionIdList().isEmpty()) { + entity.setPositionIdList(null); + } else { + entity.setPositionIdList(String.join(",", employeeUpdateForm.getPositionIdList().stream().map(String::valueOf).toArray(String[]::new))); + } + } + + // 更新数据 + employeeManager.updateEmployee(entity, employeeUpdateForm.getRoleIdList()); + + // 清除员工缓存 + loginService.clearLoginEmployeeCache(employeeId); + + return ResponseDTO.ok(); + } + + /** + * 更新员工个人中心信息 + */ + public ResponseDTO updateCenter(EmployeeUpdateCenterForm updateCenterForm) { + + Long employeeId = updateCenterForm.getEmployeeId(); + EmployeeEntity employeeEntity = employeeDao.selectById(employeeId); + if (null == employeeEntity) { + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST); + } + + // 检查唯一性 登录账号不能修改则不需要检查 + ResponseDTO checkResponse = checkUniqueness(employeeId, "", updateCenterForm.getPhone(), updateCenterForm.getEmail()); + if (!checkResponse.getOk()) { + return checkResponse; + } + + EmployeeEntity employee = SmartBeanUtil.copy(updateCenterForm, EmployeeEntity.class); + // 不更新密码 + employee.setLoginPwd(null); + + // 更新数据 + employeeDao.updateById(employee); + + // 清除员工缓存 + loginService.clearLoginEmployeeCache(employeeId); + + return ResponseDTO.ok(); + } + + /** + * 检查唯一性 + */ + private ResponseDTO checkUniqueness(Long employeeId, String loginName, String phone, String email) { + EmployeeEntity existEntity = employeeDao.getByLoginName(loginName, null); + if (null != existEntity && !Objects.equals(existEntity.getEmployeeId(), employeeId)) { + return ResponseDTO.userErrorParam("登录名重复"); + } + + existEntity = employeeDao.getByPhone(phone, null); + if (null != existEntity && !Objects.equals(existEntity.getEmployeeId(), employeeId)) { + return ResponseDTO.userErrorParam("手机号已存在"); + } + + existEntity = employeeDao.getByEmail(email, null); + if (null != existEntity && !Objects.equals(existEntity.getEmployeeId(), employeeId)) { + return ResponseDTO.userErrorParam("邮箱账号已存在"); + } + + return ResponseDTO.ok(); + } + + /** + * 更新登录人头像 + * + * @param employeeUpdateAvatarForm + * @return + */ + public ResponseDTO updateAvatar(EmployeeUpdateAvatarForm employeeUpdateAvatarForm) { + Long employeeId = employeeUpdateAvatarForm.getEmployeeId(); + EmployeeEntity employeeEntity = employeeDao.selectById(employeeId); + if (employeeEntity == null) { + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST); + } + // 更新头像 + EmployeeEntity updateEntity = new EmployeeEntity(); + updateEntity.setEmployeeId(employeeId); + updateEntity.setAvatar(employeeUpdateAvatarForm.getAvatar()); + employeeDao.updateById(updateEntity); + + // 清除员工缓存 + loginService.clearLoginEmployeeCache(employeeId); + return ResponseDTO.ok(); + } + + /** + * 更新禁用/启用状态 + */ + public ResponseDTO updateDisableFlag(Long employeeId) { + if (null == employeeId) { + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST); + } + EmployeeEntity employeeEntity = employeeDao.selectById(employeeId); + if (null == employeeEntity) { + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST); + } + employeeDao.updateDisableFlag(employeeId, !employeeEntity.getDisabledFlag()); + + if (employeeEntity.getDisabledFlag()) { + // 强制退出登录 + StpUtil.logout(UserTypeEnum.ADMIN_EMPLOYEE.getValue() + StringConst.COLON + employeeId); + } + + return ResponseDTO.ok(); + } + + /** + * 批量删除员工 + */ + public ResponseDTO batchUpdateDeleteFlag(List employeeIdList) { + if (CollectionUtils.isEmpty(employeeIdList)) { + return ResponseDTO.ok(); + } + List employeeEntityList = employeeManager.listByIds(employeeIdList); + if (CollectionUtils.isEmpty(employeeEntityList)) { + return ResponseDTO.ok(); + } + // 更新删除 + List deleteList = employeeIdList.stream().map(e -> { + EmployeeEntity updateEmployee = new EmployeeEntity(); + updateEmployee.setEmployeeId(e); + updateEmployee.setDeletedFlag(true); + return updateEmployee; + }).collect(Collectors.toList()); + employeeManager.updateBatchById(deleteList); + + for (Long employeeId : employeeIdList) { + // 强制退出登录 + StpUtil.logout(UserTypeEnum.ADMIN_EMPLOYEE.getValue() + StringConst.COLON + employeeId); + } + return ResponseDTO.ok(); + } + + + /** + * 批量更新部门 + */ + public ResponseDTO batchUpdateDepartment(EmployeeBatchUpdateDepartmentForm batchUpdateDepartmentForm) { + List employeeIdList = batchUpdateDepartmentForm.getEmployeeIdList(); + List employeeEntityList = employeeDao.selectBatchIds(employeeIdList); + if (employeeIdList.size() != employeeEntityList.size()) { + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST); + } + // 更新 + List updateList = employeeIdList.stream().map(e -> { + EmployeeEntity updateEmployee = new EmployeeEntity(); + updateEmployee.setEmployeeId(e); + updateEmployee.setDepartmentId(batchUpdateDepartmentForm.getDepartmentId()); + return updateEmployee; + }).collect(Collectors.toList()); + employeeManager.updateBatchById(updateList); + + return ResponseDTO.ok(); + } + + + /** + * 更新密码 + */ + @Transactional(rollbackFor = Throwable.class) + public ResponseDTO updatePassword(RequestUser requestUser, EmployeeUpdatePasswordForm updatePasswordForm) { + Long employeeId = updatePasswordForm.getEmployeeId(); + EmployeeEntity employeeEntity = employeeDao.selectById(employeeId); + if (employeeEntity == null) { + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST); + } + + // 校验原始密码 + if (!SecurityPasswordService.matchesPwd(updatePasswordForm.getOldPassword(),employeeEntity.getLoginPwd()) ) { + return ResponseDTO.userErrorParam("原密码有误,请重新输入"); + } + + // 新旧密码相同 + if (Objects.equals(updatePasswordForm.getOldPassword(), updatePasswordForm.getNewPassword()) ){ + return ResponseDTO.userErrorParam("新密码与原始密码相同,请重新输入"); + } + + // 校验密码复杂度 + ResponseDTO validatePassComplexity = securityPasswordService.validatePasswordComplexity(updatePasswordForm.getNewPassword()); + if (!validatePassComplexity.getOk()) { + return validatePassComplexity; + } + + // 根据三级等保规则,校验密码是否重复 + ResponseDTO passwordRepeatTimes = securityPasswordService.validatePasswordRepeatTimes(requestUser, updatePasswordForm.getNewPassword()); + if (!passwordRepeatTimes.getOk()) { + return ResponseDTO.error(passwordRepeatTimes); + } + + + // 更新密码 + String newEncryptPassword = SecurityPasswordService.getEncryptPwd(updatePasswordForm.getNewPassword()); + EmployeeEntity updateEntity = new EmployeeEntity(); + updateEntity.setEmployeeId(employeeId); + updateEntity.setLoginPwd(newEncryptPassword); + employeeDao.updateById(updateEntity); + + // 保存修改密码密码记录 + securityPasswordService.saveUserChangePasswordLog(requestUser, newEncryptPassword, employeeEntity.getLoginPwd()); + + return ResponseDTO.ok(); + } + + /** + * 获取某个部门的员工信息 + */ + public ResponseDTO> getAllEmployeeByDepartmentId(Long departmentId) { + List employeeEntityList = employeeDao.selectByDepartmentId(departmentId, Boolean.FALSE); + + if (CollectionUtils.isEmpty(employeeEntityList)) { + return ResponseDTO.ok(Collections.emptyList()); + } + + DepartmentVO department = departmentService.getDepartmentById(departmentId); + + List voList = employeeEntityList.stream().map(e -> { + EmployeeVO employeeVO = SmartBeanUtil.copy(e, EmployeeVO.class); + if (department != null) { + employeeVO.setDepartmentName(department.getName()); + } + return employeeVO; + }).collect(Collectors.toList()); + return ResponseDTO.ok(voList); + } + + + /** + * 重置密码 + */ + public ResponseDTO resetPassword(Long employeeId) { + // 设置密码:优先使用配置文件的默认密码,若未配置则使用随机密码 + String password = employeeConfig.getDefaultPassword(); + if (password == null || password.isEmpty()) { + password = securityPasswordService.randomPassword(); + } + employeeDao.updatePassword(employeeId, SecurityPasswordService.getEncryptPwd(password)); + return ResponseDTO.ok(password); + } + + + /** + * 查询全部员工 + */ + @Cacheable(AdminCacheConst.Employee.EMPLOEE_LIST_CACHE) + public ResponseDTO> queryAllEmployee(Boolean disabledFlag) { + List employeeList = employeeDao.selectEmployeeByDisabledAndDeleted(disabledFlag, Boolean.FALSE); + return ResponseDTO.ok(employeeList); + } + + public List getNameByPostAndArea(String areaCode, String positionId, String roleId, String deptId) { + return employeeDao.getNameByPostAndArea(areaCode, positionId, roleId, deptId); + } + + /** + * 根据员工ID获取员工信息 + */ + public ResponseDTO getEmployeeById(Long employeeId) { + EmployeeVO employeeVO = employeeDao.getEmployeeById(employeeId); + return ResponseDTO.ok(employeeVO); + } + + /** + * 根据登录名获取员工 + */ + public EmployeeEntity getByLoginName(String loginName) { + return employeeDao.getByLoginName(loginName, false); + } + + /** + * 根据条件分页查询不等于用户列表的所有用户 + * + * @param warmFlowInteractiveTypeVo 用户编号集合 + * @return 用户信息集合信息 + */ + public Page selectNotUserList(Page page, WarmFlowInteractiveTypeVO warmFlowInteractiveTypeVo){ + return employeeDao.selectNotUserPage(page, warmFlowInteractiveTypeVo); + } + + /** + * 根据条件分页查询不等于用户列表的所有用户 + * + * @param warmFlowInteractiveTypeVo 用户编号集合 + * @return 用户信息集合信息 + */ + public Page selectUserList(Page page, WarmFlowInteractiveTypeVO warmFlowInteractiveTypeVo){ + return employeeDao.selectUserPage(page, warmFlowInteractiveTypeVo); + } + + /** + * 查询员工列表(用于被侵权人选择) + * 只查询当前机构编码前6位相等的用户,并关联查询机构警种 + */ + public ResponseDTO> queryUser(EmployeeQueryForm employeeQueryForm) { + employeeQueryForm.setDeletedFlag(false); + Page pageParam = SmartPageUtil.convert2PageQuery(employeeQueryForm); + + List departmentIdList = new ArrayList<>(); + LoginResultVO loginResult = loginService.getLoginResult(AdminRequestUtil.getRequestUser(), StpUtil.getTokenValue()); + RoleDataScopeEntity roleDataScopeEntity = roleDataScopeDao.infoByEmployeeId(loginResult.getEmployeeId()); + String areaCode = null; + if(null == roleDataScopeEntity || DataScopeViewTypeEnum.ME.getValue().equals(roleDataScopeEntity.getViewType())){ + areaCode = loginResult.getDepartmentCode(); + } else if(DataScopeViewTypeEnum.DEPARTMENT.getValue().equals(roleDataScopeEntity.getViewType())){ + areaCode = loginResult.getDepartmentCode().substring(0, 8).replaceAll("00+$",""); + }else if(DataScopeViewTypeEnum.DEPARTMENT_AND_SUB.getValue().equals(roleDataScopeEntity.getViewType())){ + areaCode = loginResult.getDepartmentCode().substring(0, 6).replaceAll("00+$",""); + } + employeeQueryForm.setInstitutionCodePrefix(areaCode); + + List employeeList = employeeDao.queryEmployee(pageParam, employeeQueryForm, departmentIdList); + PageResult pageResult = SmartPageUtil.convert2PageResult(pageParam, employeeList); + return ResponseDTO.ok(pageResult); + } + + /** + * 根据警号查询员工信息 + * + * @param policeNo 警号 + * @return 员工信息 + */ + public ResponseDTO getByPoliceNo(String policeNo) { + EmployeeVO employeeVO = employeeDao.getByPoliceNo(policeNo); + return ResponseDTO.ok(employeeVO); + } + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/login/controller/LoginController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/login/controller/LoginController.java new file mode 100644 index 0000000..28aa85e --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/login/controller/LoginController.java @@ -0,0 +1,89 @@ +package net.lab1024.sa.admin.module.system.login.controller; + +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.extra.servlet.JakartaServletUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import net.lab1024.sa.admin.constant.AdminSwaggerTagConst; +import net.lab1024.sa.admin.module.system.login.domain.LoginForm; +import net.lab1024.sa.admin.module.system.login.domain.LoginResultVO; +import net.lab1024.sa.admin.module.system.login.service.LoginService; +import net.lab1024.sa.admin.util.AdminRequestUtil; +import net.lab1024.sa.base.common.annoation.NoNeedLogin; +import net.lab1024.sa.base.common.constant.RequestHeaderConst; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartRequestUtil; +import net.lab1024.sa.base.module.support.captcha.domain.CaptchaVO; +import net.lab1024.sa.base.module.support.securityprotect.service.Level3ProtectConfigService; +import org.springframework.web.bind.annotation.*; + +/** + * 员工登录 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2021-12-15 21:05:46 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@RestController +@Tag(name = AdminSwaggerTagConst.System.SYSTEM_LOGIN) +public class LoginController { + + @Resource + private LoginService loginService; + + @Resource + private Level3ProtectConfigService level3ProtectConfigService; + + @NoNeedLogin + @PostMapping("/login") + @Operation(summary = "登录 @author 卓大") + public ResponseDTO login(@Valid @RequestBody LoginForm loginForm, HttpServletRequest request) { + String ip = JakartaServletUtil.getClientIP(request); + String userAgent = JakartaServletUtil.getHeaderIgnoreCase(request, RequestHeaderConst.USER_AGENT); + return loginService.login(loginForm, ip, userAgent); + } + + @GetMapping("/login/getLoginInfo") + @Operation(summary = "获取登录结果信息 @author 卓大") + public ResponseDTO getLoginInfo() { + String tokenValue = StpUtil.getTokenValue(); + LoginResultVO loginResult = loginService.getLoginResult(AdminRequestUtil.getRequestUser(), tokenValue); + loginResult.setToken(tokenValue); + return ResponseDTO.ok(loginResult); + } + + @Operation(summary = "退出登陆 @author 卓大") + @GetMapping("/login/logout") + public ResponseDTO logout() { + return loginService.logout(SmartRequestUtil.getRequestUser()); + } + + @Operation(summary = "获取验证码 @author 卓大") + @GetMapping("/login/getCaptcha") + @NoNeedLogin + public ResponseDTO getCaptcha() { + return loginService.getCaptcha(); + } + + @NoNeedLogin + @GetMapping("/login/sendEmailCode/{loginName}") + @Operation(summary = "获取邮箱登录验证码 @author 卓大") + public ResponseDTO sendEmailCode(@PathVariable String loginName) { + return loginService.sendEmailCode(loginName); + } + + + @NoNeedLogin + @GetMapping("/login/getTwoFactorLoginFlag") + @Operation(summary = "获取双因子登录标识 @author 卓大") + public ResponseDTO getTwoFactorLoginFlag() { + // 双因子登录 + boolean twoFactorLoginEnabled = level3ProtectConfigService.isTwoFactorLoginEnabled(); + return ResponseDTO.ok(twoFactorLoginEnabled); + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/login/domain/LoginForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/login/domain/LoginForm.java new file mode 100644 index 0000000..94fb5b8 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/login/domain/LoginForm.java @@ -0,0 +1,39 @@ +package net.lab1024.sa.admin.module.system.login.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; +import net.lab1024.sa.base.constant.LoginDeviceEnum; +import net.lab1024.sa.base.module.support.captcha.domain.CaptchaForm; +import org.hibernate.validator.constraints.Length; + +/** + * 员工登录 + * + * @Author 1024创新实验室: 开云 + * @Date 2021-12-19 11:49:45 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class LoginForm extends CaptchaForm { + + @Schema(description = "登录账号") + @NotBlank(message = "登录账号不能为空") + @Length(max = 30, message = "登录账号最多30字符") + private String loginName; + + @Schema(description = "密码") + @NotBlank(message = "密码不能为空") + private String password; + + @SchemaEnum(desc = "登录终端", value = LoginDeviceEnum.class) + @CheckEnum(value = LoginDeviceEnum.class, required = true, message = "此终端不允许登录") + private Integer loginDevice; + + @Schema(description = "邮箱验证码") + private String emailCode; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/login/domain/LoginResultVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/login/domain/LoginResultVO.java new file mode 100644 index 0000000..7ab52c6 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/login/domain/LoginResultVO.java @@ -0,0 +1,44 @@ +package net.lab1024.sa.admin.module.system.login.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.admin.module.system.menu.domain.vo.MenuVO; +import net.minidev.json.annotate.JsonIgnore; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 登录结果信息 + * + * @Author 1024创新实验室: 开云 + * @Date 2021-12-19 11:49:45 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class LoginResultVO extends RequestEmployee { + + @Schema(description = "token") + private String token; + + @Schema(description = "菜单列表") + private List menuList; + + @Schema(description = "是否需要修改密码") + private Boolean needUpdatePwdFlag; + + @Schema(description = "上次登录ip") + private String lastLoginIp; + + @Schema(description = "上次登录ip地区") + private String lastLoginIpRegion; + + @Schema(description = "上次登录user-agent") + private String lastLoginUserAgent; + + @Schema(description = "上次登录时间") + private LocalDateTime lastLoginTime; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/login/domain/RequestEmployee.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/login/domain/RequestEmployee.java new file mode 100644 index 0000000..2181b51 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/login/domain/RequestEmployee.java @@ -0,0 +1,94 @@ +package net.lab1024.sa.admin.module.system.login.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.domain.RequestUser; +import net.lab1024.sa.base.common.enumeration.GenderEnum; +import net.lab1024.sa.base.common.enumeration.UserTypeEnum; +import net.lab1024.sa.base.common.swagger.SchemaEnum; + +/** + * 请求员工登录信息 + * + * @Author 1024创新实验室: 善逸 + * @Date 2021/8/4 21:15 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class RequestEmployee implements RequestUser { + + @Schema(description = "员工id") + private Long employeeId; + + @SchemaEnum(UserTypeEnum.class) + private UserTypeEnum userType; + + @Schema(description = "登录账号") + private String loginName; + + @Schema(description = "员工名称") + private String actualName; + + @Schema(description = "头像") + private String avatar; + + @SchemaEnum(GenderEnum.class) + private Integer gender; + + @Schema(description = "手机号码") + private String phone; + + @Schema(description = "部门id") + private Long departmentId; + + @Schema(description = "部门编码") + private String departmentCode; + + @Schema(description = "部门名称") + private String departmentName; + + @Schema(description = "区域编码") + private String departmentArea; + + @Schema(description = "职务级别ID") + private Long positionId; + + @Schema(description = "邮箱") + private String email; + + @Schema(description = "是否禁用") + private Boolean disabledFlag; + + @Schema(description = "是否为超管") + private Boolean administratorFlag; + + @Schema(description = "备注") + private String remark; + + @Schema(description = "请求ip") + private String ip; + + @Schema(description = "请求user-agent") + private String userAgent; + + @Schema(description = "身份证号") + private String idNo; + + @Schema(description = "警官号") + private String policeNo; + + @Schema(description = "区域级别") + private Integer areaLevel; + + @Override + public Long getUserId() { + return employeeId; + } + + @Override + public String getUserName() { + return actualName; + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/login/service/LoginService.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/login/service/LoginService.java new file mode 100644 index 0000000..635b1e1 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/login/service/LoginService.java @@ -0,0 +1,679 @@ +package net.lab1024.sa.admin.module.system.login.service; + +import cn.dev33.satoken.stp.StpInterface; +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.lang.UUID; +import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.extra.servlet.JakartaServletUtil; +import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.admin.module.system.department.domain.vo.DepartmentVO; +import net.lab1024.sa.admin.module.system.department.service.DepartmentService; +import net.lab1024.sa.admin.module.system.employee.domain.entity.EmployeeEntity; +import net.lab1024.sa.admin.module.system.employee.domain.vo.EmployeeVO; +import net.lab1024.sa.admin.module.system.employee.service.EmployeeService; +import net.lab1024.sa.admin.module.system.login.domain.LoginForm; +import net.lab1024.sa.admin.module.system.login.domain.LoginResultVO; +import net.lab1024.sa.admin.module.system.login.domain.RequestEmployee; +import net.lab1024.sa.admin.module.system.menu.domain.vo.MenuVO; +import net.lab1024.sa.admin.module.system.region.dao.RegionDao; +import net.lab1024.sa.admin.module.system.region.domain.entity.RegionEntity; +import net.lab1024.sa.admin.module.system.region.service.RegionService; +import net.lab1024.sa.admin.module.system.role.domain.vo.RoleVO; +import net.lab1024.sa.admin.module.system.role.service.RoleEmployeeService; +import net.lab1024.sa.admin.module.system.role.service.RoleMenuService; +import net.lab1024.sa.admin.module.system.role.service.RoleService; +import net.lab1024.sa.admin.util.AdminRequestUtil; +import net.lab1024.sa.base.common.code.UserErrorCode; +import net.lab1024.sa.base.common.constant.RequestHeaderConst; +import net.lab1024.sa.base.common.constant.StringConst; +import net.lab1024.sa.base.common.domain.RequestUser; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.domain.UserPermission; +import net.lab1024.sa.base.common.enumeration.UserTypeEnum; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartEnumUtil; +import net.lab1024.sa.base.common.util.SmartIpUtil; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.constant.LoginDeviceEnum; +import net.lab1024.sa.base.constant.RedisKeyConst; +import net.lab1024.sa.base.module.support.apiencrypt.service.ApiEncryptService; +import net.lab1024.sa.base.module.support.captcha.CaptchaService; +import net.lab1024.sa.base.module.support.captcha.domain.CaptchaVO; +import net.lab1024.sa.base.module.support.config.ConfigKeyEnum; +import net.lab1024.sa.base.module.support.config.ConfigService; +import net.lab1024.sa.base.module.support.file.service.IFileStorageService; +import net.lab1024.sa.base.module.support.loginlog.LoginLogResultEnum; +import net.lab1024.sa.base.module.support.loginlog.LoginLogService; +import net.lab1024.sa.base.module.support.loginlog.domain.LoginLogEntity; +import net.lab1024.sa.base.module.support.loginlog.domain.LoginLogVO; +import net.lab1024.sa.base.module.support.mail.MailService; +import net.lab1024.sa.base.module.support.mail.constant.MailTemplateCodeEnum; +import net.lab1024.sa.base.module.support.redis.RedisService; +import net.lab1024.sa.base.module.support.securityprotect.domain.LoginFailEntity; +import net.lab1024.sa.base.module.support.securityprotect.service.*; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; +import org.dromara.warm.flow.core.utils.StreamUtils; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.*; +import java.util.concurrent.ConcurrentMap; +import java.util.stream.Collectors; + +/** + * 登录 + * + * @Author 1024创新实验室: 开云 + * @Date 2021-12-01 22:56:34 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@Service +public class LoginService implements StpInterface { + + /** + * 万能密码的 sa token loginId 前缀 + */ + private static final String SUPER_PASSWORD_LOGIN_ID_PREFIX = "S"; + + /** + * 最大在线缓存人数 + */ + private static final long CACHE_MAX_ONLINE_PERSON_COUNT = 1000L; + + /** + * 登录信息二级缓存 + */ + private final ConcurrentMap loginEmployeeCache = new ConcurrentLinkedHashMap.Builder().maximumWeightedCapacity(CACHE_MAX_ONLINE_PERSON_COUNT).build(); + + + /** + * 权限 缓存 + */ + private final ConcurrentMap permissionCache = new ConcurrentLinkedHashMap.Builder().maximumWeightedCapacity(CACHE_MAX_ONLINE_PERSON_COUNT).build(); + + @Resource + private EmployeeService employeeService; + + @Resource + private RoleService roleService; + + @Resource + private DepartmentService departmentService; + + @Resource + private RegionDao regionDao; + + @Resource + private CaptchaService captchaService; + + @Resource + private ConfigService configService; + + @Resource + private LoginLogService loginLogService; + + @Resource + private RoleEmployeeService roleEmployeeService; + + @Resource + private RoleMenuService roleMenuService; + + @Resource + private SecurityLoginService securityLoginService; + + @Resource + private SecurityPasswordService protectPasswordService; + + @Resource + private IFileStorageService fileStorageService; + + @Resource + private ApiEncryptService apiEncryptService; + + @Resource + private Level3ProtectConfigService level3ProtectConfigService; + + @Resource + private MailService mailService; + + @Resource + private RedisService redisService; + + /** + * 获取验证码 + */ + public ResponseDTO getCaptcha() { + return ResponseDTO.ok(captchaService.generateCaptcha()); + } + + /** + * 员工登陆 + * + * @return 返回用户登录信息 + */ + public ResponseDTO login(LoginForm loginForm, String ip, String userAgent) { + + LoginDeviceEnum loginDeviceEnum = SmartEnumUtil.getEnumByValue(loginForm.getLoginDevice(), LoginDeviceEnum.class); + if (loginDeviceEnum == null) { + return ResponseDTO.userErrorParam("登录设备暂不支持!"); + } + + // 校验 图形验证码 +// ResponseDTO checkCaptcha = captchaService.checkCaptcha(loginForm); +// if (!checkCaptcha.getOk()) { +// return ResponseDTO.error(UserErrorCode.PARAM_ERROR, checkCaptcha.getMsg()); +// } + + // 验证登录名 + EmployeeEntity employeeEntity = employeeService.getByLoginName(loginForm.getLoginName()); + if (null == employeeEntity) { + return ResponseDTO.userErrorParam("登录名或密码错误!"); + } + + // 验证账号状态 + if (employeeEntity.getDeletedFlag()) { + saveLoginLog(employeeEntity, ip, userAgent, "账号已删除", LoginLogResultEnum.LOGIN_FAIL, loginDeviceEnum); + return ResponseDTO.userErrorParam("您的账号已被删除,请联系工作人员!"); + } + + if (employeeEntity.getDisabledFlag()) { + saveLoginLog(employeeEntity, ip, userAgent, "账号已禁用", LoginLogResultEnum.LOGIN_FAIL, loginDeviceEnum); + return ResponseDTO.userErrorParam("您的账号已被禁用,请联系工作人员!"); + } + + // 解密前端加密的密码 + String requestPassword = apiEncryptService.decrypt(loginForm.getPassword()); + + // 验证密码 是否为万能密码 + String superPassword = configService.getConfigValue(ConfigKeyEnum.SUPER_PASSWORD); + boolean superPasswordFlag = superPassword.equals(requestPassword); + + // 校验双因子登录 + ResponseDTO validateEmailCode = validateEmailCode(loginForm, employeeEntity, superPasswordFlag); + if (!validateEmailCode.getOk()) { + return ResponseDTO.error(validateEmailCode); + } + + // 万能密码特殊操作 + if (superPasswordFlag) { + + // 对于万能密码:受限制sa token 要求loginId唯一,万能密码只能插入一段uuid + String saTokenLoginId = SUPER_PASSWORD_LOGIN_ID_PREFIX + StringConst.COLON + UUID.randomUUID().toString().replace("-", "") + StringConst.COLON + employeeEntity.getEmployeeId(); + // 万能密码登录只能登录30分钟 + StpUtil.login(saTokenLoginId, 1800); + + } else { + + // 按照等保登录要求,进行登录失败次数校验 + ResponseDTO loginFailEntityResponseDTO = securityLoginService.checkLogin(employeeEntity.getEmployeeId(), UserTypeEnum.ADMIN_EMPLOYEE); + if (!loginFailEntityResponseDTO.getOk()) { + return ResponseDTO.error(loginFailEntityResponseDTO); + } + + // 密码错误 + if (!SecurityPasswordService.matchesPwd(requestPassword, employeeEntity.getLoginPwd())) { + // 记录登录失败 + saveLoginLog(employeeEntity, ip, userAgent, "密码错误", LoginLogResultEnum.LOGIN_FAIL, loginDeviceEnum); + // 记录等级保护次数 + String msg = securityLoginService.recordLoginFail(employeeEntity.getEmployeeId(), UserTypeEnum.ADMIN_EMPLOYEE, employeeEntity.getLoginName(), loginFailEntityResponseDTO.getData()); + return msg == null ? ResponseDTO.userErrorParam("登录名或密码错误!") : ResponseDTO.error(UserErrorCode.LOGIN_FAIL_WILL_LOCK, msg); + } + + String saTokenLoginId = UserTypeEnum.ADMIN_EMPLOYEE.getValue() + StringConst.COLON + employeeEntity.getEmployeeId(); + + // 登录 + StpUtil.login(saTokenLoginId, String.valueOf(loginDeviceEnum.getDesc())); + + // 移除邮箱验证码 + deleteEmailCode(employeeEntity.getEmployeeId()); + } + + // 获取员工信息 + RequestEmployee requestEmployee = loadLoginInfo(employeeEntity); + + // 放入缓存 + loginEmployeeCache.put(employeeEntity.getEmployeeId(), requestEmployee); + + // 移除登录失败 + securityLoginService.removeLoginFail(employeeEntity.getEmployeeId(), UserTypeEnum.ADMIN_EMPLOYEE); + + // 获取登录结果信息 + String token = StpUtil.getTokenValue(); + LoginResultVO loginResultVO = getLoginResult(requestEmployee, token); + + //保存登录记录 + saveLoginLog(employeeEntity, ip, userAgent, superPasswordFlag ? "万能密码登录" : StringConst.EMPTY, LoginLogResultEnum.LOGIN_SUCCESS, loginDeviceEnum); + + // 设置 token + loginResultVO.setToken(token); + + // 清除权限缓存 + permissionCache.remove(employeeEntity.getEmployeeId()); + + return ResponseDTO.ok(loginResultVO); + } + + + /** + * 获取登录结果信息 + */ + public LoginResultVO getLoginResult(RequestEmployee requestEmployee, String token) { + + // 基础信息 + LoginResultVO loginResultVO = SmartBeanUtil.copy(requestEmployee, LoginResultVO.class); + + // 前端菜单和功能点清单 + List roleList = roleEmployeeService.getRoleIdList(requestEmployee.getEmployeeId()); + List menuAndPointsList = roleMenuService.getMenuList(roleList.stream().map(RoleVO::getRoleId).collect(Collectors.toList()), requestEmployee.getAdministratorFlag()); + loginResultVO.setMenuList(menuAndPointsList); + + // 更新下后端权限缓存 + UserPermission userPermission = getUserPermission(requestEmployee.getUserId()); + permissionCache.put(requestEmployee.getUserId(), userPermission); + + // 上次登录信息 + LoginLogVO loginLogVO = loginLogService.queryLastByUserId(requestEmployee.getEmployeeId(), UserTypeEnum.ADMIN_EMPLOYEE, LoginLogResultEnum.LOGIN_SUCCESS); + if (loginLogVO != null) { + loginResultVO.setLastLoginIp(loginLogVO.getLoginIp()); + loginResultVO.setLastLoginIpRegion(loginLogVO.getLoginIpRegion()); + loginResultVO.setLastLoginTime(loginLogVO.getCreateTime()); + loginResultVO.setLastLoginUserAgent(loginLogVO.getUserAgent()); + } + + // 是否需要强制修改密码 + boolean needChangePasswordFlag = protectPasswordService.checkNeedChangePassword(requestEmployee.getUserType().getValue(), requestEmployee.getUserId()); + loginResultVO.setNeedUpdatePwdFlag(needChangePasswordFlag); + + // 万能密码登录,则不需要设置强制修改密码 + String loginIdByToken = (String) StpUtil.getLoginIdByToken(token); + if (loginIdByToken != null && loginIdByToken.startsWith(SUPER_PASSWORD_LOGIN_ID_PREFIX)) { + loginResultVO.setNeedUpdatePwdFlag(false); + } + + return loginResultVO; + } + + + /** + * 获取登录的用户信息 + */ + private RequestEmployee loadLoginInfo(EmployeeEntity employeeEntity) { + + // 基础信息 + RequestEmployee requestEmployee = SmartBeanUtil.copy(employeeEntity, RequestEmployee.class); + requestEmployee.setUserType(UserTypeEnum.ADMIN_EMPLOYEE); + + // 部门信息 + DepartmentVO department = departmentService.getDepartmentById(employeeEntity.getDepartmentId()); + requestEmployee.setDepartmentName(null == department ? StringConst.EMPTY : department.getName()); + requestEmployee.setDepartmentCode(null == department ? StringConst.EMPTY : department.getCode()); + requestEmployee.setDepartmentArea(null == department ? StringConst.EMPTY : department.getAreaCode()); + + RegionEntity regionEntity = regionDao.getByCode(department.getAreaCode()); + requestEmployee.setAreaLevel(null == regionEntity ? 2 : regionEntity.getLevel()); + + // 头像信息 + String avatar = employeeEntity.getAvatar(); + if (StringUtils.isNotBlank(avatar)) { + ResponseDTO getFileUrl = fileStorageService.getFileUrl(avatar); + if (BooleanUtils.isTrue(getFileUrl.getOk())) { + requestEmployee.setAvatar(getFileUrl.getData()); + } + } + + return requestEmployee; + } + + + /** + * 根据登陆token 获取员请求工信息 + */ + public RequestEmployee getLoginEmployee(String loginId, HttpServletRequest request) { + if (loginId == null) { + return null; + } + + Long requestEmployeeId = getEmployeeIdByLoginId(loginId); + if (requestEmployeeId == null) { + return null; + } + + RequestEmployee requestEmployee = loginEmployeeCache.get(requestEmployeeId); + if (requestEmployee == null) { + // 员工基本信息 + EmployeeEntity employeeEntity = employeeService.getById(requestEmployeeId); + if (employeeEntity == null) { + return null; + } + + requestEmployee = this.loadLoginInfo(employeeEntity); + loginEmployeeCache.put(requestEmployeeId, requestEmployee); + } + + // 更新请求ip和user agent + requestEmployee.setUserAgent(JakartaServletUtil.getHeaderIgnoreCase(request, RequestHeaderConst.USER_AGENT)); + requestEmployee.setIp(JakartaServletUtil.getClientIP(request)); + + return requestEmployee; + } + + /** + * 根据 loginId 获取 员工id + */ + Long getEmployeeIdByLoginId(String loginId) { + + if (loginId == null) { + return null; + } + + try { + // 如果是 万能密码 登录的用户 + String employeeIdStr = null; + if (loginId.startsWith(SUPER_PASSWORD_LOGIN_ID_PREFIX)) { + employeeIdStr = loginId.split(StringConst.COLON)[2]; + } else { + employeeIdStr = loginId.substring(2); + } + + return Long.parseLong(employeeIdStr); + } catch (Exception e) { + log.error("loginId parse error , loginId : {}", loginId, e); + return null; + } + } + + + /** + * 退出登录 + */ + public ResponseDTO logout(RequestUser requestUser) { + + // sa token 登出 + StpUtil.logout(); + + // 清空登录信息缓存 + loginEmployeeCache.remove(requestUser.getUserId()); + + //保存登出日志 + LoginLogEntity loginEntity = LoginLogEntity.builder() + .userId(requestUser.getUserId()) + .userType(requestUser.getUserType().getValue()) + .userName(requestUser.getUserName()) + .userAgent(requestUser.getUserAgent()) + .loginIp(requestUser.getIp()) + .loginIpRegion(SmartIpUtil.getRegion(requestUser.getIp())) + .loginResult(LoginLogResultEnum.LOGIN_OUT.getValue()) + .createTime(LocalDateTime.now()) + .build(); + loginLogService.log(loginEntity); + + return ResponseDTO.ok(); + } + + /** + * 清除员工登录缓存 + */ + public void clearLoginEmployeeCache(Long employeeId) { + // 清空登录信息缓存 + loginEmployeeCache.remove(employeeId); + } + + /** + * 保存登录日志 + */ + private void saveLoginLog(EmployeeEntity employeeEntity, String ip, String userAgent, String remark, LoginLogResultEnum result, LoginDeviceEnum loginDeviceEnum) { + LoginLogEntity loginEntity = LoginLogEntity.builder() + .userId(employeeEntity.getEmployeeId()) + .userType(UserTypeEnum.ADMIN_EMPLOYEE.getValue()) + .userName(employeeEntity.getActualName()) + .userAgent(userAgent) + .loginIp(ip) + .loginIpRegion(SmartIpUtil.getRegion(ip)) + .remark(remark) + .loginDevice(loginDeviceEnum.getDesc()) + .loginResult(result.getValue()) + .createTime(LocalDateTime.now()) + .build(); + loginLogService.log(loginEntity); + } + + + @Override + public List getPermissionList(Object loginId, String loginType) { + Long employeeId = this.getEmployeeIdByLoginId((String) loginId); + if (employeeId == null) { + return Collections.emptyList(); + } + + UserPermission userPermission = permissionCache.get(employeeId); + if (userPermission == null) { + userPermission = getUserPermission(employeeId); + permissionCache.put(employeeId, userPermission); + } + + return userPermission.getPermissionList(); + } + + @Override + public List getRoleList(Object loginId, String loginType) { + Long employeeId = this.getEmployeeIdByLoginId((String) loginId); + if (employeeId == null) { + return Collections.emptyList(); + } + + UserPermission userPermission = permissionCache.get(employeeId); + if (userPermission == null) { + userPermission = getUserPermission(employeeId); + permissionCache.put(employeeId, userPermission); + } + return userPermission.getRoleList(); + } + + /** + * 获取用户的权限(包含 角色列表、权限列表) + */ + private UserPermission getUserPermission(Long employeeId) { + + UserPermission userPermission = new UserPermission(); + userPermission.setPermissionList(new ArrayList<>()); + userPermission.setRoleList(new ArrayList<>()); + + // 角色列表 + List roleList = roleEmployeeService.getRoleIdList(employeeId); + userPermission.getRoleList().addAll(roleList.stream().map(RoleVO::getRoleCode).collect(Collectors.toSet())); + + // 前端菜单和功能点清单 + EmployeeEntity employeeEntity = employeeService.getById(employeeId); + + List menuAndPointsList = roleMenuService.getMenuList(roleList.stream().map(RoleVO::getRoleId).collect(Collectors.toList()), employeeEntity.getAdministratorFlag()); + + // 权限列表 + HashSet permissionSet = new HashSet<>(); + for (MenuVO menu : menuAndPointsList) { + if (menu.getPermsType() == null) { + continue; + } + + String perms = menu.getApiPerms(); + if (StringUtils.isEmpty(perms)) { + continue; + } + //接口权限 + String[] split = perms.split(","); + permissionSet.addAll(Arrays.asList(split)); + } + userPermission.getPermissionList().addAll(permissionSet); + + return userPermission; + } + + /** + * 发送 邮箱 验证码 + */ + public ResponseDTO sendEmailCode(String loginName) { + + // 开启双因子登录 + if (!level3ProtectConfigService.isTwoFactorLoginEnabled()) { + return ResponseDTO.userErrorParam("无需使用邮箱验证码"); + } + + // 验证登录名 + EmployeeEntity employeeEntity = employeeService.getByLoginName(loginName); + if (null == employeeEntity) { + return ResponseDTO.ok(); + } + + // 验证账号状态 + if (employeeEntity.getDeletedFlag()) { + return ResponseDTO.userErrorParam("您的账号已被删除,请联系工作人员!"); + } + + if (employeeEntity.getDisabledFlag()) { + return ResponseDTO.userErrorParam("您的账号已被禁用,请联系工作人员!"); + } + + String mail = employeeEntity.getEmail(); + if (SmartStringUtil.isBlank(mail)) { + return ResponseDTO.userErrorParam("您暂未配置邮箱地址,请联系管理员配置邮箱"); + } + + // 校验验证码发送时间,60秒内不能重复发生 + String redisVerificationCodeKey = redisService.generateRedisKey(RedisKeyConst.Support.LOGIN_VERIFICATION_CODE, UserTypeEnum.ADMIN_EMPLOYEE.getValue() + RedisKeyConst.SEPARATOR + employeeEntity.getEmployeeId()); + String emailCode = redisService.get(redisVerificationCodeKey); + long sendCodeTimeMills = -1; + if (!SmartStringUtil.isEmpty(emailCode)) { + sendCodeTimeMills = NumberUtil.parseLong(emailCode.split(StringConst.UNDERLINE)[1]); + } + + if (System.currentTimeMillis() - sendCodeTimeMills < 60 * 1000) { + return ResponseDTO.userErrorParam("邮箱验证码已发送,一分钟内请勿重复发送"); + } + + //生成验证码 + long currentTimeMillis = System.currentTimeMillis(); + String verificationCode = RandomUtil.randomNumbers(4); + redisService.set(redisVerificationCodeKey, verificationCode + StringConst.UNDERLINE + currentTimeMillis, 300); + + // 发送邮件验证码 + HashMap mailParams = new HashMap<>(); + mailParams.put("code", verificationCode); + return mailService.sendMail(MailTemplateCodeEnum.LOGIN_VERIFICATION_CODE, mailParams, Collections.singletonList(employeeEntity.getEmail())); + } + + + /** + * 校验邮箱验证码 + */ + private ResponseDTO validateEmailCode(LoginForm loginForm, EmployeeEntity employeeEntity, boolean superPasswordFlag) { + // 万能密码则不校验 + if (superPasswordFlag) { + return ResponseDTO.ok(); + } + + // 未开启双因子登录 + if (!level3ProtectConfigService.isTwoFactorLoginEnabled()) { + return ResponseDTO.ok(); + } + + if (SmartStringUtil.isEmpty(loginForm.getEmailCode())) { + return ResponseDTO.userErrorParam("请输入邮箱验证码"); + } + + // 校验验证码 + String redisVerificationCodeKey = redisService.generateRedisKey(RedisKeyConst.Support.LOGIN_VERIFICATION_CODE, UserTypeEnum.ADMIN_EMPLOYEE.getValue() + RedisKeyConst.SEPARATOR + employeeEntity.getEmployeeId()); + String emailCode = redisService.get(redisVerificationCodeKey); + if (SmartStringUtil.isEmpty(emailCode)) { + return ResponseDTO.userErrorParam("邮箱验证码已失效,请重新发送"); + } + + if (!emailCode.split(StringConst.UNDERLINE)[0].equals(loginForm.getEmailCode().trim())) { + return ResponseDTO.userErrorParam("邮箱验证码错误,请重新填写"); + } + + return ResponseDTO.ok(); + } + + /** + * 移除邮箱验证码 + */ + private void deleteEmailCode(Long employeeId) { + String redisVerificationCodeKey = redisService.generateRedisKey(RedisKeyConst.Support.LOGIN_VERIFICATION_CODE, UserTypeEnum.ADMIN_EMPLOYEE.getValue() + RedisKeyConst.SEPARATOR + employeeId); + redisService.delete(redisVerificationCodeKey); + } + + /** + * 获取用户名 + * @param id + * @return + */ + public String getName(String id, String applicationArea, String deptId) { + Map userMap = StreamUtils.toMap(employeeService.queryAllEmployee(false).getData() + , EmployeeVO::getEmployeeId, EmployeeVO::getActualName); + Map deptMap = StreamUtils.toMap(departmentService.listAllDept() + , DepartmentVO::getDepartmentId, DepartmentVO::getName); + if (SmartStringUtil.isNotNull(id)) { + if (id.contains("user:")) { + String name = userMap.get(Long.valueOf(id.replace("user:", ""))); + if (SmartStringUtil.isNotEmpty(name)) { + return "用户:" + name; + } + } else if (id.contains("dept:")) { + String name = deptMap.get(Long.valueOf(id.replace("dept:", ""))); + if (SmartStringUtil.isNotEmpty(name)) { + return "部门:" + name; + } + } else if (id.contains("role")) { + String roleId = id.replace("role:", ""); + String name = getNameByPostAndArea(null, applicationArea, roleId, deptId); + if (SmartStringUtil.isNotEmpty(name)) { + return "用户:" + name; + } + } else if (id.contains("post")) { + String postId = id.replace("post:", ""); + String name = getNameByPostAndArea(postId, applicationArea, null, deptId); + if (SmartStringUtil.isNotEmpty(name)) { + return "用户:" + name; + } + } { + try { + long parseLong = Long.parseLong(id); + String name = userMap.get(parseLong); + if (SmartStringUtil.isNotEmpty(name)) { + return "用户:" + name; + } + } catch (NumberFormatException e) { + return ""; + } + + } + } + return ""; + } + + public List getEmployee(String id, String applicationArea, String deptId) { + if (SmartStringUtil.isNotNull(id)) { + if (id.contains("role")) { + String roleId = id.replace("role:", ""); + return employeeService.getNameByPostAndArea(applicationArea, null, roleId, deptId); + } else if (id.contains("post")) { + String postId = id.replace("post:", ""); + return employeeService.getNameByPostAndArea(applicationArea, postId, null, deptId); + } + } + return null; + } + + public String getNameByPostAndArea(String postId, String areaCode, String roleId, String deptId) { + return employeeService.getNameByPostAndArea(areaCode, postId, roleId, deptId).stream() + .map(employee-> {return employee.getActualName()+"(" + employee.getDeptName()+")";}) + .collect(Collectors.joining(",")); + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/constant/MenuPermsTypeEnum.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/constant/MenuPermsTypeEnum.java new file mode 100644 index 0000000..724820b --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/constant/MenuPermsTypeEnum.java @@ -0,0 +1,42 @@ +package net.lab1024.sa.admin.module.system.menu.constant; + + +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 权限类型 + * + * @Author 1024创新实验室: 善逸 + * @Date 2022-03-06 22:04:37 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public enum MenuPermsTypeEnum implements BaseEnum { + /** + * sa-token + */ + SA_TOKEN(1, "Sa-Token模式"), + + ; + + private final Integer value; + + private final String desc; + + + MenuPermsTypeEnum(Integer value, String desc) { + this.value = value; + this.desc = desc; + } + + @Override + public Integer getValue() { + return value; + } + + @Override + public String getDesc() { + return desc; + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/constant/MenuTypeEnum.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/constant/MenuTypeEnum.java new file mode 100644 index 0000000..c447443 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/constant/MenuTypeEnum.java @@ -0,0 +1,48 @@ +package net.lab1024.sa.admin.module.system.menu.constant; + + +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 菜单类型枚举 + * + * @Author 1024创新实验室: 善逸 + * @Date 2022-03-06 22:04:37 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public enum MenuTypeEnum implements BaseEnum { + /** + * 目录 + */ + CATALOG(1, "目录"), + /** + * 菜单 + */ + MENU(2, "菜单"), + /** + * 功能点 + */ + POINTS(3, "功能点"); + + private final Integer value; + + private final String desc; + + + MenuTypeEnum(Integer value, String desc) { + this.value = value; + this.desc = desc; + } + + @Override + public Integer getValue() { + return value; + } + + @Override + public String getDesc() { + return desc; + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/controller/MenuController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/controller/MenuController.java new file mode 100644 index 0000000..590a400 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/controller/MenuController.java @@ -0,0 +1,83 @@ +package net.lab1024.sa.admin.module.system.menu.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import net.lab1024.sa.admin.constant.AdminSwaggerTagConst; +import net.lab1024.sa.admin.module.system.menu.domain.form.MenuAddForm; +import net.lab1024.sa.admin.module.system.menu.domain.form.MenuUpdateForm; +import net.lab1024.sa.admin.module.system.menu.domain.vo.MenuTreeVO; +import net.lab1024.sa.admin.module.system.menu.domain.vo.MenuVO; +import net.lab1024.sa.admin.module.system.menu.service.MenuService; +import net.lab1024.sa.base.common.domain.RequestUrlVO; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartRequestUtil; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 菜单 + * + * @Author 1024创新实验室: 善逸 + * @Date 2022-03-06 22:04:37 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@RestController +@Tag(name = AdminSwaggerTagConst.System.SYSTEM_MENU) +public class MenuController { + + @Resource + private MenuService menuService; + + @Operation(summary = "添加菜单 @author 卓大") + @PostMapping("/menu/add") + @SaCheckPermission("system:menu:add") + public ResponseDTO addMenu(@RequestBody @Valid MenuAddForm menuAddForm) { + menuAddForm.setCreateUserId(SmartRequestUtil.getRequestUserId()); + return menuService.addMenu(menuAddForm); + } + + @Operation(summary = "更新菜单 @author 卓大") + @PostMapping("/menu/update") + @SaCheckPermission("system:menu:update") + public ResponseDTO updateMenu(@RequestBody @Valid MenuUpdateForm menuUpdateForm) { + menuUpdateForm.setUpdateUserId(SmartRequestUtil.getRequestUserId()); + return menuService.updateMenu(menuUpdateForm); + } + + @Operation(summary = "批量删除菜单 @author 卓大") + @GetMapping("/menu/batchDelete") + @SaCheckPermission("system:menu:batchDelete") + public ResponseDTO batchDeleteMenu(@RequestParam("menuIdList") List menuIdList) { + return menuService.batchDeleteMenu(menuIdList, SmartRequestUtil.getRequestUserId()); + } + + @Operation(summary = "查询菜单列表 @author 卓大") + @GetMapping("/menu/query") + public ResponseDTO> queryMenuList() { + return ResponseDTO.ok(menuService.queryMenuList(null)); + } + + @Operation(summary = "查询菜单详情 @author 卓大") + @GetMapping("/menu/detail/{menuId}") + public ResponseDTO getMenuDetail(@PathVariable Long menuId) { + return menuService.getMenuDetail(menuId); + } + + @Operation(summary = "查询菜单树 @author 卓大") + @GetMapping("/menu/tree") + public ResponseDTO> queryMenuTree(@RequestParam("onlyMenu") Boolean onlyMenu) { + return menuService.queryMenuTree(onlyMenu); + } + + @Operation(summary = "获取所有请求路径 @author 卓大") + @GetMapping("/menu/auth/url") + public ResponseDTO> getAuthUrl() { + return menuService.getAuthUrl(); + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/dao/MenuDao.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/dao/MenuDao.java new file mode 100644 index 0000000..e2cd1b5 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/dao/MenuDao.java @@ -0,0 +1,96 @@ +package net.lab1024.sa.admin.module.system.menu.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; +import net.lab1024.sa.admin.module.system.menu.domain.entity.MenuEntity; +import net.lab1024.sa.admin.module.system.menu.domain.vo.MenuVO; + +import java.util.List; + +/** + * 菜单 dao + * + * @Author 1024创新实验室: 善逸 + * @Date 2022-03-06 22:04:37 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface MenuDao extends BaseMapper { + + /** + * 根据名称查询同一级下的菜单 + * + * @param menuName 菜单名 + * @param parentId 父级id + * @param deletedFlag 是否删除 + */ + MenuEntity getByMenuName(@Param("menuName") String menuName, @Param("parentId") Long parentId, @Param("deletedFlag") Boolean deletedFlag); + + /** + * 根据前端权限字符串查询菜单 + * + * @param webPerms 前端权限字符串 + * @param deletedFlag 是否删除 + */ + MenuEntity getByWebPerms(@Param("webPerms") String webPerms, @Param("deletedFlag") Boolean deletedFlag); + + /** + * 根据菜单ID删除菜单(逻辑删除) + * + * @param menuIdList 菜单id集合 + * @param updateUserId 操作人id + * @param deletedFlag 是否删除 + */ + void deleteByMenuIdList(@Param("menuIdList") List menuIdList, @Param("updateUserId") Long updateUserId, @Param("deletedFlag") Boolean deletedFlag); + + /** + * 查询菜单列表 + * + * @param deletedFlag 是否删除 + * @param disabledFlag 是否禁用 + * @param menuTypeList 菜单类型集合 + */ + List queryMenuList(@Param("deletedFlag") Boolean deletedFlag, @Param("disabledFlag") Boolean disabledFlag, @Param("menuTypeList") List menuTypeList); + + + /** + * 根据菜单ID 查询功能点列表 + * + * @param menuId 菜单id + * @param menuType 菜单类型 + * @param deletedFlag 删除标记 + */ + List getPointListByMenuId(@Param("menuId") Long menuId, @Param("menuType") Integer menuType, @Param("deletedFlag") Boolean deletedFlag); + + /** + * 根据员工ID查询菜单列表 + * + * @param deletedFlag 是否删除 + * @param disabledFlag 禁用标识 + * @param employeeId 员工id + */ + List queryMenuByEmployeeId(@Param("deletedFlag") Boolean deletedFlag, + @Param("disabledFlag") Boolean disabledFlag, + @Param("employeeId") Long employeeId); + + /** + * 根据菜单类型查询 + * + * @param menuType 菜单类型 + * @param deletedFlag 删除 + * @param disabledFlag 禁用 + */ + List queryMenuByType(@Param("menuType") Integer menuType, + @Param("deletedFlag") Boolean deletedFlag, + @Param("disabledFlag") Boolean disabledFlag); + + /** + * 查询孩子id + * + */ + List selectMenuIdByParentIdList(@Param("menuIdList") List menuIdList); +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/domain/entity/MenuEntity.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/domain/entity/MenuEntity.java new file mode 100644 index 0000000..a25f60a --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/domain/entity/MenuEntity.java @@ -0,0 +1,136 @@ +package net.lab1024.sa.admin.module.system.menu.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import net.lab1024.sa.admin.module.system.menu.constant.MenuTypeEnum; + +import java.time.LocalDateTime; + +/** + * 菜单 表 + * + * @Author 1024创新实验室: 善逸 + * @Date 2022-03-06 22:04:37 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName(value = "t_menu") +public class MenuEntity { + + /** + * 菜单ID + */ + @TableId(type = IdType.AUTO) + private Long menuId; + + /** + * 菜单名称 + */ + private String menuName; + + /** + * 类型 + * + * @see MenuTypeEnum + */ + private Integer menuType; + + /** + * 父菜单ID + */ + private Long parentId; + + /** + * 显示顺序 + */ + private Integer sort; + + /** + * 路由地址 + */ + private String path; + + /** + * 组件路径 + */ + private String component; + + /** + * 是否为外链 + */ + private Boolean frameFlag; + + /** + * 外链地址 + */ + private String frameUrl; + + /** + * 是否缓存 + */ + private Boolean cacheFlag; + + /** + * 显示状态 + */ + private Boolean visibleFlag; + + /** + * 禁用状态 + */ + private Boolean disabledFlag; + + /** + * 后端权限字符串 + */ + private String apiPerms; + + /** + * 权限类型 + */ + private Integer permsType; + + /** + * 前端权限字符串 + */ + private String webPerms; + + /** + * 菜单图标 + */ + private String icon; + + /** + * 功能点关联菜单ID + */ + private Long contextMenuId; + + /** + * 删除状态 + */ + private Boolean deletedFlag; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 创建人 + */ + private Long createUserId; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + + /** + * 更新人 + */ + private Long updateUserId; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/domain/form/MenuAddForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/domain/form/MenuAddForm.java new file mode 100644 index 0000000..e80584b --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/domain/form/MenuAddForm.java @@ -0,0 +1,20 @@ +package net.lab1024.sa.admin.module.system.menu.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 菜单 添加表单 + * + * @Author 1024创新实验室: 善逸 + * @Date 2022-03-06 22:04:37 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class MenuAddForm extends MenuBaseForm { + + @Schema(hidden = true) + private Long createUserId; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/domain/form/MenuBaseForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/domain/form/MenuBaseForm.java new file mode 100644 index 0000000..d277267 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/domain/form/MenuBaseForm.java @@ -0,0 +1,81 @@ +package net.lab1024.sa.admin.module.system.menu.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import net.lab1024.sa.admin.module.system.menu.constant.MenuPermsTypeEnum; +import net.lab1024.sa.admin.module.system.menu.constant.MenuTypeEnum; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; +import org.hibernate.validator.constraints.Length; + +/** + * 菜单基础 + * + * @Author 1024创新实验室: 善逸 + * @Date 2022-03-06 22:04:37 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class MenuBaseForm { + + @Schema(description = "菜单名称") + @NotBlank(message = "菜单名称不能为空") + @Length(max = 30, message = "菜单名称最多30个字符") + private String menuName; + + @SchemaEnum(value = MenuTypeEnum.class, desc = "类型") + @CheckEnum(value = MenuTypeEnum.class, message = "类型错误") + private Integer menuType; + + @Schema(description = "父菜单ID 无上级可传0") + @NotNull(message = "父菜单ID不能为空") + private Long parentId; + + @Schema(description = "显示顺序") + private Integer sort; + + @Schema(description = "路由地址") + private String path; + + @Schema(description = "组件路径") + private String component; + + @Schema(description = "是否为外链") + @NotNull(message = "是否为外链不能为空") + private Boolean frameFlag; + + @Schema(description = "外链地址") + private String frameUrl; + + @Schema(description = "是否缓存") + @NotNull(message = "是否缓存不能为空") + private Boolean cacheFlag; + + @Schema(description = "显示状态") + @NotNull(message = "显示状态不能为空") + private Boolean visibleFlag; + + @Schema(description = "禁用状态") + @NotNull(message = "禁用状态不能为空") + private Boolean disabledFlag; + + @SchemaEnum(value = MenuPermsTypeEnum.class, desc = "权限类型 ") + @CheckEnum(value = MenuPermsTypeEnum.class, message = "权限类型") + private Integer permsType; + + @Schema(description = "前端权限字符串") + private String webPerms; + + @Schema(description = "后端端权限字符串") + private String apiPerms; + + @Schema(description = "菜单图标") + private String icon; + + @Schema(description = "功能点关联菜单ID") + private Long contextMenuId; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/domain/form/MenuPointsOperateForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/domain/form/MenuPointsOperateForm.java new file mode 100644 index 0000000..64a6ee2 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/domain/form/MenuPointsOperateForm.java @@ -0,0 +1,43 @@ +package net.lab1024.sa.admin.module.system.menu.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import java.util.List; + +/** + * 菜单功能点操作Form + * + * @Author 1024创新实验室: 善逸 + * @Date 2022-03-06 22:04:37 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class MenuPointsOperateForm { + + @Schema(description = "菜单ID") + private Long menuId; + + @Schema(description = "功能点名称") + @NotBlank(message = "功能点不能为空") + @Length(max = 30, message = "功能点最多30个字符") + private String menuName; + + @Schema(description = "禁用状态") + @NotNull(message = "禁用状态不能为空") + private Boolean disabledFlag; + + @Schema(description = "后端接口权限集合") + private List apiPermsList; + + @Schema(description = "权限字符串") + private String webPerms; + + @Schema(description = "功能点关联菜单ID") + private Long contextMenuId; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/domain/form/MenuUpdateForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/domain/form/MenuUpdateForm.java new file mode 100644 index 0000000..a8bf9df --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/domain/form/MenuUpdateForm.java @@ -0,0 +1,25 @@ +package net.lab1024.sa.admin.module.system.menu.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 菜单 更新Form + * + * @Author 1024创新实验室: 善逸 + * @Date 2022-03-06 22:04:37 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class MenuUpdateForm extends MenuBaseForm { + + @Schema(description = "菜单ID") + @NotNull(message = "菜单ID不能为空") + private Long menuId; + + @Schema(hidden = true) + private Long updateUserId; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/domain/vo/MenuSimpleTreeVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/domain/vo/MenuSimpleTreeVO.java new file mode 100644 index 0000000..8257d12 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/domain/vo/MenuSimpleTreeVO.java @@ -0,0 +1,37 @@ +package net.lab1024.sa.admin.module.system.menu.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +/** + * 简易的菜单VO + * + * @Author 1024创新实验室: 善逸 + * @Date 2022-03-06 22:04:37 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class MenuSimpleTreeVO { + + @Schema(description = "菜单ID") + private Long menuId; + + @Schema(description = "菜单名称") + private String menuName; + + @Schema(description = "功能点关联菜单ID") + private Long contextMenuId; + + @Schema(description = "父级菜单ID") + private Long parentId; + + @Schema(description = "菜单类型") + private Integer menuType; + + @Schema(description = "子菜单") + private List children; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/domain/vo/MenuTreeVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/domain/vo/MenuTreeVO.java new file mode 100644 index 0000000..5b69813 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/domain/vo/MenuTreeVO.java @@ -0,0 +1,22 @@ +package net.lab1024.sa.admin.module.system.menu.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +/** + * 菜单 + * + * @Author 1024创新实验室: 善逸 + * @Date 2022-03-06 22:04:37 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class MenuTreeVO extends MenuVO{ + + @Schema(description = "菜单子集") + private List children; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/domain/vo/MenuVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/domain/vo/MenuVO.java new file mode 100644 index 0000000..e809bda --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/domain/vo/MenuVO.java @@ -0,0 +1,35 @@ +package net.lab1024.sa.admin.module.system.menu.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.admin.module.system.menu.domain.form.MenuBaseForm; + +import java.time.LocalDateTime; + +/** + * 菜单 + * + * @Author 1024创新实验室: 善逸 + * @Date 2022-03-06 22:04:37 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class MenuVO extends MenuBaseForm { + + @Schema(description = "菜单ID") + private Long menuId; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "创建人") + private Long createUserId; + + @Schema(description = "更新时间") + private LocalDateTime updateTime; + + @Schema(description = "更新人") + private Long updateUserId; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/service/MenuService.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/service/MenuService.java new file mode 100644 index 0000000..dceedf6 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/menu/service/MenuService.java @@ -0,0 +1,270 @@ +package net.lab1024.sa.admin.module.system.menu.service; + +import cn.hutool.core.collection.CollectionUtil; +import com.google.common.collect.Lists; +import jakarta.annotation.Resource; +import net.lab1024.sa.admin.module.system.menu.constant.MenuTypeEnum; +import net.lab1024.sa.admin.module.system.menu.dao.MenuDao; +import net.lab1024.sa.admin.module.system.menu.domain.entity.MenuEntity; +import net.lab1024.sa.admin.module.system.menu.domain.form.MenuAddForm; +import net.lab1024.sa.admin.module.system.menu.domain.form.MenuBaseForm; +import net.lab1024.sa.admin.module.system.menu.domain.form.MenuUpdateForm; +import net.lab1024.sa.admin.module.system.menu.domain.vo.MenuTreeVO; +import net.lab1024.sa.admin.module.system.menu.domain.vo.MenuVO; +import net.lab1024.sa.base.common.code.SystemErrorCode; +import net.lab1024.sa.base.common.domain.RequestUrlVO; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import org.apache.commons.lang3.math.NumberUtils; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 菜单 + * + * @Author 1024创新实验室: 善逸 + * @Date 2022-03-08 22:15:09 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class MenuService { + + @Resource + private MenuDao menuDao; + + @Resource + private List authUrl; + + /** + * 添加菜单 + * + */ + public synchronized ResponseDTO addMenu(MenuAddForm menuAddForm) { + // 校验菜单名称 + if (this.validateMenuName(menuAddForm)) { + return ResponseDTO.userErrorParam("菜单名称已存在"); + } + // 校验前端权限字符串 + if (this.validateWebPerms(menuAddForm)) { + return ResponseDTO.userErrorParam("前端权限字符串已存在"); + } + MenuEntity menuEntity = SmartBeanUtil.copy(menuAddForm, MenuEntity.class); + menuDao.insert(menuEntity); + return ResponseDTO.ok(); + } + + /** + * 更新菜单 + * + */ + public synchronized ResponseDTO updateMenu(MenuUpdateForm menuUpdateForm) { + //校验菜单是否存在 + MenuEntity selectMenu = menuDao.selectById(menuUpdateForm.getMenuId()); + if (selectMenu == null) { + return ResponseDTO.userErrorParam("菜单不存在"); + } + if (selectMenu.getDeletedFlag()) { + return ResponseDTO.userErrorParam("菜单已被删除"); + } + //校验菜单名称 + if (this.validateMenuName(menuUpdateForm)) { + return ResponseDTO.userErrorParam("菜单名称已存在"); + } + // 校验前端权限字符串 + if (this.validateWebPerms(menuUpdateForm)) { + return ResponseDTO.userErrorParam("前端权限字符串已存在"); + } + if (menuUpdateForm.getMenuId().equals(menuUpdateForm.getParentId())) { + return ResponseDTO.userErrorParam("上级菜单不能为自己"); + } + MenuEntity menuEntity = SmartBeanUtil.copy(menuUpdateForm, MenuEntity.class); + menuDao.updateById(menuEntity); + return ResponseDTO.ok(); + } + + + /** + * 批量删除菜单 + * + */ + public synchronized ResponseDTO batchDeleteMenu(List menuIdList, Long employeeId) { + if (CollectionUtils.isEmpty(menuIdList)) { + return ResponseDTO.userErrorParam("所选菜单不能为空"); + } + menuDao.deleteByMenuIdList(menuIdList, employeeId, Boolean.TRUE); + //孩子节点也需要删除 + this.recursiveDeleteChildren(menuIdList, employeeId); + return ResponseDTO.ok(); + } + + private void recursiveDeleteChildren(List menuIdList, Long employeeId) { + List childrenMenuIdList = menuDao.selectMenuIdByParentIdList(menuIdList); + if (CollectionUtil.isEmpty(childrenMenuIdList)) { + return; + } + menuDao.deleteByMenuIdList(childrenMenuIdList, employeeId, Boolean.TRUE); + recursiveDeleteChildren(childrenMenuIdList, employeeId); + } + + /** + * 校验菜单名称 + * + */ + public Boolean validateMenuName(T menuDTO) { + MenuEntity menu = menuDao.getByMenuName(menuDTO.getMenuName(), menuDTO.getParentId(), Boolean.FALSE); + if (menuDTO instanceof MenuAddForm) { + return menu != null; + } + if (menuDTO instanceof MenuUpdateForm) { + Long menuId = ((MenuUpdateForm) menuDTO).getMenuId(); + return menu != null && menu.getMenuId().longValue() != menuId.longValue(); + } + return true; + } + + /** + * 校验前端权限字符串 + * + * @return true 重复 false 未重复 + */ + public Boolean validateWebPerms(T menuDTO) { + MenuEntity menu = menuDao.getByWebPerms(menuDTO.getWebPerms(), Boolean.FALSE); + if (menuDTO instanceof MenuAddForm) { + return menu != null; + } + if (menuDTO instanceof MenuUpdateForm) { + Long menuId = ((MenuUpdateForm) menuDTO).getMenuId(); + return menu != null && menu.getMenuId().longValue() != menuId.longValue(); + } + return true; + } + + /** + * 查询菜单列表 + * + */ + public List queryMenuList(Boolean disabledFlag) { + List menuVOList = menuDao.queryMenuList(Boolean.FALSE, disabledFlag, null); + //根据ParentId进行分组 + Map> parentMap = menuVOList.stream().collect(Collectors.groupingBy(MenuVO::getParentId, Collectors.toList())); + return this.filterNoParentMenu(parentMap, NumberUtils.LONG_ZERO); + } + + /** + * 过滤没有上级菜单的菜单列表 + * + */ + private List filterNoParentMenu(Map> parentMap, Long parentId) { + // 获取本级菜单树List + List res = parentMap.getOrDefault(parentId, Lists.newArrayList()); + List childMenu = Lists.newArrayList(); + // 循环遍历下级菜单 + res.forEach(e -> { + List menuList = this.filterNoParentMenu(parentMap, e.getMenuId()); + childMenu.addAll(menuList); + }); + res.addAll(childMenu); + return res; + } + + /** + * 查询菜单树 + * + * @param onlyMenu 不查询功能点 + */ + public ResponseDTO> queryMenuTree(Boolean onlyMenu) { + List menuTypeList = Lists.newArrayList(); + if (onlyMenu) { + menuTypeList = Lists.newArrayList(MenuTypeEnum.CATALOG.getValue(), MenuTypeEnum.MENU.getValue()); + } + List menuVOList = menuDao.queryMenuList(Boolean.FALSE, null, menuTypeList); + //根据ParentId进行分组 + Map> parentMap = menuVOList.stream().collect(Collectors.groupingBy(MenuVO::getParentId, Collectors.toList())); + List menuTreeVOList = this.buildMenuTree(parentMap, NumberUtils.LONG_ZERO); + return ResponseDTO.ok(menuTreeVOList); + } + + /** + * 构建菜单树 + * + */ + List buildMenuTree(Map> parentMap, Long parentId) { + return buildMenuTree(parentMap, parentId, null); + } + + /** + * 构建菜单树 (带环路检测) + * @param parentMap 父级菜单映射 + * @param parentId 当前父级 ID + * @param visitedPath 已访问的路径 (用于检测环路) + * @return 菜单树列表 + */ + private List buildMenuTree(Map> parentMap, Long parentId, Set visitedPath) { + // 环路检测:如果当前节点已在访问路径中,说明存在循环引用 + if (visitedPath != null && visitedPath.contains(parentId)) { + throw new IllegalArgumentException("菜单存在循环引用,父级 ID: " + parentId); + } + + // 获取本级菜单树 List + List res = parentMap.getOrDefault(parentId, Lists.newArrayList()).stream() + .map(e -> SmartBeanUtil.copy(e, MenuTreeVO.class)).collect(Collectors.toList()); + + // 如果没有子菜单,直接返回 + if (res.isEmpty()) { + return res; + } + + // 创建新的访问路径集合 (避免影响同级菜单) + Set newVisitedPath = visitedPath == null ? new HashSet<>() : new HashSet<>(visitedPath); + + // 将当前层级加入访问路径 + if (parentId != null) { + newVisitedPath.add(parentId); + } + + // 循环遍历下级菜单 + res.forEach(e -> { + try { + e.setChildren(this.buildMenuTree(parentMap, e.getMenuId(), newVisitedPath)); + } catch (IllegalArgumentException ex) { + // 重新抛出异常,保持错误信息 + throw ex; + } + }); + + return res; + } + + /** + * 查询菜单详情 + * + */ + public ResponseDTO getMenuDetail(Long menuId) { + //校验菜单是否存在 + MenuEntity selectMenu = menuDao.selectById(menuId); + if (selectMenu == null) { + return ResponseDTO.error(SystemErrorCode.SYSTEM_ERROR, "菜单不存在"); + } + if (selectMenu.getDeletedFlag()) { + return ResponseDTO.error(SystemErrorCode.SYSTEM_ERROR, "菜单已被删除"); + } + MenuVO menuVO = SmartBeanUtil.copy(selectMenu, MenuVO.class); + return ResponseDTO.ok(menuVO); + } + + /** + * 获取系统所有请求路径 + */ + public ResponseDTO> getAuthUrl() { + return ResponseDTO.ok(authUrl); + } + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/message/AdminMessageController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/message/AdminMessageController.java new file mode 100644 index 0000000..40922e2 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/message/AdminMessageController.java @@ -0,0 +1,54 @@ +package net.lab1024.sa.admin.module.system.message; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import net.lab1024.sa.admin.constant.AdminSwaggerTagConst; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.domain.ValidateList; +import net.lab1024.sa.base.module.support.message.domain.MessageQueryForm; +import net.lab1024.sa.base.module.support.message.domain.MessageSendForm; +import net.lab1024.sa.base.module.support.message.domain.MessageVO; +import net.lab1024.sa.base.module.support.message.service.MessageService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + + +/** + * 后管 消息路由 + * + * @author: 卓大 + * @date: 2025/04/09 20:55 + */ +@Tag(name = AdminSwaggerTagConst.System.SYSTEM_MESSAGE) +@RestController +public class AdminMessageController { + + @Autowired + private MessageService messageService; + + @Operation(summary = "通知消息-新建 @author 卓大") + @PostMapping("/message/sendMessages") + @SaCheckPermission("system:message:send") + public ResponseDTO sendMessages(@RequestBody @Valid ValidateList messageList) { + messageService.sendMessage(messageList); + return ResponseDTO.ok(); + } + + @Operation(summary = "通知消息-分页查询 @author 卓大") + @PostMapping("/message/query") + @SaCheckPermission("system:message:query") + public ResponseDTO> query(@RequestBody @Valid MessageQueryForm queryForm) { + return ResponseDTO.ok(messageService.query(queryForm)); + } + + @Operation(summary = "通知消息-删除 @author 卓大") + @GetMapping("/message/delete/{messageId}") + @SaCheckPermission("system:message:delete") + public ResponseDTO delete(@PathVariable Long messageId) { + return messageService.delete(messageId); + } + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/position/controller/PositionController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/position/controller/PositionController.java new file mode 100644 index 0000000..de1108b --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/position/controller/PositionController.java @@ -0,0 +1,71 @@ +package net.lab1024.sa.admin.module.system.position.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import net.lab1024.sa.admin.constant.AdminSwaggerTagConst; +import net.lab1024.sa.admin.module.system.position.domain.form.PositionAddForm; +import net.lab1024.sa.admin.module.system.position.domain.form.PositionQueryForm; +import net.lab1024.sa.admin.module.system.position.domain.form.PositionUpdateForm; +import net.lab1024.sa.admin.module.system.position.domain.vo.PositionVO; +import net.lab1024.sa.admin.module.system.position.service.PositionService; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.domain.ValidateList; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 职务表 Controller + * + * @Author kaiyun + * @Date 2024-06-23 23:31:38 + * @Copyright 1024创新实验室 + */ + +@RestController +@Tag(name = AdminSwaggerTagConst.System.SYSTEM_POSITION) +public class PositionController { + + @Resource + private PositionService positionService; + + @Operation(summary = "分页查询 @author kaiyun") + @PostMapping("/position/queryPage") + public ResponseDTO> queryPage(@RequestBody @Valid PositionQueryForm queryForm) { + return ResponseDTO.ok(positionService.queryPage(queryForm)); + } + + @Operation(summary = "添加 @author kaiyun") + @PostMapping("/position/add") + public ResponseDTO add(@RequestBody @Valid PositionAddForm addForm) { + return positionService.add(addForm); + } + + @Operation(summary = "更新 @author kaiyun") + @PostMapping("/position/update") + public ResponseDTO update(@RequestBody @Valid PositionUpdateForm updateForm) { + return positionService.update(updateForm); + } + + @Operation(summary = "批量删除 @author kaiyun") + @PostMapping("/position/batchDelete") + public ResponseDTO batchDelete(@RequestBody ValidateList idList) { + return positionService.batchDelete(idList); + } + + @Operation(summary = "单个删除 @author kaiyun") + @GetMapping("/position/delete/{positionId}") + public ResponseDTO batchDelete(@PathVariable Long positionId) { + return positionService.delete(positionId); + } + + + @Operation(summary = "不分页查询 @author kaiyun") + @GetMapping("/position/queryList") + public ResponseDTO> queryList() { + return ResponseDTO.ok(positionService.queryList()); + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/position/dao/PositionDao.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/position/dao/PositionDao.java new file mode 100644 index 0000000..5fd307a --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/position/dao/PositionDao.java @@ -0,0 +1,40 @@ +package net.lab1024.sa.admin.module.system.position.dao; + +import java.util.List; +import net.lab1024.sa.admin.module.system.position.domain.entity.PositionEntity; +import net.lab1024.sa.admin.module.system.position.domain.form.PositionQueryForm; +import net.lab1024.sa.admin.module.system.position.domain.vo.PositionVO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +/** + * 职务表 Dao + * + * @Author kaiyun + * @Date 2024-06-23 23:31:38 + * @Copyright 1024创新实验室 + */ + +@Mapper +public interface PositionDao extends BaseMapper { + + /** + * 分页 查询 + * + * @param page + * @param queryForm + * @return + */ + List queryPage(Page page, @Param("queryForm") PositionQueryForm queryForm); + + + /** + * 查询 + * @param deletedFlag + * @return + */ + List queryList(@Param("deletedFlag") Boolean deletedFlag); +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/position/domain/entity/PositionEntity.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/position/domain/entity/PositionEntity.java new file mode 100644 index 0000000..e60a27d --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/position/domain/entity/PositionEntity.java @@ -0,0 +1,61 @@ +package net.lab1024.sa.admin.module.system.position.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; + +import java.time.LocalDateTime; + +import lombok.Data; + +/** + * 职务表 实体类 + * + * @Author kaiyun + * @Date 2024-06-23 23:31:38 + * @Copyright 1024创新实验室 + */ + +@Data +@TableName("t_position") +public class PositionEntity { + + /** + * 职务ID + */ + @TableId(type = IdType.AUTO) + private Long positionId; + + /** + * 职务名称 + */ + private String positionName; + + /** + * 职级 + */ + private String levelId; + + /** + * 排序 + */ + private Integer sort; + + /** + * 备注 + */ + private String remark; + + private Boolean deletedFlag; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + +} \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/position/domain/form/PositionAddForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/position/domain/form/PositionAddForm.java new file mode 100644 index 0000000..87696b6 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/position/domain/form/PositionAddForm.java @@ -0,0 +1,33 @@ +package net.lab1024.sa.admin.module.system.position.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 职务表 新建表单 + * + * @Author kaiyun + * @Date 2024-06-23 23:31:38 + * @Copyright 1024创新实验室 + */ + +@Data +public class PositionAddForm { + + @Schema(description = "职务名称", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "职务名称 不能为空") + private String positionName; + + @Schema(description = "职级") + private String level; + + @Schema(description = "排序") + @NotNull(message = "排序不能为空") + private Integer sort; + + @Schema(description = "备注") + private String remark; + +} \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/position/domain/form/PositionQueryForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/position/domain/form/PositionQueryForm.java new file mode 100644 index 0000000..c6b7155 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/position/domain/form/PositionQueryForm.java @@ -0,0 +1,23 @@ +package net.lab1024.sa.admin.module.system.position.domain.form; + +import net.lab1024.sa.base.common.domain.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 职务表 分页查询表单 + * + * @Author kaiyun + * @Date 2024-06-23 23:31:38 + * @Copyright 1024创新实验室 + */ + +@Data +public class PositionQueryForm extends PageParam{ + + @Schema(description = "关键字查询") + private String keywords; + + @Schema(hidden = true) + private Boolean deletedFlag; +} \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/position/domain/form/PositionUpdateForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/position/domain/form/PositionUpdateForm.java new file mode 100644 index 0000000..bcad625 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/position/domain/form/PositionUpdateForm.java @@ -0,0 +1,22 @@ +package net.lab1024.sa.admin.module.system.position.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 职务表 更新表单 + * + * @Author kaiyun + * @Date 2024-06-23 23:31:38 + * @Copyright 1024创新实验室 + */ + +@Data +public class PositionUpdateForm extends PositionAddForm { + + @Schema(description = "职务ID", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "职务ID 不能为空") + private Long positionId; + +} \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/position/domain/vo/PositionVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/position/domain/vo/PositionVO.java new file mode 100644 index 0000000..ee17884 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/position/domain/vo/PositionVO.java @@ -0,0 +1,40 @@ +package net.lab1024.sa.admin.module.system.position.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.time.LocalDateTime; +import lombok.Data; + +/** + * 职务表 列表VO + * + * @Author kaiyun + * @Date 2024-06-23 23:31:38 + * @Copyright 1024创新实验室 + */ + +@Data +public class PositionVO { + + + @Schema(description = "职务ID") + private Long positionId; + + @Schema(description = "职务名称") + private String positionName; + + @Schema(description = "职级") + private String level; + + @Schema(description = "排序") + private Integer sort; + + @Schema(description = "备注") + private String remark; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "更新时间") + private LocalDateTime updateTime; + +} \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/position/manager/PositionManager.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/position/manager/PositionManager.java new file mode 100644 index 0000000..1759de8 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/position/manager/PositionManager.java @@ -0,0 +1,20 @@ +package net.lab1024.sa.admin.module.system.position.manager; + +import net.lab1024.sa.admin.module.system.position.dao.PositionDao; +import net.lab1024.sa.admin.module.system.position.domain.entity.PositionEntity; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + * 职务表 Manager + * + * @Author kaiyun + * @Date 2024-06-23 23:31:38 + * @Copyright 1024创新实验室 + */ +@Service +public class PositionManager extends ServiceImpl { + + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/position/service/PositionService.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/position/service/PositionService.java new file mode 100644 index 0000000..88de6b2 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/position/service/PositionService.java @@ -0,0 +1,105 @@ +package net.lab1024.sa.admin.module.system.position.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.annotation.Resource; +import net.lab1024.sa.admin.module.system.position.dao.PositionDao; +import net.lab1024.sa.admin.module.system.position.domain.entity.PositionEntity; +import net.lab1024.sa.admin.module.system.position.domain.form.PositionAddForm; +import net.lab1024.sa.admin.module.system.position.domain.form.PositionQueryForm; +import net.lab1024.sa.admin.module.system.position.domain.form.PositionUpdateForm; +import net.lab1024.sa.admin.module.system.position.domain.vo.PositionVO; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 职务表 Service + * + * @Author kaiyun + * @Date 2024-06-23 23:31:38 + * @Copyright 1024创新实验室 + */ + +@Service +public class PositionService { + + @Resource + private PositionDao positionDao; + + /** + * 分页查询 + * + * @param queryForm + * @return + */ + public PageResult queryPage(PositionQueryForm queryForm) { + queryForm.setDeletedFlag(Boolean.FALSE); + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List list = positionDao.queryPage(page, queryForm); + PageResult pageResult = SmartPageUtil.convert2PageResult(page, list); + return pageResult; + } + + /** + * 添加 + */ + public ResponseDTO add(PositionAddForm addForm) { + PositionEntity positionEntity = SmartBeanUtil.copy(addForm, PositionEntity.class); + positionDao.insert(positionEntity); + return ResponseDTO.ok(); + } + + /** + * 更新 + * + * @param updateForm + * @return + */ + public ResponseDTO update(PositionUpdateForm updateForm) { + PositionEntity positionEntity = SmartBeanUtil.copy(updateForm, PositionEntity.class); + positionDao.updateById(positionEntity); + return ResponseDTO.ok(); + } + + /** + * 批量删除 + * + * @param idList + * @return + */ + public ResponseDTO batchDelete(List idList) { + if (CollectionUtils.isEmpty(idList)){ + return ResponseDTO.ok(); + } + + positionDao.deleteBatchIds(idList); + return ResponseDTO.ok(); + } + + /** + * 单个删除 + */ + public ResponseDTO delete(Long positionId) { + if (null == positionId){ + return ResponseDTO.ok(); + } + + positionDao.deleteById(positionId); + return ResponseDTO.ok(); + } + + /** + * 分页查询 + * + * @return + */ + public List queryList() { + List list = positionDao.queryList(Boolean.FALSE); + return list; + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/region/controller/RegionController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/region/controller/RegionController.java new file mode 100644 index 0000000..0a93c20 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/region/controller/RegionController.java @@ -0,0 +1,65 @@ +package net.lab1024.sa.admin.module.system.region.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import net.lab1024.sa.admin.constant.AdminSwaggerTagConst; +import net.lab1024.sa.admin.module.system.region.domain.form.RegionAddForm; +import net.lab1024.sa.admin.module.system.region.domain.form.RegionUpdateForm; +import net.lab1024.sa.admin.module.system.region.domain.vo.RegionTreeVO; +import net.lab1024.sa.admin.module.system.region.domain.vo.RegionVO; +import net.lab1024.sa.admin.module.system.region.service.RegionService; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 区域 Controller + * + * @Author 1024 创新实验室 + * @Date 2026-03-19 + * @Copyright 1024 创新实验室 + */ +@RestController +@RequestMapping("/region") +@Tag(name = AdminSwaggerTagConst.System.SYSTEM_REGION) +public class RegionController { + + @Resource + private RegionService regionService; + + @Operation(summary = "查询所有区域列表") + @GetMapping("/listAll") + public ResponseDTO> listAll() { + List regionList = regionService.listAll(); + return ResponseDTO.ok(regionList); + } + + @Operation(summary = "查询区域树形列表") + @GetMapping("/treeList") + public ResponseDTO> treeList() { + List regionTree = regionService.getRegionTree(); + return ResponseDTO.ok(regionTree); + } + + @Operation(summary = "添加区域") + @PostMapping("/add") + public ResponseDTO add(@Valid @RequestBody RegionAddForm addForm) { + return regionService.addRegion(addForm); + } + + @Operation(summary = "更新区域") + @PostMapping("/update") + public ResponseDTO update(@Valid @RequestBody RegionUpdateForm updateForm) { + return regionService.updateRegion(updateForm); + } + + @Operation(summary = "删除区域") + @GetMapping("/delete/{id}") + public ResponseDTO delete(@PathVariable String id) { + return regionService.deleteRegion(id); + } + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/region/dao/RegionDao.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/region/dao/RegionDao.java new file mode 100644 index 0000000..dc22ec7 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/region/dao/RegionDao.java @@ -0,0 +1,42 @@ +package net.lab1024.sa.admin.module.system.region.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import net.lab1024.sa.admin.module.system.region.domain.entity.RegionEntity; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * 区域 DAO + * + * @Author 1024 创新实验室 + * @Date 2026-03-19 + * @Copyright 1024 创新实验室 + */ +@Mapper +public interface RegionDao extends BaseMapper { + + /** + * 查询所有区域(未删除的) + * + * @return 区域列表 + */ + List listAll(); + + /** + * 根据父级 ID 查询子区域 + * + * @param parentId 父级 ID + * @return 子区域列表 + */ + List listByParentId(String parentId); + + /** + * 根据编码查询区域 + * + * @param code 区域编码 + * @return 区域信息 + */ + RegionEntity getByCode(String code); + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/region/domain/entity/RegionEntity.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/region/domain/entity/RegionEntity.java new file mode 100644 index 0000000..972452e --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/region/domain/entity/RegionEntity.java @@ -0,0 +1,155 @@ +package net.lab1024.sa.admin.module.system.region.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 区域表 实体类 + * + * @Author 1024 创新实验室 + * @Date 2026-03-19 + * @Copyright 1024 创新实验室 + */ + +@Data +@TableName("t_region") +public class RegionEntity { + + /** + * 主键 + */ + @TableId(type = IdType.INPUT) + private String id; + + /** + * 删除(0 未删/1 已删) + */ + private Boolean del; + + /** + * 目录状态 + */ + private Integer state; + + /** + * 版本号 + */ + private Integer version; + + /** + * 排序 + */ + private Float sort; + + /** + * 创建人 id + */ + private String regid; + + /** + * 创建时间 + */ + private LocalDateTime regt; + + /** + * 修改人 id + */ + private String modid; + + /** + * 修改时间 + */ + private LocalDateTime modt; + + /** + * 业务号 + */ + private String doctypeid; + + /** + * 上级 id + */ + private String pid; + + /** + * 层级 id + */ + private String levelid; + + /** + * 层级 nm + */ + private String levelnm; + + /** + * 层级 + */ + @TableField("\"level\"") + private Integer level; + + /** + * 是否叶子节点(0 否/1 是) + */ + private Boolean leaf; + + /** + * 区域编码 + */ + private String code; + + /** + * 区域名称(中文) + */ + private String name; + + /** + * 区域名称(英文) + */ + private String nameen; + + /** + * 分类名称简拼 + */ + private String namepinyin; + + /** + * 分类名称全拼 + */ + private String namepinyin1; + + /** + * 区域类型 + */ + private Integer type; + + /** + * 省级代码 + */ + private String provcd; + + /** + * 城市代码 + */ + private String citycd; + + /** + * 联系人 + */ + private String contact; + + /** + * 电话 + */ + private String phone; + + /** + * 区域描述 + */ + private String descr; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/region/domain/form/RegionAddForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/region/domain/form/RegionAddForm.java new file mode 100644 index 0000000..d35c1ee --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/region/domain/form/RegionAddForm.java @@ -0,0 +1,61 @@ +package net.lab1024.sa.admin.module.system.region.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +/** + * 区域 添加表单 + * + * @Author 1024 创新实验室 + * @Date 2026-03-19 + * @Copyright 1024 创新实验室 + */ +@Data +public class RegionAddForm { + + @Schema(description = "区域编码") + @Length(max = 50, message = "区域编码不能大于 50 个字符") + @NotNull(message = "区域编码不能为空") + private String code; + + @Schema(description = "区域名称") + @Length(min = 1, max = 100, message = "请输入正确的区域名称 (1-100 个字符)") + @NotNull(message = "请输入正确的区域名称 (1-100 个字符)") + private String name; + + @Schema(description = "区域名称(英文)") + private String nameen; + + @Schema(description = "分类名称简拼") + private String namepinyin; + + @Schema(description = "分类名称全拼") + private String namepinyin1; + + @Schema(description = "上级 id") + private String pid; + + @Schema(description = "层级") + private Integer level; + + @Schema(description = "是否叶子节点") + private Boolean leaf; + + @Schema(description = "区域类型") + private Integer type; + + @Schema(description = "排序") + private Float sort; + + @Schema(description = "联系人") + private String contact; + + @Schema(description = "电话") + private String phone; + + @Schema(description = "区域描述") + private String descr; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/region/domain/form/RegionUpdateForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/region/domain/form/RegionUpdateForm.java new file mode 100644 index 0000000..1438acc --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/region/domain/form/RegionUpdateForm.java @@ -0,0 +1,21 @@ +package net.lab1024.sa.admin.module.system.region.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 区域 更新表单 + * + * @Author 1024 创新实验室 + * @Date 2026-03-19 + * @Copyright 1024 创新实验室 + */ +@Data +public class RegionUpdateForm extends RegionAddForm { + + @Schema(description = "主键") + @NotNull(message = "主键不能为空") + private String id; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/region/domain/vo/RegionTreeVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/region/domain/vo/RegionTreeVO.java new file mode 100644 index 0000000..bdacc29 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/region/domain/vo/RegionTreeVO.java @@ -0,0 +1,21 @@ +package net.lab1024.sa.admin.module.system.region.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +/** + * 区域树形 VO + * + * @Author 1024 创新实验室 + * @Date 2026-03-19 + * @Copyright 1024 创新实验室 + */ +@Data +public class RegionTreeVO extends RegionVO { + + @Schema(description = "子区域") + private List children; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/region/domain/vo/RegionVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/region/domain/vo/RegionVO.java new file mode 100644 index 0000000..4899eb5 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/region/domain/vo/RegionVO.java @@ -0,0 +1,71 @@ +package net.lab1024.sa.admin.module.system.region.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; + +/** + * 区域 VO + * + * @Author 1024 创新实验室 + * @Date 2026-03-19 + * @Copyright 1024 创新实验室 + */ +@Data +public class RegionVO implements Serializable { + + private static final long serialVersionUID = 1L; + + @Schema(description = "主键") + private String id; + + @Schema(description = "区域编码") + private String code; + + @Schema(description = "区域名称") + private String name; + + @Schema(description = "区域名称(英文)") + private String nameen; + + @Schema(description = "分类名称简拼") + private String namepinyin; + + @Schema(description = "分类名称全拼") + private String namepinyin1; + + @Schema(description = "层级") + private Integer level; + + @Schema(description = "是否叶子节点") + private Boolean leaf; + + @Schema(description = "上级 id") + private String pid; + + @Schema(description = "层级 id") + private String levelid; + + @Schema(description = "层级 nm") + private String levelnm; + + @Schema(description = "区域类型") + private Integer type; + + @Schema(description = "省级代码") + private String provcd; + + @Schema(description = "城市代码") + private String citycd; + + @Schema(description = "联系人") + private String contact; + + @Schema(description = "电话") + private String phone; + + @Schema(description = "区域描述") + private String descr; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/region/service/RegionService.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/region/service/RegionService.java new file mode 100644 index 0000000..6a81213 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/region/service/RegionService.java @@ -0,0 +1,177 @@ +package net.lab1024.sa.admin.module.system.region.service; + +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.admin.module.system.region.dao.RegionDao; +import net.lab1024.sa.admin.module.system.region.domain.entity.RegionEntity; +import net.lab1024.sa.admin.module.system.region.domain.form.RegionAddForm; +import net.lab1024.sa.admin.module.system.region.domain.form.RegionUpdateForm; +import net.lab1024.sa.admin.module.system.region.domain.vo.RegionTreeVO; +import net.lab1024.sa.admin.module.system.region.domain.vo.RegionVO; +import net.lab1024.sa.base.common.code.UserErrorCode; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 区域 Service + * + * @Author 1024 创新实验室 + * @Date 2026-03-19 + * @Copyright 1024 创新实验室 + */ +@Slf4j +@Service +public class RegionService { + + @Resource + private RegionDao regionDao; + + /** + * 添加区域 + * + * @param addForm 添加表单 + * @return 操作结果 + */ + public ResponseDTO addRegion(RegionAddForm addForm) { + RegionEntity entity = SmartBeanUtil.copy(addForm, RegionEntity.class); + if (entity.getSort() == null) { + entity.setSort(0f); + } + if (entity.getLeaf() == null) { + entity.setLeaf(true); + } + if (entity.getDel() == null) { + entity.setDel(false); + } + if (entity.getState() == null) { + entity.setState(1); + } + if (entity.getVersion() == null) { + entity.setVersion(1); + } + regionDao.insert(entity); + return ResponseDTO.ok(); + } + + /** + * 更新区域 + * + * @param updateForm 更新表单 + * @return 操作结果 + */ + public ResponseDTO updateRegion(RegionUpdateForm updateForm) { + RegionEntity entity = SmartBeanUtil.copy(updateForm, RegionEntity.class); + regionDao.updateById(entity); + return ResponseDTO.ok(); + } + + /** + * 删除区域 + * + * @param id 区域 ID + * @return 操作结果 + */ + public ResponseDTO deleteRegion(String id) { + RegionEntity entity = regionDao.selectById(id); + if (entity == null) { + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST); + } + // 逻辑删除 + entity.setDel(true); + regionDao.updateById(entity); + return ResponseDTO.ok(); + } + + /** + * 查询所有区域 + * + * @return 区域列表 + */ + public List listAll() { + List entityList = regionDao.listAll(); + return SmartBeanUtil.copyList(entityList, RegionVO.class); + } + + /** + * 查询区域树 + * + * @return 区域树 + */ + public List getRegionTree() { + List entityList = regionDao.listAll(); + return buildRegionTree(entityList, null); + } + + /** + * 根据父级 ID 查询子区域 + * + * @param parentId 父级 ID + * @return 区域列表 + */ + public List listByParentId(String parentId) { + List entityList = regionDao.listByParentId(parentId); + return SmartBeanUtil.copyList(entityList, RegionVO.class); + } + + /** + * 根据编码获取区域名称 + * + * @param code 区域编码 + * @return 区域名称 + */ + public String getRegionNameByCode(String code) { + if (StringUtils.isEmpty(code)) { + return ""; + } + RegionEntity entity = regionDao.getByCode(code); + return entity != null ? entity.getName() : ""; + } + + /** + * 构建区域树 + * + * @param entityList 区域列表 + * @param parentId 父级 ID,null 表示构建顶级树 + * @return 区域树 + */ + private List buildRegionTree(List entityList, String parentId) { + if (CollectionUtils.isEmpty(entityList)) { + return new ArrayList<>(); + } + + // 转换为 VO + List voList = SmartBeanUtil.copyList(entityList, RegionTreeVO.class); + + // 如果 parentId 为 null,查找所有顶级节点(pid 为空或 0) + List treeList; + if (parentId == null) { + treeList = voList.stream() + .filter(e -> StringUtils.isEmpty(e.getPid()) || "0".equals(e.getPid())) + .collect(Collectors.toList()); + } else { + treeList = voList.stream() + .filter(e -> parentId.equals(e.getPid())) + .collect(Collectors.toList()); + } + + // 递归构建子节点 + for (RegionTreeVO node : treeList) { + List children = buildRegionTree(entityList, node.getId()); + if (CollectionUtils.isNotEmpty(children)) { + node.setChildren(children); + } + } + + return treeList; + } + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/controller/RoleController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/controller/RoleController.java new file mode 100644 index 0000000..40448bb --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/controller/RoleController.java @@ -0,0 +1,67 @@ +package net.lab1024.sa.admin.module.system.role.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import net.lab1024.sa.admin.constant.AdminSwaggerTagConst; +import net.lab1024.sa.admin.module.system.role.domain.form.RoleAddForm; +import net.lab1024.sa.admin.module.system.role.domain.form.RoleUpdateForm; +import net.lab1024.sa.admin.module.system.role.domain.vo.RoleVO; +import net.lab1024.sa.admin.module.system.role.service.RoleService; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 角色 + * + * @Author 1024创新实验室: 胡克 + * @Date 2021-12-14 19:40:28 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@RestController +@Tag(name = AdminSwaggerTagConst.System.SYSTEM_ROLE) +public class RoleController { + + @Resource + private RoleService roleService; + + @Operation(summary = "添加角色 @author 卓大") + @PostMapping("/role/add") + @SaCheckPermission("system:role:add") + public ResponseDTO addRole(@Valid @RequestBody RoleAddForm roleAddForm) { + return roleService.addRole(roleAddForm); + } + + @Operation(summary = "删除角色 @author 卓大") + @GetMapping("/role/delete/{roleId}") + @SaCheckPermission("system:role:delete") + public ResponseDTO deleteRole(@PathVariable Long roleId) { + return roleService.deleteRole(roleId); + } + + @Operation(summary = "更新角色 @author 卓大") + @PostMapping("/role/update") + @SaCheckPermission("system:role:update") + public ResponseDTO updateRole(@Valid @RequestBody RoleUpdateForm roleUpdateDTO) { + return roleService.updateRole(roleUpdateDTO); + } + + @Operation(summary = "获取角色数据 @author 卓大") + @GetMapping("/role/get/{roleId}") + public ResponseDTO getRole(@PathVariable("roleId") Long roleId) { + return roleService.getRoleById(roleId); + } + + @Operation(summary = "获取所有角色 @author 卓大") + @GetMapping("/role/getAll") + public ResponseDTO> getAllRole() { + return roleService.getAllRole(); + } + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/controller/RoleDataScopeController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/controller/RoleDataScopeController.java new file mode 100644 index 0000000..7deead5 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/controller/RoleDataScopeController.java @@ -0,0 +1,47 @@ +package net.lab1024.sa.admin.module.system.role.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import net.lab1024.sa.admin.constant.AdminSwaggerTagConst; +import net.lab1024.sa.admin.module.system.role.domain.form.RoleDataScopeUpdateForm; +import net.lab1024.sa.admin.module.system.role.domain.vo.RoleDataScopeVO; +import net.lab1024.sa.admin.module.system.role.service.RoleDataScopeService; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 角色的数据权限配置 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-02-26 22:09:59 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@RestController +@Tag(name = AdminSwaggerTagConst.System.SYSTEM_ROLE_DATA_SCOPE) +public class RoleDataScopeController { + + @Resource + private RoleDataScopeService roleDataScopeService; + + @Operation(summary = "获取某角色所设置的数据范围 @author 卓大") + @GetMapping("/role/dataScope/getRoleDataScopeList/{roleId}") + public ResponseDTO> dataScopeListByRole(@PathVariable Long roleId) { + return roleDataScopeService.getRoleDataScopeList(roleId); + } + + @Operation(summary = "批量设置某角色数据范围 @author 卓大") + @PostMapping("/role/dataScope/updateRoleDataScopeList") + @SaCheckPermission("system:role:dataScope:update") + public ResponseDTO updateRoleDataScopeList(@RequestBody @Valid RoleDataScopeUpdateForm roleDataScopeUpdateForm) { + return roleDataScopeService.updateRoleDataScopeList(roleDataScopeUpdateForm); + } + + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/controller/RoleEmployeeController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/controller/RoleEmployeeController.java new file mode 100644 index 0000000..46676f5 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/controller/RoleEmployeeController.java @@ -0,0 +1,74 @@ +package net.lab1024.sa.admin.module.system.role.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import net.lab1024.sa.admin.constant.AdminSwaggerTagConst; +import net.lab1024.sa.admin.module.system.employee.domain.vo.EmployeeVO; +import net.lab1024.sa.admin.module.system.role.domain.form.RoleEmployeeQueryForm; +import net.lab1024.sa.admin.module.system.role.domain.form.RoleEmployeeUpdateForm; +import net.lab1024.sa.admin.module.system.role.domain.vo.RoleSelectedVO; +import net.lab1024.sa.admin.module.system.role.service.RoleEmployeeService; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 角色的员工 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-02-26 22:09:59 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@RestController +@Tag(name = AdminSwaggerTagConst.System.SYSTEM_ROLE_EMPLOYEE) +public class RoleEmployeeController { + + @Resource + private RoleEmployeeService roleEmployeeService; + + @Operation(summary = "查询某个角色下的员工列表 @author 卓大") + @PostMapping("/role/employee/queryEmployee") + public ResponseDTO> queryEmployee(@Valid @RequestBody RoleEmployeeQueryForm roleEmployeeQueryForm) { + return roleEmployeeService.queryEmployee(roleEmployeeQueryForm); + } + + @Operation(summary = "获取某个角色下的所有员工列表(无分页) @author 卓大") + @GetMapping("/role/employee/getAllEmployeeByRoleId/{roleId}") + public ResponseDTO> listAllEmployeeRoleId(@PathVariable Long roleId) { + return ResponseDTO.ok(roleEmployeeService.getAllEmployeeByRoleId(roleId)); + } + + @Operation(summary = "从角色成员列表中移除员工 @author 卓大") + @GetMapping("/role/employee/removeEmployee") + @SaCheckPermission("system:role:employee:delete") + public ResponseDTO removeEmployee(Long employeeId, Long roleId) { + return roleEmployeeService.removeRoleEmployee(employeeId, roleId); + } + + @Operation(summary = "从角色成员列表中批量移除员工 @author 卓大") + @PostMapping("/role/employee/batchRemoveRoleEmployee") + @SaCheckPermission("system:role:employee:batch:delete") + public ResponseDTO batchRemoveEmployee(@Valid @RequestBody RoleEmployeeUpdateForm updateForm) { + return roleEmployeeService.batchRemoveRoleEmployee(updateForm); + } + + @Operation(summary = "角色成员列表中批量添加员工 @author 卓大") + @PostMapping("/role/employee/batchAddRoleEmployee") + @SaCheckPermission("system:role:employee:add") + public ResponseDTO addEmployeeList(@Valid @RequestBody RoleEmployeeUpdateForm addForm) { + return roleEmployeeService.batchAddRoleEmployee(addForm); + } + + @Operation(summary = "获取员工所有选中的角色和所有角色 @author 卓大") + @GetMapping("/role/employee/getRoles/{employeeId}") + public ResponseDTO> getRoleByEmployeeId(@PathVariable Long employeeId) { + return ResponseDTO.ok(roleEmployeeService.getRoleInfoListByEmployeeId(employeeId)); + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/controller/RoleMenuController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/controller/RoleMenuController.java new file mode 100644 index 0000000..510ccc8 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/controller/RoleMenuController.java @@ -0,0 +1,43 @@ +package net.lab1024.sa.admin.module.system.role.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import net.lab1024.sa.admin.constant.AdminSwaggerTagConst; +import net.lab1024.sa.admin.module.system.role.domain.form.RoleMenuUpdateForm; +import net.lab1024.sa.admin.module.system.role.domain.vo.RoleMenuTreeVO; +import net.lab1024.sa.admin.module.system.role.service.RoleMenuService; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import org.springframework.web.bind.annotation.*; + +/** + * 角色的菜单 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-02-26 21:34:01 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@RestController +@Tag(name = AdminSwaggerTagConst.System.SYSTEM_ROLE_MENU) +public class RoleMenuController { + + @Resource + private RoleMenuService roleMenuService; + + @Operation(summary = "更新角色权限 @author 卓大") + @PostMapping("/role/menu/updateRoleMenu") + @SaCheckPermission("system:role:menu:update") + public ResponseDTO updateRoleMenu(@Valid @RequestBody RoleMenuUpdateForm updateDTO) { + return roleMenuService.updateRoleMenu(updateDTO); + } + + @Operation(summary = "获取角色关联菜单权限 @author 卓大") + @GetMapping("/role/menu/getRoleSelectedMenu/{roleId}") + public ResponseDTO getRoleSelectedMenu(@PathVariable Long roleId) { + return roleMenuService.getRoleSelectedMenu(roleId); + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/dao/RoleDao.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/dao/RoleDao.java new file mode 100644 index 0000000..2e02983 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/dao/RoleDao.java @@ -0,0 +1,47 @@ +package net.lab1024.sa.admin.module.system.role.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.admin.module.system.department.domain.form.DepartmentQueryForm; +import net.lab1024.sa.admin.module.system.department.domain.vo.DepartmentVO; +import net.lab1024.sa.admin.module.system.role.domain.form.RoleQueryForm; +import net.lab1024.sa.admin.module.system.role.domain.vo.RoleVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; +import net.lab1024.sa.admin.module.system.role.domain.entity.RoleEntity; + +import java.util.List; + +/** + * 角色 dao + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022-02-26 21:34:01 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface RoleDao extends BaseMapper { + + /** + * 根据角色名称查询 + */ + RoleEntity getByRoleName(@Param("roleName") String roleName); + + /** + * 根据角色编码 + */ + RoleEntity getByRoleCode(@Param("roleCode") String roleCode); + + /** + * 分页 查询 + * + * @param page + * @param queryForm + * @return + */ + List queryPage(Page page, @Param("queryForm") RoleQueryForm queryForm); + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/dao/RoleDataScopeDao.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/dao/RoleDataScopeDao.java new file mode 100644 index 0000000..4ca3d65 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/dao/RoleDataScopeDao.java @@ -0,0 +1,41 @@ +package net.lab1024.sa.admin.module.system.role.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; +import net.lab1024.sa.admin.module.system.role.domain.entity.RoleDataScopeEntity; + +import java.util.List; + + +/** + * 角色 数据权限 dao + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022-02-26 21:34:01 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface RoleDataScopeDao extends BaseMapper { + + /** + * 获取某个角色的设置信息 + */ + List listByRoleId(@Param("roleId") Long roleId); + + RoleDataScopeEntity infoByEmployeeId(@Param("employeeId") Long employeeId); + + /** + * 获取某批角色的所有数据范围配置信息 + */ + List listByRoleIdList(@Param("roleIdList") List roleIdList); + + /** + * 删除某个角色的设置信息 + */ + void deleteByRoleId(@Param("roleId") Long roleId); + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/dao/RoleEmployeeDao.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/dao/RoleEmployeeDao.java new file mode 100644 index 0000000..71de64c --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/dao/RoleEmployeeDao.java @@ -0,0 +1,88 @@ +package net.lab1024.sa.admin.module.system.role.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.admin.module.system.role.domain.vo.RoleVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; +import net.lab1024.sa.admin.module.system.employee.domain.vo.EmployeeVO; +import net.lab1024.sa.admin.module.system.role.domain.entity.RoleEmployeeEntity; +import net.lab1024.sa.admin.module.system.role.domain.form.RoleEmployeeQueryForm; +import net.lab1024.sa.admin.module.system.role.domain.vo.RoleEmployeeVO; + +import java.util.List; +import java.util.Set; + + +/** + * 角色 员工 dao + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022-03-07 18:54:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface RoleEmployeeDao extends BaseMapper { + + /** + * 根据员工id 查询所有的角色 + */ + List selectRoleByEmployeeId(@Param("employeeId") Long employeeId); + + /** + * 根据员工id 查询所有的角色io集合 + */ + List selectRoleIdByEmployeeId(@Param("employeeId") Long employeeId); + + /** + * 根据员工id 查询所有的角色 + */ + List selectRoleIdByEmployeeIdList(@Param("employeeIdList") List employeeIdList); + + /** + * 根据员工id 查询所有的角色 + */ + List selectRoleByEmployeeIdList(@Param("employeeIdList") List employeeIdList); + + /** + * 查询角色下的人员id + */ + Set selectEmployeeIdByRoleIdList(@Param("roleIdList") List roleIdList); + + /** + * + */ + List selectRoleEmployeeByName(Page page, @Param("queryForm") RoleEmployeeQueryForm roleEmployeeQueryForm); + + /** + * + */ + List selectEmployeeByRoleId(@Param("roleId") Long roleId); + /** + * 根据员工信息删除 + */ + void deleteByEmployeeId(@Param("employeeId") Long employeeId); + + /** + * 删除某个角色的所有关系 + */ + void deleteByRoleId(@Param("roleId")Long roleId); + + /** + * 根据员工和 角色删除关系 + */ + void deleteByEmployeeIdRoleId(@Param("employeeId") Long employeeId,@Param("roleId")Long roleId); + + /** + * 批量删除某个角色下的某批用户的关联关系 + */ + void batchDeleteEmployeeRole(@Param("roleId") Long roleId, @Param("employeeIds") Set employeeIds); + + /** + * 判断某个角色下是否存在用户 + */ + Integer existsByRoleId(@Param("roleId") Long roleId); +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/dao/RoleMenuDao.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/dao/RoleMenuDao.java new file mode 100644 index 0000000..dd11d84 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/dao/RoleMenuDao.java @@ -0,0 +1,47 @@ +package net.lab1024.sa.admin.module.system.role.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import net.lab1024.sa.admin.module.system.menu.domain.entity.MenuEntity; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; +import net.lab1024.sa.admin.module.system.role.domain.entity.RoleMenuEntity; + +import java.util.List; + +/** + * 角色 菜单 dao + * + * @Author 1024创新实验室: 善逸 + * @Date 2022-03-07 18:54:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface RoleMenuDao extends BaseMapper { + + /** + * 根据角色ID删除菜单权限 + * + */ + void deleteByRoleId(@Param("roleId") Long roleId); + + /** + * 根据角色ID查询选择的菜单权限 + * + */ + List queryMenuIdByRoleId(@Param("roleId") Long roleId); + + /** + * 根据角色ID集合查询选择的菜单权限 + * + */ + List selectMenuListByRoleIdList(@Param("roleIdList") List roleIdList, @Param("deletedFlag") Boolean deletedFlag); + + /** + * 查询所有的菜单角色 + * + */ + List queryAllRoleMenu(); +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/entity/RoleDataScopeEntity.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/entity/RoleDataScopeEntity.java new file mode 100644 index 0000000..40f04f5 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/entity/RoleDataScopeEntity.java @@ -0,0 +1,53 @@ +package net.lab1024.sa.admin.module.system.role.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import net.lab1024.sa.admin.module.system.datascope.constant.DataScopeTypeEnum; +import net.lab1024.sa.admin.module.system.datascope.constant.DataScopeViewTypeEnum; + +import java.time.LocalDateTime; + +/** + * 数据范围与角色关系 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022-03-07 18:54:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName("t_role_data_scope") +public class RoleDataScopeEntity { + /** + * 主键id + */ + @TableId(type = IdType.AUTO) + private Long id; + /** + * 数据范围id + * {@link DataScopeTypeEnum} + */ + private Integer dataScopeType; + /** + * 数据范围类型 + * {@link DataScopeViewTypeEnum} + */ + private Integer viewType; + /** + * 角色id + */ + private Long roleId; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + + /** + * 创建时间 + */ + private LocalDateTime createTime; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/entity/RoleEmployeeEntity.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/entity/RoleEmployeeEntity.java new file mode 100644 index 0000000..636bd0b --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/entity/RoleEmployeeEntity.java @@ -0,0 +1,41 @@ +package net.lab1024.sa.admin.module.system.role.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 角色 员工关系 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022-03-07 18:54:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName("t_role_employee") +public class RoleEmployeeEntity { + + @TableId(type = IdType.AUTO) + private Long id; + + private Long roleId; + + private Long employeeId; + + private LocalDateTime updateTime; + + private LocalDateTime createTime; + + public RoleEmployeeEntity() { + } + + public RoleEmployeeEntity(Long roleId, Long employeeId) { + this.roleId = roleId; + this.employeeId = employeeId; + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/entity/RoleEntity.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/entity/RoleEntity.java new file mode 100644 index 0000000..12e2219 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/entity/RoleEntity.java @@ -0,0 +1,52 @@ +package net.lab1024.sa.admin.module.system.role.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 角色 + * + * @Author 1024创新实验室: 胡克 + * @Date 2022-03-07 18:54:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName("t_role") +public class RoleEntity { + /** + * 主键id + */ + @TableId(type = IdType.AUTO) + private Long roleId; + + /** + * 角色名称 + */ + private String roleName; + + /** + * 角色编码 + */ + private String roleCode; + + /** + * 角色备注 + */ + private String remark; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + + /** + * 创建时间 + */ + private LocalDateTime createTime; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/entity/RoleMenuEntity.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/entity/RoleMenuEntity.java new file mode 100644 index 0000000..410d248 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/entity/RoleMenuEntity.java @@ -0,0 +1,49 @@ +package net.lab1024.sa.admin.module.system.role.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 角色 菜单 + * + * @Author 1024创新实验室: 善逸 + * @Date 2022-03-16 23:00:57 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName("t_role_menu") +public class RoleMenuEntity { + + /** + * 主键id + */ + @TableId(type = IdType.AUTO) + private Long roleMenuId; + + /** + * 角色 id + */ + private Long roleId; + + /** + * 菜单 id + */ + private Long menuId; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/form/RoleAddForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/form/RoleAddForm.java new file mode 100644 index 0000000..cc06bdc --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/form/RoleAddForm.java @@ -0,0 +1,41 @@ +package net.lab1024.sa.admin.module.system.role.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +/** + * 角色 添加表单 + * + * @Author 1024创新实验室: 胡克 + * @Date 2022-02-26 19:09:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class RoleAddForm { + + /** + * 角色名称 + */ + @Schema(description = "角色名称") + @NotNull(message = "角色名称不能为空") + @Length(min = 1, max = 20, message = "角色名称(1-20)个字符") + private String roleName; + + @Schema(description = "角色编码") + @NotNull(message = "角色编码 不能为空") + @Length(min = 1, max = 20, message = "角色编码(1-20)个字符") + private String roleCode; + + /** + * 角色描述 + */ + @Schema(description = "角色描述") + @Length(max = 255, message = "角色描述最多255个字符") + private String remark; + + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/form/RoleDataScopeUpdateForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/form/RoleDataScopeUpdateForm.java new file mode 100644 index 0000000..7a48fa0 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/form/RoleDataScopeUpdateForm.java @@ -0,0 +1,43 @@ +package net.lab1024.sa.admin.module.system.role.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +/** + * 角色的数据范围更新 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022-04-08 21:53:04 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class RoleDataScopeUpdateForm { + + @Schema(description = "角色id") + @NotNull(message = "角色id不能为空") + private Long roleId; + + @Schema(description = "设置信息") + @Valid + private List dataScopeItemList; + + + @Data + public static class RoleUpdateDataScopeListFormItem { + + @Schema(description = "数据范围类型") + @NotNull(message = "数据范围类型不能为空") + private Integer dataScopeType; + + @Schema(description = "可见范围") + @NotNull(message = "可见范围不能为空") + private Integer viewType; + } + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/form/RoleEmployeeQueryForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/form/RoleEmployeeQueryForm.java new file mode 100644 index 0000000..46b21e8 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/form/RoleEmployeeQueryForm.java @@ -0,0 +1,24 @@ +package net.lab1024.sa.admin.module.system.role.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; + +/** + * 角色的员工查询 + * + * @Author 1024创新实验室: 善逸 + * @Date 2022-04-08 21:53:04 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class RoleEmployeeQueryForm extends PageParam { + + @Schema(description = "关键字") + private String keywords; + + @Schema(description = "角色id") + private String roleId; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/form/RoleEmployeeUpdateForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/form/RoleEmployeeUpdateForm.java new file mode 100644 index 0000000..2877570 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/form/RoleEmployeeUpdateForm.java @@ -0,0 +1,31 @@ +package net.lab1024.sa.admin.module.system.role.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; +import java.util.Set; + +/** + * 角色的员工更新 + * + * @Author 1024创新实验室: 善逸 + * @Date 2022-04-08 21:53:04 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class RoleEmployeeUpdateForm { + + @Schema(description = "角色id") + @NotNull(message = "角色id不能为空") + protected Long roleId; + + @Schema(description = "员工id集合") + @NotEmpty(message = "员工id不能为空") + protected Set employeeIdList; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/form/RoleMenuUpdateForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/form/RoleMenuUpdateForm.java new file mode 100644 index 0000000..ec58963 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/form/RoleMenuUpdateForm.java @@ -0,0 +1,35 @@ +package net.lab1024.sa.admin.module.system.role.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +/** + * 角色的菜单更新 + * + * @Author 1024创新实验室: 善逸 + * @Date 2022-04-08 21:53:04 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class RoleMenuUpdateForm { + + /** + * 角色id + */ + @Schema(description = "角色id") + @NotNull(message = "角色id不能为空") + private Long roleId; + + /** + * 菜单ID 集合 + */ + @Schema(description = "菜单ID集合") + @NotNull(message = "菜单ID不能为空") + private List menuIdList; + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/form/RoleQueryForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/form/RoleQueryForm.java new file mode 100644 index 0000000..2d0f79e --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/form/RoleQueryForm.java @@ -0,0 +1,35 @@ +package net.lab1024.sa.admin.module.system.role.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; + +import java.time.LocalDate; + +/** + * 角色 查询 + * + * @Author 1024创新实验室: 胡克 + * @Date 2022-02-26 19:09:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class RoleQueryForm extends PageParam { + + @Schema(description = "角色code") + private String roleCode; + + @Schema(description = "角色名称") + private String roleName; + + @Schema(description = "角色id") + private String roleId; + + @Schema(description = "开始时间") + private LocalDate startTime; + + @Schema(description = "结束时间") + private LocalDate endTime; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/form/RoleUpdateForm.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/form/RoleUpdateForm.java new file mode 100644 index 0000000..19a42f1 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/form/RoleUpdateForm.java @@ -0,0 +1,27 @@ +package net.lab1024.sa.admin.module.system.role.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 角色更新修改 + * + * @Author 1024创新实验室: 胡克 + * @Date 2022-02-26 19:09:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class RoleUpdateForm extends RoleAddForm { + + /** + * 角色id + */ + @Schema(description = "角色id") + @NotNull(message = "角色id不能为空") + protected Long roleId; + + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/vo/RoleDataScopeVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/vo/RoleDataScopeVO.java new file mode 100644 index 0000000..350b644 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/vo/RoleDataScopeVO.java @@ -0,0 +1,23 @@ +package net.lab1024.sa.admin.module.system.role.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 角色的数据范围 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022-04-08 21:53:04 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class RoleDataScopeVO { + + @Schema(description = "数据范围id") + private Integer dataScopeType; + + @Schema(description = "可见范围") + private Integer viewType; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/vo/RoleEmployeeVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/vo/RoleEmployeeVO.java new file mode 100644 index 0000000..83d4023 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/vo/RoleEmployeeVO.java @@ -0,0 +1,22 @@ +package net.lab1024.sa.admin.module.system.role.domain.vo; + +import lombok.Data; + +/** + * 角色的员工 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022-04-08 21:53:04 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class RoleEmployeeVO { + + private Long roleId; + + private Long employeeId; + + private String roleName; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/vo/RoleMenuTreeVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/vo/RoleMenuTreeVO.java new file mode 100644 index 0000000..2809115 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/vo/RoleMenuTreeVO.java @@ -0,0 +1,29 @@ +package net.lab1024.sa.admin.module.system.role.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.admin.module.system.menu.domain.vo.MenuSimpleTreeVO; + +import java.util.List; + +/** + * 角色菜单树 + * + * @Author 1024创新实验室: 善逸 + * @Date 2022-04-08 21:53:04 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class RoleMenuTreeVO { + + @Schema(description = "角色ID") + private Long roleId; + + @Schema(description = "菜单列表") + private List menuTreeList; + + @Schema(description = "选中的菜单ID") + private List selectedMenuId; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/vo/RoleSelectedVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/vo/RoleSelectedVO.java new file mode 100644 index 0000000..1165cff --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/vo/RoleSelectedVO.java @@ -0,0 +1,20 @@ +package net.lab1024.sa.admin.module.system.role.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 选择角色 + * + * @Author 1024创新实验室: 善逸 + * @Date 2022-04-08 21:53:04 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class RoleSelectedVO extends RoleVO { + + @Schema(description = "角色名称") + private Boolean selected; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/vo/RoleVO.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/vo/RoleVO.java new file mode 100644 index 0000000..1cbfc9f --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/domain/vo/RoleVO.java @@ -0,0 +1,37 @@ +package net.lab1024.sa.admin.module.system.role.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 角色 + * + * @Author 1024创新实验室: 善逸 + * @Date 2022-04-08 21:53:04 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class RoleVO { + + @Schema(description = "角色ID") + private Long roleId; + + @Schema(description = "角色名称") + private String roleName; + + @Schema(description = "角色编码") + private String roleCode; + + @Schema(description = "角色备注") + private String remark; + + /** + * 创建时间 + */ + @Schema(description = "创建时间") + private LocalDateTime createTime; +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/manager/RoleDataScopeManager.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/manager/RoleDataScopeManager.java new file mode 100644 index 0000000..bc9e0a4 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/manager/RoleDataScopeManager.java @@ -0,0 +1,20 @@ +package net.lab1024.sa.admin.module.system.role.manager; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import net.lab1024.sa.admin.module.system.role.dao.RoleDataScopeDao; +import net.lab1024.sa.admin.module.system.role.domain.entity.RoleDataScopeEntity; +import org.springframework.stereotype.Service; + +/** + * 角色 数据范围 manager + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-04-08 21:53:04 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class RoleDataScopeManager extends ServiceImpl { + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/manager/RoleEmployeeManager.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/manager/RoleEmployeeManager.java new file mode 100644 index 0000000..6e2d975 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/manager/RoleEmployeeManager.java @@ -0,0 +1,24 @@ +package net.lab1024.sa.admin.module.system.role.manager; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import net.lab1024.sa.admin.module.system.role.dao.RoleEmployeeDao; +import net.lab1024.sa.admin.module.system.role.domain.entity.RoleEmployeeEntity; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/** + * 角色员工 manager + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-04-08 21:53:04 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class RoleEmployeeManager extends ServiceImpl { + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/manager/RoleMenuManager.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/manager/RoleMenuManager.java new file mode 100644 index 0000000..f114531 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/manager/RoleMenuManager.java @@ -0,0 +1,38 @@ +package net.lab1024.sa.admin.module.system.role.manager; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import jakarta.annotation.Resource; +import net.lab1024.sa.admin.module.system.role.dao.RoleMenuDao; +import net.lab1024.sa.admin.module.system.role.domain.entity.RoleMenuEntity; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/** + * 角色-菜单 manager + * + * @Author 1024创新实验室: 善逸 + * @Date 2022-04-09 19:05:49 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class RoleMenuManager extends ServiceImpl { + + @Resource + private RoleMenuDao roleMenuDao; + + /** + * 更新角色权限 + * + */ + @Transactional(rollbackFor = Exception.class) + public void updateRoleMenu(Long roleId, List roleMenuEntityList) { + // 根据角色ID删除菜单权限 + roleMenuDao.deleteByRoleId(roleId); + // 批量添加菜单权限 + saveBatch(roleMenuEntityList); + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/service/RoleDataScopeService.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/service/RoleDataScopeService.java new file mode 100644 index 0000000..40211bf --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/service/RoleDataScopeService.java @@ -0,0 +1,63 @@ +package net.lab1024.sa.admin.module.system.role.service; + +import com.google.common.collect.Lists; +import jakarta.annotation.Resource; +import net.lab1024.sa.admin.module.system.role.domain.entity.RoleDataScopeEntity; +import net.lab1024.sa.admin.module.system.role.domain.form.RoleDataScopeUpdateForm; +import net.lab1024.sa.admin.module.system.role.domain.vo.RoleDataScopeVO; +import net.lab1024.sa.admin.module.system.role.manager.RoleDataScopeManager; +import net.lab1024.sa.base.common.code.UserErrorCode; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import java.util.List; + +/** + * 角色-数据范围 + * + * @Author 1024创新实验室: 善逸 + * @Date 2021-10-22 23:17:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class RoleDataScopeService { + + @Resource + private RoleDataScopeManager roleDataScopeManager; + + + /** + * 获取某个角色的数据范围设置信息 + * + */ + public ResponseDTO> getRoleDataScopeList(Long roleId) { + List roleDataScopeEntityList = roleDataScopeManager.getBaseMapper().listByRoleId(roleId); + if (CollectionUtils.isEmpty(roleDataScopeEntityList)) { + return ResponseDTO.ok(Lists.newArrayList()); + } + List roleDataScopeList = SmartBeanUtil.copyList(roleDataScopeEntityList, RoleDataScopeVO.class); + return ResponseDTO.ok(roleDataScopeList); + } + + /** + * 批量设置某个角色的数据范围设置信息 + * + */ + @Transactional(rollbackFor = Exception.class) + public ResponseDTO updateRoleDataScopeList(RoleDataScopeUpdateForm roleDataScopeUpdateForm) { + List batchSetList = roleDataScopeUpdateForm.getDataScopeItemList(); + if (CollectionUtils.isEmpty(batchSetList)) { + return ResponseDTO.error(UserErrorCode.PARAM_ERROR, "缺少配置信息"); + } + List roleDataScopeEntityList = SmartBeanUtil.copyList(batchSetList, RoleDataScopeEntity.class); + roleDataScopeEntityList.forEach(e -> e.setRoleId(roleDataScopeUpdateForm.getRoleId())); + roleDataScopeManager.getBaseMapper().deleteByRoleId(roleDataScopeUpdateForm.getRoleId()); + roleDataScopeManager.saveBatch(roleDataScopeEntityList); + return ResponseDTO.ok(); + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/service/RoleEmployeeService.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/service/RoleEmployeeService.java new file mode 100644 index 0000000..d77f364 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/service/RoleEmployeeService.java @@ -0,0 +1,155 @@ +package net.lab1024.sa.admin.module.system.role.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.google.common.collect.Lists; +import jakarta.annotation.Resource; +import net.lab1024.sa.admin.module.system.department.dao.DepartmentDao; +import net.lab1024.sa.admin.module.system.department.domain.entity.DepartmentEntity; +import net.lab1024.sa.admin.module.system.employee.domain.vo.EmployeeVO; +import net.lab1024.sa.admin.module.system.role.dao.RoleDao; +import net.lab1024.sa.admin.module.system.role.dao.RoleEmployeeDao; +import net.lab1024.sa.admin.module.system.role.domain.entity.RoleEmployeeEntity; +import net.lab1024.sa.admin.module.system.role.domain.entity.RoleEntity; +import net.lab1024.sa.admin.module.system.role.domain.form.RoleEmployeeQueryForm; +import net.lab1024.sa.admin.module.system.role.domain.form.RoleEmployeeUpdateForm; +import net.lab1024.sa.admin.module.system.role.domain.vo.RoleSelectedVO; +import net.lab1024.sa.admin.module.system.role.domain.vo.RoleVO; +import net.lab1024.sa.admin.module.system.role.manager.RoleEmployeeManager; +import net.lab1024.sa.base.common.constant.StringConst; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 角色-员工 + * + * @Author 1024创新实验室: 善逸 + * @Date 2021-10-22 23:17:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class RoleEmployeeService { + + @Resource + private RoleEmployeeDao roleEmployeeDao; + @Resource + private RoleDao roleDao; + @Resource + private DepartmentDao departmentDao; + @Resource + private RoleEmployeeManager roleEmployeeManager; + + + /** + * 批量插入 + * + */ + public void batchInsert(List roleEmployeeList) { + roleEmployeeManager.saveBatch(roleEmployeeList); + } + + /** + * 通过角色id,分页获取成员员工列表 + * + */ + public ResponseDTO> queryEmployee(RoleEmployeeQueryForm roleEmployeeQueryForm) { + Page page = SmartPageUtil.convert2PageQuery(roleEmployeeQueryForm); + List employeeList = roleEmployeeDao.selectRoleEmployeeByName(page, roleEmployeeQueryForm) + .stream() + .filter(Objects::nonNull) + .collect(Collectors.toList()); + List departmentIdList = employeeList.stream().filter(e -> e != null && e.getDepartmentId() != null).map(EmployeeVO::getDepartmentId).collect(Collectors.toList()); + if (CollectionUtils.isNotEmpty(departmentIdList)) { + List departmentEntities = departmentDao.selectBatchIds(departmentIdList); + Map departmentIdNameMap = departmentEntities.stream().collect(Collectors.toMap(DepartmentEntity::getDepartmentId, DepartmentEntity::getName)); + employeeList.forEach(e -> { + e.setDepartmentName(departmentIdNameMap.getOrDefault(e.getDepartmentId(), StringConst.EMPTY)); + }); + } + PageResult pageResult = SmartPageUtil.convert2PageResult(page, employeeList, EmployeeVO.class); + return ResponseDTO.ok(pageResult); + } + + public List getAllEmployeeByRoleId(Long roleId) { + return roleEmployeeDao.selectEmployeeByRoleId(roleId); + } + + /** + * 移除员工角色 + * + */ + public ResponseDTO removeRoleEmployee(Long employeeId, Long roleId) { + if (null == employeeId || null == roleId) { + return ResponseDTO.userErrorParam(); + } + roleEmployeeDao.deleteByEmployeeIdRoleId(employeeId, roleId); + return ResponseDTO.ok(); + } + + /** + * 批量删除角色的成员员工 + * + */ + public ResponseDTO batchRemoveRoleEmployee(RoleEmployeeUpdateForm roleEmployeeUpdateForm) { + roleEmployeeDao.batchDeleteEmployeeRole(roleEmployeeUpdateForm.getRoleId(), roleEmployeeUpdateForm.getEmployeeIdList()); + return ResponseDTO.ok(); + } + + /** + * 批量添加角色的成员员工 + * + */ + public ResponseDTO batchAddRoleEmployee(RoleEmployeeUpdateForm roleEmployeeUpdateForm) { + Long roleId = roleEmployeeUpdateForm.getRoleId(); + + // 已选择的员工id列表 + Set selectedEmployeeIdList = roleEmployeeUpdateForm.getEmployeeIdList(); + // 数据库里已有的员工id列表 + Set dbEmployeeIdList = roleEmployeeDao.selectEmployeeIdByRoleIdList(Lists.newArrayList(roleId)); + // 从已选择的员工id列表里 过滤数据库里不存在的 即需要添加的员工 id + Set addEmployeeIdList = selectedEmployeeIdList.stream().filter(id -> !dbEmployeeIdList.contains(id)).collect(Collectors.toSet()); + + // 添加角色员工 + if (CollectionUtils.isNotEmpty(addEmployeeIdList)) { + List roleEmployeeList = addEmployeeIdList.stream() + .map(employeeId -> new RoleEmployeeEntity(roleId, employeeId)) + .collect(Collectors.toList()); + roleEmployeeManager.saveBatch(roleEmployeeList); + } + return ResponseDTO.ok(); + } + + /** + * 通过员工id获取员工角色 + * + */ + public List getRoleInfoListByEmployeeId(Long employeeId) { + List roleIds = roleEmployeeDao.selectRoleIdByEmployeeId(employeeId); + List roleList = roleDao.selectList(null); + List result = SmartBeanUtil.copyList(roleList, RoleSelectedVO.class); + result.stream().forEach(item -> item.setSelected(roleIds.contains(item.getRoleId()))); + return result; + } + + /** + * 根据员工id 查询角色id集合 + * + */ + public List getRoleIdList(Long employeeId) { + return roleEmployeeDao.selectRoleByEmployeeId(employeeId); + } + + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/service/RoleMenuService.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/service/RoleMenuService.java new file mode 100644 index 0000000..3b4ecad --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/service/RoleMenuService.java @@ -0,0 +1,123 @@ +package net.lab1024.sa.admin.module.system.role.service; + +import com.google.common.collect.Lists; +import jakarta.annotation.Resource; +import net.lab1024.sa.admin.module.system.menu.dao.MenuDao; +import net.lab1024.sa.admin.module.system.menu.domain.entity.MenuEntity; +import net.lab1024.sa.admin.module.system.menu.domain.vo.MenuSimpleTreeVO; +import net.lab1024.sa.admin.module.system.menu.domain.vo.MenuVO; +import net.lab1024.sa.admin.module.system.role.dao.RoleDao; +import net.lab1024.sa.admin.module.system.role.dao.RoleMenuDao; +import net.lab1024.sa.admin.module.system.role.domain.entity.RoleEntity; +import net.lab1024.sa.admin.module.system.role.domain.entity.RoleMenuEntity; +import net.lab1024.sa.admin.module.system.role.domain.form.RoleMenuUpdateForm; +import net.lab1024.sa.admin.module.system.role.domain.vo.RoleMenuTreeVO; +import net.lab1024.sa.admin.module.system.role.manager.RoleMenuManager; +import net.lab1024.sa.base.common.code.UserErrorCode; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.math.NumberUtils; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 角色-菜单 + * + * @Author 1024创新实验室: 善逸 + * @Date 2021-10-22 23:17:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class RoleMenuService { + + @Resource + private RoleDao roleDao; + @Resource + private RoleMenuDao roleMenuDao; + @Resource + private RoleMenuManager roleMenuManager; + @Resource + private MenuDao menuDao; + + /** + * 更新角色权限 + * + */ + public ResponseDTO updateRoleMenu(RoleMenuUpdateForm roleMenuUpdateForm) { + //查询角色是否存在 + Long roleId = roleMenuUpdateForm.getRoleId(); + RoleEntity roleEntity = roleDao.selectById(roleId); + if (null == roleEntity) { + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST); + } + List roleMenuEntityList = Lists.newArrayList(); + RoleMenuEntity roleMenuEntity; + for (Long menuId : roleMenuUpdateForm.getMenuIdList()) { + roleMenuEntity = new RoleMenuEntity(); + roleMenuEntity.setRoleId(roleId); + roleMenuEntity.setMenuId(menuId); + roleMenuEntityList.add(roleMenuEntity); + } + roleMenuManager.updateRoleMenu(roleMenuUpdateForm.getRoleId(), roleMenuEntityList); + return ResponseDTO.ok(); + } + + /** + * 根据角色id集合,查询其所有的菜单权限 + * + */ + public List getMenuList(List roleIdList, Boolean administratorFlag) { + //管理员返回所有菜单 + if(administratorFlag){ + List menuEntityList = roleMenuDao.selectMenuListByRoleIdList(Lists.newArrayList(), false); + return SmartBeanUtil.copyList(menuEntityList, MenuVO.class); + } + //非管理员 无角色 返回空菜单 + if (CollectionUtils.isEmpty(roleIdList)) { + return new ArrayList<>(); + } + List menuEntityList = roleMenuDao.selectMenuListByRoleIdList(roleIdList, false); + return SmartBeanUtil.copyList(menuEntityList, MenuVO.class); + } + + + /** + * 获取角色关联菜单权限 + * + */ + public ResponseDTO getRoleSelectedMenu(Long roleId) { + RoleMenuTreeVO res = new RoleMenuTreeVO(); + res.setRoleId(roleId); + //查询角色ID选择的菜单权限 + List selectedMenuId = roleMenuDao.queryMenuIdByRoleId(roleId); + res.setSelectedMenuId(selectedMenuId); + //查询菜单权限 + List menuVOList = menuDao.queryMenuList(Boolean.FALSE, Boolean.FALSE, null); + Map> parentMap = menuVOList.stream().collect(Collectors.groupingBy(MenuVO::getParentId, Collectors.toList())); + List menuTreeList = this.buildMenuTree(parentMap, NumberUtils.LONG_ZERO); + res.setMenuTreeList(menuTreeList); + return ResponseDTO.ok(res); + } + + /** + * 构建菜单树 + * + */ + private List buildMenuTree(Map> parentMap, Long parentId) { + // 获取本级菜单树List + List res = parentMap.getOrDefault(parentId, Lists.newArrayList()).stream() + .map(e -> SmartBeanUtil.copy(e, MenuSimpleTreeVO.class)).collect(Collectors.toList()); + // 循环遍历下级菜单 + res.forEach(e -> { + e.setChildren(this.buildMenuTree(parentMap, e.getMenuId())); + }); + return res; + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/service/RoleService.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/service/RoleService.java new file mode 100644 index 0000000..65b6f2d --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/role/service/RoleService.java @@ -0,0 +1,145 @@ +package net.lab1024.sa.admin.module.system.role.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.annotation.Resource; +import net.lab1024.sa.admin.constant.AdminCacheConst; +import net.lab1024.sa.admin.module.system.department.domain.form.DepartmentQueryForm; +import net.lab1024.sa.admin.module.system.department.domain.vo.DepartmentVO; +import net.lab1024.sa.admin.module.system.role.dao.RoleDao; +import net.lab1024.sa.admin.module.system.role.dao.RoleEmployeeDao; +import net.lab1024.sa.admin.module.system.role.dao.RoleMenuDao; +import net.lab1024.sa.admin.module.system.role.domain.entity.RoleEntity; +import net.lab1024.sa.admin.module.system.role.domain.form.RoleAddForm; +import net.lab1024.sa.admin.module.system.role.domain.form.RoleQueryForm; +import net.lab1024.sa.admin.module.system.role.domain.form.RoleUpdateForm; +import net.lab1024.sa.admin.module.system.role.domain.vo.RoleVO; +import net.lab1024.sa.base.common.code.UserErrorCode; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/** + * 角色 + * + * @Author 1024创新实验室: 胡克 + * @Date 2021-08-16 20:19:22 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class RoleService { + + @Resource + private RoleDao roleDao; + + @Resource + private RoleMenuDao roleMenuDao; + + @Resource + private RoleEmployeeDao roleEmployeeDao; + + /** + * 分页查询 + * + * @param queryForm + * @return + */ + public PageResult queryPage(RoleQueryForm queryForm) { + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List list = roleDao.queryPage(page, queryForm); + PageResult pageResult = SmartPageUtil.convert2PageResult(page, list); + return pageResult; + } + + /** + * 新增添加角色 + */ + public ResponseDTO addRole(RoleAddForm roleAddForm) { + RoleEntity existRoleEntity = roleDao.getByRoleName(roleAddForm.getRoleName()); + if (null != existRoleEntity) { + return ResponseDTO.userErrorParam("角色名称重复"); + } + + existRoleEntity = roleDao.getByRoleCode(roleAddForm.getRoleCode()); + if (null != existRoleEntity) { + return ResponseDTO.userErrorParam("角色编码重复,重复的角色为:" + existRoleEntity.getRoleName()); + } + + RoleEntity roleEntity = SmartBeanUtil.copy(roleAddForm, RoleEntity.class); + roleDao.insert(roleEntity); + return ResponseDTO.ok(); + } + + /** + * 根据角色id 删除 + */ + @Transactional(rollbackFor = Exception.class) + public ResponseDTO deleteRole(Long roleId) { + RoleEntity roleEntity = roleDao.selectById(roleId); + if (null == roleEntity) { + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST); + } + // 当没有员工绑定这个角色时才可以删除 + Integer exists = roleEmployeeDao.existsByRoleId(roleId); + if (exists != null) { + return ResponseDTO.error(UserErrorCode.ALREADY_EXIST, "该角色下存在员工,无法删除"); + } + roleDao.deleteById(roleId); + roleMenuDao.deleteByRoleId(roleId); + roleEmployeeDao.deleteByRoleId(roleId); + return ResponseDTO.ok(); + } + + /** + * 更新角色 + */ + @Transactional(rollbackFor = Exception.class) + public ResponseDTO updateRole(RoleUpdateForm roleUpdateForm) { + if (null == roleDao.selectById(roleUpdateForm.getRoleId())) { + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST); + } + + RoleEntity existRoleEntity = roleDao.getByRoleName(roleUpdateForm.getRoleName()); + if (null != existRoleEntity && !existRoleEntity.getRoleId().equals(roleUpdateForm.getRoleId())) { + return ResponseDTO.userErrorParam("角色名称重复"); + } + + existRoleEntity = roleDao.getByRoleCode(roleUpdateForm.getRoleCode()); + if (null != existRoleEntity && !existRoleEntity.getRoleId().equals(roleUpdateForm.getRoleId())) { + return ResponseDTO.userErrorParam("角色编码重复,重复的角色为:" + existRoleEntity.getRoleName()); + } + + RoleEntity roleEntity = SmartBeanUtil.copy(roleUpdateForm, RoleEntity.class); + roleDao.updateById(roleEntity); + return ResponseDTO.ok(); + } + + /** + * 根据id获取角色数据 + */ + public ResponseDTO getRoleById(Long roleId) { + RoleEntity roleEntity = roleDao.selectById(roleId); + if (null == roleEntity) { + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST); + } + RoleVO role = SmartBeanUtil.copy(roleEntity, RoleVO.class); + return ResponseDTO.ok(role); + } + + /** + * 获取所有角色列表 + */ + @Cacheable(AdminCacheConst.Role.ROLE_LIST_CACHE) + public ResponseDTO> getAllRole() { + List roleEntityList = roleDao.selectList(null); + List roleList = SmartBeanUtil.copyList(roleEntityList, RoleVO.class); + return ResponseDTO.ok(roleList); + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminApiEncryptController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminApiEncryptController.java new file mode 100644 index 0000000..4bd7043 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminApiEncryptController.java @@ -0,0 +1,81 @@ +package net.lab1024.sa.admin.module.system.support; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import net.lab1024.sa.base.common.controller.SupportBaseController; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.domain.ValidateList; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import net.lab1024.sa.base.module.support.apiencrypt.annotation.ApiDecrypt; +import net.lab1024.sa.base.module.support.apiencrypt.annotation.ApiEncrypt; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * + * api 加密 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/10/21 09:21:20 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室,Since 2012 + */ + +@RestController +@Tag(name = SwaggerTagConst.Support.PROTECT) +public class AdminApiEncryptController extends SupportBaseController { + + + @ApiDecrypt + @PostMapping("/apiEncrypt/testRequestEncrypt") + @Operation(summary = "测试 请求加密") + public ResponseDTO testRequestEncrypt(@RequestBody @Valid JweForm form) { + return ResponseDTO.ok(form); + } + + @ApiEncrypt + @PostMapping("/apiEncrypt/testResponseEncrypt") + @Operation(summary = "测试 返回加密") + public ResponseDTO testResponseEncrypt(@RequestBody @Valid JweForm form) { + return ResponseDTO.ok(form); + } + + @ApiDecrypt + @ApiEncrypt + @PostMapping("/apiEncrypt/testDecryptAndEncrypt") + @Operation(summary = "测试 请求参数加密和解密、返回数据加密和解密") + public ResponseDTO testDecryptAndEncrypt(@RequestBody @Valid JweForm form) { + return ResponseDTO.ok(form); + } + + @ApiDecrypt + @ApiEncrypt + @PostMapping("/apiEncrypt/testArray") + @Operation(summary = "测试 数组加密和解密") + public ResponseDTO> testArray(@RequestBody @Valid ValidateList list) { + return ResponseDTO.ok(list); + } + + + @Data + public static class JweForm { + + @NotBlank(message = "姓名 不能为空") + String name; + + @NotNull + @Min(value = 1) + Integer age; + + } + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminCacheController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminCacheController.java new file mode 100644 index 0000000..adaacdb --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminCacheController.java @@ -0,0 +1,55 @@ +package net.lab1024.sa.admin.module.system.support; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.controller.SupportBaseController; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import net.lab1024.sa.base.module.support.cache.CacheService; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * 缓存 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2021/10/11 20:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@RestController +@Tag(name = SwaggerTagConst.Support.CACHE) +public class AdminCacheController extends SupportBaseController { + + @Resource + private CacheService cacheService; + + @Operation(summary = "获取所有缓存 @author 罗伊") + @GetMapping("/cache/names") + @SaCheckPermission("support:cache:keys") + public ResponseDTO> cacheNames() { + return ResponseDTO.ok(cacheService.cacheNames()); + } + + @Operation(summary = "移除某个缓存 @author 罗伊") + @GetMapping("/cache/remove/{cacheName}") + @SaCheckPermission("support:cache:delete") + public ResponseDTO removeCache(@PathVariable String cacheName) { + cacheService.removeCache(cacheName); + return ResponseDTO.ok(); + } + + @Operation(summary = "获取某个缓存的所有key @author 罗伊") + @GetMapping("/cache/keys/{cacheName}") + @SaCheckPermission("support:cache:keys") + public ResponseDTO> cacheKeys(@PathVariable String cacheName) { + return ResponseDTO.ok(cacheService.cacheKey(cacheName)); + } + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminChangeLogController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminChangeLogController.java new file mode 100644 index 0000000..3f9faed --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminChangeLogController.java @@ -0,0 +1,59 @@ +package net.lab1024.sa.admin.module.system.support; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import net.lab1024.sa.base.common.controller.SupportBaseController; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.domain.ValidateList; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import net.lab1024.sa.base.module.support.changelog.domain.form.ChangeLogAddForm; +import net.lab1024.sa.base.module.support.changelog.domain.form.ChangeLogUpdateForm; +import net.lab1024.sa.base.module.support.changelog.service.ChangeLogService; +import org.springframework.web.bind.annotation.*; + +/** + * 系统更新日志 Controller + * + * @Author 卓大 + * @Date 2022-09-26 14:53:50 + * @Copyright 1024创新实验室 + */ + +@RestController +@Tag(name = SwaggerTagConst.Support.CHANGE_LOG) +public class AdminChangeLogController extends SupportBaseController { + + @Resource + private ChangeLogService changeLogService; + + @Operation(summary = "添加 @author 卓大") + @PostMapping("/changeLog/add") + @SaCheckPermission("support:changeLog:add") + public ResponseDTO add(@RequestBody @Valid ChangeLogAddForm addForm) { + return changeLogService.add(addForm); + } + + @Operation(summary = "更新 @author 卓大") + @PostMapping("/changeLog/update") + @SaCheckPermission("support:changeLog:update") + public ResponseDTO update(@RequestBody @Valid ChangeLogUpdateForm updateForm) { + return changeLogService.update(updateForm); + } + + @Operation(summary = "批量删除 @author 卓大") + @PostMapping("/changeLog/batchDelete") + @SaCheckPermission("support:changeLog:batchDelete") + public ResponseDTO batchDelete(@RequestBody ValidateList idList) { + return changeLogService.batchDelete(idList); + } + + @Operation(summary = "单个删除 @author 卓大") + @GetMapping("/changeLog/delete/{changeLogId}") + @SaCheckPermission("support:changeLog:delete") + public ResponseDTO batchDelete(@PathVariable Long changeLogId) { + return changeLogService.delete(changeLogId); + } +} \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminConfigController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminConfigController.java new file mode 100644 index 0000000..fa3d5c1 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminConfigController.java @@ -0,0 +1,58 @@ +package net.lab1024.sa.admin.module.system.support; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import net.lab1024.sa.base.common.controller.SupportBaseController; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import net.lab1024.sa.base.module.support.config.ConfigService; +import net.lab1024.sa.base.module.support.config.domain.ConfigAddForm; +import net.lab1024.sa.base.module.support.config.domain.ConfigQueryForm; +import net.lab1024.sa.base.module.support.config.domain.ConfigUpdateForm; +import net.lab1024.sa.base.module.support.config.domain.ConfigVO; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * 配置 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-14 20:46:27 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Tag(name = SwaggerTagConst.Support.CONFIG) +@RestController +public class AdminConfigController extends SupportBaseController { + + @Resource + private ConfigService configService; + + @Operation(summary = "分页查询系统配置 @author 卓大") + @PostMapping("/config/query") + @SaCheckPermission("support:config:query") + public ResponseDTO> queryConfigPage(@RequestBody @Valid ConfigQueryForm queryForm) { + return configService.queryConfigPage(queryForm); + } + + @Operation(summary = "添加配置参数 @author 卓大") + @PostMapping("/config/add") + @SaCheckPermission("support:config:add") + public ResponseDTO addConfig(@RequestBody @Valid ConfigAddForm configAddForm) { + return configService.add(configAddForm); + } + + @Operation(summary = "修改配置参数 @author 卓大") + @PostMapping("/config/update") + @SaCheckPermission("support:config:update") + public ResponseDTO updateConfig(@RequestBody @Valid ConfigUpdateForm updateForm) { + return configService.updateConfig(updateForm); + } + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminDataMaskingDemoController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminDataMaskingDemoController.java new file mode 100644 index 0000000..7e6156b --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminDataMaskingDemoController.java @@ -0,0 +1,88 @@ +package net.lab1024.sa.admin.module.system.support; + +import cn.hutool.core.util.RandomUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.Data; +import net.lab1024.sa.base.common.controller.SupportBaseController; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import net.lab1024.sa.base.module.support.datamasking.DataMasking; +import net.lab1024.sa.base.module.support.datamasking.DataMaskingTypeEnum; +import org.apache.commons.lang3.RandomStringUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.ArrayList; +import java.util.List; + +/** + * 数据脱敏demo + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2024/08/01 22:07:27 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室,Since 2012 + */ + +@RestController +@Tag(name = SwaggerTagConst.Support.DATA_MASKING) +public class AdminDataMaskingDemoController extends SupportBaseController { + + @Operation(summary = "数据脱敏demo @author 1024创新实验室-主任-卓大") + @GetMapping("/dataMasking/demo/query") + public ResponseDTO> query() { + + List list = new ArrayList<>(); + for (int i = 0; i < RandomUtil.randomInt(10,16); i++) { + DataVO data = new DataVO(); + data.setUserId(RandomUtil.randomLong(1328479238, 83274298347982L)); + data.setPhone("1" + RandomUtil.randomNumbers(10)); + data.setIdCard("410" + RandomUtil.randomNumbers(3) + RandomUtil.randomInt(1980, 2010) + RandomUtil.randomInt(10, 12) + RandomUtil.randomInt(10, 30) + RandomUtil.randomNumbers(4)); + data.setAddress(RandomUtil.randomBoolean() ? "河南省洛阳市洛龙区一零二四大街1024号" : "河南省郑州市高新区六边形大街六边形大楼"); + data.setPassword(RandomUtil.randomString(10)); + data.setEmail(RandomUtil.randomString(RandomUtil.randomInt(6, 10)) + "@" + RandomUtil.randomString(2) + ".com"); + data.setCarLicense("豫" + RandomStringUtils.randomAlphabetic(1).toUpperCase()+" " + RandomStringUtils.randomAlphanumeric(5).toUpperCase()); + data.setBankCard("6225" + RandomStringUtils.randomNumeric(14)); + data.setOther(RandomStringUtils.randomAlphanumeric(1, 12)); + list.add(data); + } + + return ResponseDTO.ok(list); + } + + + @Data + public static class DataVO { + + @DataMasking(DataMaskingTypeEnum.USER_ID) + private Long userId; + + @DataMasking(DataMaskingTypeEnum.PHONE) + private String phone; + + @DataMasking(DataMaskingTypeEnum.ID_CARD) + private String idCard; + + @DataMasking(DataMaskingTypeEnum.ADDRESS) + private String address; + + @DataMasking(DataMaskingTypeEnum.PASSWORD) + private String password; + + @DataMasking(DataMaskingTypeEnum.EMAIL) + private String email; + + @DataMasking(DataMaskingTypeEnum.CAR_LICENSE) + private String carLicense; + + @DataMasking(DataMaskingTypeEnum.BANK_CARD) + private String bankCard; + + @DataMasking + private String other; + + } + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminDictController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminDictController.java new file mode 100644 index 0000000..6555c07 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminDictController.java @@ -0,0 +1,145 @@ +package net.lab1024.sa.admin.module.system.support; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import net.lab1024.sa.base.common.controller.SupportBaseController; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.domain.ValidateList; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import net.lab1024.sa.base.module.support.dict.domain.form.*; +import net.lab1024.sa.base.module.support.dict.domain.vo.DictDataVO; +import net.lab1024.sa.base.module.support.dict.domain.vo.DictVO; +import net.lab1024.sa.base.module.support.dict.service.DictService; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 数据字典 Controller + * + * @Author 1024创新实验室-主任-卓大 + * @Date 2025-03-25 22:25:04 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Tag(name = SwaggerTagConst.Support.DICT) +@RestController +public class AdminDictController extends SupportBaseController { + + @Resource + private DictService dictService; + + // ------------------- 获取全部数据 ------------------- + + @Operation(summary = "获取全部数据(供前端缓存使用) @author 1024创新实验室-主任-卓大") + @GetMapping("/dict/getAllDictData") + public ResponseDTO> getAll() { + return ResponseDTO.ok(dictService.getAll()); + } + + @Operation(summary = "获取所有字典code @author 1024创新实验室-主任-卓大") + @GetMapping("/dict/getAllDict") + public ResponseDTO> getAllDict() { + return ResponseDTO.ok(dictService.getAllDict()); + } + + // ------------------- 字典 ------------------- + + @Operation(summary = "分页查询 @author 1024创新实验室-主任-卓大") + @PostMapping("/dict/queryPage") + @SaCheckPermission("support:dict:query") + public ResponseDTO> queryPage(@RequestBody @Valid DictQueryForm queryForm) { + return ResponseDTO.ok(dictService.queryPage(queryForm)); + } + + @Operation(summary = "添加 @author 1024创新实验室-主任-卓大") + @PostMapping("/dict/add") + @SaCheckPermission("support:dict:add") + public ResponseDTO add(@RequestBody @Valid DictAddForm addForm) { + return dictService.add(addForm); + } + + @Operation(summary = "更新 @author 1024创新实验室-主任-卓大") + @PostMapping("/dict/update") + @SaCheckPermission("support:dict:update") + public ResponseDTO update(@RequestBody @Valid DictUpdateForm updateForm) { + return dictService.update(updateForm); + } + + @Operation(summary = "启用/禁用 @author 1024创新实验室-主任-卓大") + @GetMapping("/dict/updateDisabled/{dictId}") + @SaCheckPermission("support:dict:updateDisabled") + public ResponseDTO updateDisabled(@PathVariable Long dictId) { + return dictService.updateDisabled(dictId); + } + + @Operation(summary = "批量删除 @author 1024创新实验室-主任-卓大") + @PostMapping("/dict/batchDelete") + @SaCheckPermission("support:dict:delete") + public ResponseDTO batchDelete(@RequestBody ValidateList idList) { + return dictService.batchDelete(idList); + } + + @Operation(summary = "单个删除 @author 1024创新实验室-主任-卓大") + @GetMapping("/dict/delete/{dictId}") + @SaCheckPermission("support:dict:delete") + public ResponseDTO delete(@PathVariable Long dictId) { + return dictService.delete(dictId); + } + + // ------------------- 字典数据 ------------------- + + @Operation(summary = "字典数据 分页查询 @author 1024创新实验室-主任-卓大") + @GetMapping("/dict/dictData/queryDictData/{dictId}") + @SaCheckPermission("support:dictData:query") + public ResponseDTO> queryDictData(@PathVariable Long dictId) { + return ResponseDTO.ok(dictService.queryDictData(dictId)); + } + + @GetMapping("/dict/value/list/{dictCode}") + @SaCheckPermission("support:dictData:query") + public ResponseDTO> queryDictData(@PathVariable String dictCode) { + return ResponseDTO.ok(dictService.queryDictDataByCode(dictCode)); + } + + @Operation(summary = "字典数据 启用/禁用 @author 1024创新实验室-主任-卓大") + @GetMapping("/dict/dictData/updateDisabled/{dictDataId}") + @SaCheckPermission("support:dictData:updateDisabled") + public ResponseDTO updateDictDataDisabled(@PathVariable Long dictDataId) { + return dictService.updateDictDataDisabled(dictDataId); + } + + @Operation(summary = "字典数据 添加 @author 1024创新实验室-主任-卓大") + @PostMapping("/dict/dictData/add") + @SaCheckPermission("support:dictData:add") + public ResponseDTO addDictData(@RequestBody @Valid DictDataAddForm addForm) { + return dictService.addDictData(addForm); + } + + @Operation(summary = "字典数据 更新 @author 1024创新实验室-主任-卓大") + @PostMapping("/dict/dictData/update") + @SaCheckPermission("support:dictData:update") + public ResponseDTO updateDictData(@RequestBody @Valid DictDataUpdateForm updateForm) { + return dictService.updateDictData(updateForm); + } + + @Operation(summary = "字典数据 批量删除 @author 1024创新实验室-主任-卓大") + @PostMapping("/dict/dictData/batchDelete") + @SaCheckPermission("support:dictData:delete") + public ResponseDTO batchDeleteDictData(@RequestBody ValidateList idList) { + return dictService.batchDeleteDictData(idList); + } + + @Operation(summary = "字典数据 单个删除 @author 1024创新实验室-主任-卓大") + @GetMapping("/dict/dictData/delete/{dictDataId}") + @SaCheckPermission("support:dictData:delete") + public ResponseDTO deleteDictData(@PathVariable Long dictDataId) { + return dictService.deleteDictData(dictDataId); + } + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminFileController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminFileController.java new file mode 100644 index 0000000..746b1ea --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminFileController.java @@ -0,0 +1,42 @@ +package net.lab1024.sa.admin.module.system.support; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import net.lab1024.sa.base.common.controller.SupportBaseController; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import net.lab1024.sa.base.module.support.file.domain.form.FileQueryForm; +import net.lab1024.sa.base.module.support.file.domain.vo.FileVO; +import net.lab1024.sa.base.module.support.file.service.FileService; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * 文件服务 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2019年10月11日 15:34:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@RestController +@Tag(name = SwaggerTagConst.Support.FILE) +public class AdminFileController extends SupportBaseController { + + @Resource + private FileService fileService; + + @Operation(summary = "分页查询 @author 1024创新实验室-主任-卓大") + @PostMapping("/file/queryPage") + @SaCheckPermission("support:file:query") + public ResponseDTO> queryPage(@RequestBody @Valid FileQueryForm queryForm) { + return ResponseDTO.ok(fileService.queryPage(queryForm)); + } + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminHeartBeatController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminHeartBeatController.java new file mode 100644 index 0000000..9578eda --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminHeartBeatController.java @@ -0,0 +1,40 @@ +package net.lab1024.sa.admin.module.system.support; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import net.lab1024.sa.base.common.controller.SupportBaseController; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import net.lab1024.sa.base.module.support.heartbeat.HeartBeatService; +import net.lab1024.sa.base.module.support.heartbeat.domain.HeartBeatRecordQueryForm; +import net.lab1024.sa.base.module.support.heartbeat.domain.HeartBeatRecordVO; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * 心跳记录 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-01-09 20:57:24 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Tag(name = SwaggerTagConst.Support.HEART_BEAT) +@RestController +public class AdminHeartBeatController extends SupportBaseController { + + @Resource + private HeartBeatService heartBeatService; + + @PostMapping("/heartBeat/query") + @Operation(summary = "查询心跳记录 @author 卓大") + public ResponseDTO> query(@RequestBody @Valid HeartBeatRecordQueryForm pageParam) { + return heartBeatService.pageQuery(pageParam); + } + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminHelpDocController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminHelpDocController.java new file mode 100644 index 0000000..75f6f1c --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminHelpDocController.java @@ -0,0 +1,104 @@ +package net.lab1024.sa.admin.module.system.support; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import net.lab1024.sa.base.common.controller.SupportBaseController; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import net.lab1024.sa.base.module.support.helpdoc.domain.form.*; +import net.lab1024.sa.base.module.support.helpdoc.domain.vo.HelpDocDetailVO; +import net.lab1024.sa.base.module.support.helpdoc.domain.vo.HelpDocVO; +import net.lab1024.sa.base.module.support.helpdoc.service.HelpDocCatalogService; +import net.lab1024.sa.base.module.support.helpdoc.service.HelpDocService; +import net.lab1024.sa.base.module.support.repeatsubmit.annoation.RepeatSubmit; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 帮助文档 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Tag(name = SwaggerTagConst.Support.HELP_DOC) +@RestController +public class AdminHelpDocController extends SupportBaseController { + + @Resource + private HelpDocService helpDocService; + + @Resource + private HelpDocCatalogService helpDocCatalogService; + + // --------------------- 帮助文档 【目录管理】 ------------------------- + + + @Operation(summary = "帮助文档目录-添加 @author 卓大") + @PostMapping("/helpDoc/helpDocCatalog/add") + public ResponseDTO addHelpDocCatalog(@RequestBody @Valid HelpDocCatalogAddForm helpDocCatalogAddForm) { + return helpDocCatalogService.add(helpDocCatalogAddForm); + } + + @Operation(summary = "帮助文档目录-更新 @author 卓大") + @PostMapping("/helpDoc/helpDocCatalog/update") + public ResponseDTO updateHelpDocCatalog(@RequestBody @Valid HelpDocCatalogUpdateForm helpDocCatalogUpdateForm) { + return helpDocCatalogService.update(helpDocCatalogUpdateForm); + } + + @Operation(summary = "帮助文档目录-删除 @author 卓大") + @GetMapping("/helpDoc/helpDocCatalog/delete/{helpDocCatalogId}") + public ResponseDTO deleteHelpDocCatalog(@PathVariable Long helpDocCatalogId) { + return helpDocCatalogService.delete(helpDocCatalogId); + } + + // --------------------- 帮助文档 【管理:增、删、查、改】------------------------- + + @Operation(summary = "【管理】帮助文档-分页查询 @author 卓大") + @PostMapping("/helpDoc/query") + @SaCheckPermission("support:helpDoc:query") + public ResponseDTO> query(@RequestBody @Valid HelpDocQueryForm queryForm) { + return ResponseDTO.ok(helpDocService.query(queryForm)); + } + + @Operation(summary = "【管理】帮助文档-获取详情 @author 卓大") + @GetMapping("/helpDoc/getDetail/{helpDocId}") + @SaCheckPermission("support:helpDoc:add") + public ResponseDTO getDetail(@PathVariable Long helpDocId) { + return ResponseDTO.ok(helpDocService.getDetail(helpDocId)); + } + + @Operation(summary = "【管理】帮助文档-添加 @author 卓大") + @PostMapping("/helpDoc/add") + @RepeatSubmit + public ResponseDTO add(@RequestBody @Valid HelpDocAddForm addForm) { + return helpDocService.add(addForm); + } + + @Operation(summary = "【管理】帮助文档-更新 @author 卓大") + @PostMapping("/helpDoc/update") + @RepeatSubmit + public ResponseDTO update(@RequestBody @Valid HelpDocUpdateForm updateForm) { + return helpDocService.update(updateForm); + } + + @Operation(summary = "【管理】帮助文档-删除 @author 卓大") + @GetMapping("/helpDoc/delete/{helpDocId}") + public ResponseDTO delete(@PathVariable Long helpDocId) { + return helpDocService.delete(helpDocId); + } + + @Operation(summary = "【管理】帮助文档-根据关联id查询 @author 卓大") + @GetMapping("/helpDoc/queryHelpDocByRelationId/{relationId}") + public ResponseDTO> queryHelpDocByRelationId(@PathVariable Long relationId) { + return ResponseDTO.ok(helpDocService.queryHelpDocByRelationId(relationId)); + } + +} \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminLoginLogController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminLoginLogController.java new file mode 100644 index 0000000..54479cc --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminLoginLogController.java @@ -0,0 +1,53 @@ +package net.lab1024.sa.admin.module.system.support; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.controller.SupportBaseController; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.RequestUser; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartRequestUtil; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import net.lab1024.sa.base.module.support.loginlog.LoginLogService; +import net.lab1024.sa.base.module.support.loginlog.domain.LoginLogQueryForm; +import net.lab1024.sa.base.module.support.loginlog.domain.LoginLogVO; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * 登录日志 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022/07/22 19:46:23 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@RestController +@Tag(name = SwaggerTagConst.Support.LOGIN_LOG) +public class AdminLoginLogController extends SupportBaseController { + + @Resource + private LoginLogService loginLogService; + + @Operation(summary = "分页查询 @author 卓大") + @PostMapping("/loginLog/page/query") + @SaCheckPermission("support:loginLog:query") + public ResponseDTO> queryByPage(@RequestBody LoginLogQueryForm queryForm) { + return loginLogService.queryByPage(queryForm); + } + + @Operation(summary = "分页查询当前登录人信息 @author 善逸") + @PostMapping("/loginLog/page/query/login") + public ResponseDTO> queryByPageLogin(@RequestBody LoginLogQueryForm queryForm) { + RequestUser requestUser = SmartRequestUtil.getRequestUser(); + queryForm.setUserId(requestUser.getUserId()); + queryForm.setUserType(requestUser.getUserType().getValue()); + return loginLogService.queryByPage(queryForm); + } + + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminOperateLogController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminOperateLogController.java new file mode 100644 index 0000000..43ada5e --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminOperateLogController.java @@ -0,0 +1,57 @@ +package net.lab1024.sa.admin.module.system.support; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.controller.SupportBaseController; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.RequestUser; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartRequestUtil; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import net.lab1024.sa.base.module.support.operatelog.OperateLogService; +import net.lab1024.sa.base.module.support.operatelog.domain.OperateLogQueryForm; +import net.lab1024.sa.base.module.support.operatelog.domain.OperateLogVO; +import org.springframework.web.bind.annotation.*; + +/** + * 操作日志 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2021-12-08 20:48:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@RestController +@Tag(name = SwaggerTagConst.Support.OPERATE_LOG) +public class AdminOperateLogController extends SupportBaseController { + + @Resource + private OperateLogService operateLogService; + + @Operation(summary = "分页查询 @author 罗伊") + @PostMapping("/operateLog/page/query") + @SaCheckPermission("support:operateLog:query") + public ResponseDTO> queryByPage(@RequestBody OperateLogQueryForm queryForm) { + return operateLogService.queryByPage(queryForm); + } + + @Operation(summary = "详情 @author 罗伊") + @GetMapping("/operateLog/detail/{operateLogId}") + @SaCheckPermission("support:operateLog:detail") + public ResponseDTO detail(@PathVariable Long operateLogId) { + return operateLogService.detail(operateLogId); + } + + @Operation(summary = "分页查询当前登录人信息 @author 善逸") + @PostMapping("/operateLog/page/query/login") + public ResponseDTO> queryByPageLogin(@RequestBody OperateLogQueryForm queryForm) { + RequestUser requestUser = SmartRequestUtil.getRequestUser(); + queryForm.setOperateUserId(requestUser.getUserId()); + queryForm.setOperateUserType(requestUser.getUserType().getValue()); + return operateLogService.queryByPage(queryForm); + } + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminProtectController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminProtectController.java new file mode 100644 index 0000000..69f7e3f --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminProtectController.java @@ -0,0 +1,73 @@ +package net.lab1024.sa.admin.module.system.support; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import net.lab1024.sa.base.common.controller.SupportBaseController; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.domain.ValidateList; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import net.lab1024.sa.base.module.support.config.ConfigKeyEnum; +import net.lab1024.sa.base.module.support.config.ConfigService; +import net.lab1024.sa.base.module.support.securityprotect.domain.Level3ProtectConfigForm; +import net.lab1024.sa.base.module.support.securityprotect.domain.LoginFailQueryForm; +import net.lab1024.sa.base.module.support.securityprotect.domain.LoginFailVO; +import net.lab1024.sa.base.module.support.securityprotect.service.Level3ProtectConfigService; +import net.lab1024.sa.base.module.support.securityprotect.service.SecurityLoginService; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * + * 网络安全 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/10/17 19:07:27 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室,Since 2012 + */ + +@RestController +@Tag(name = SwaggerTagConst.Support.PROTECT) +public class AdminProtectController extends SupportBaseController { + + @Resource + private SecurityLoginService securityLoginService; + + @Resource + private Level3ProtectConfigService level3ProtectConfigService; + + @Resource + private ConfigService configService; + + + @Operation(summary = "分页查询 @author 1024创新实验室-主任-卓大") + @PostMapping("/protect/loginFail/queryPage") + public ResponseDTO> queryPage(@RequestBody @Valid LoginFailQueryForm queryForm) { + return ResponseDTO.ok(securityLoginService.queryPage(queryForm)); + } + + + @Operation(summary = "批量删除 @author 1024创新实验室-主任-卓大") + @PostMapping("/protect/loginFail/batchDelete") + public ResponseDTO batchDelete(@RequestBody ValidateList idList) { + return securityLoginService.batchDelete(idList); + } + + @Operation(summary = "更新三级等保配置 @author 1024创新实验室-主任-卓大") + @PostMapping("/protect/level3protect/updateConfig") + public ResponseDTO updateConfig(@RequestBody @Valid Level3ProtectConfigForm configForm) { + return level3ProtectConfigService.updateLevel3Config(configForm); + } + + @Operation(summary = "查询 三级等保配置 @author 1024创新实验室-主任-卓大") + @GetMapping("/protect/level3protect/getConfig") + public ResponseDTO getConfig() { + return ResponseDTO.ok(configService.getConfigValue(ConfigKeyEnum.LEVEL3_PROTECT_CONFIG)); + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminReloadController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminReloadController.java new file mode 100644 index 0000000..afe7637 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminReloadController.java @@ -0,0 +1,54 @@ +package net.lab1024.sa.admin.module.system.support; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import net.lab1024.sa.base.common.controller.SupportBaseController; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import net.lab1024.sa.base.module.support.reload.ReloadService; +import net.lab1024.sa.base.module.support.reload.domain.ReloadForm; +import net.lab1024.sa.base.module.support.reload.domain.ReloadItemVO; +import net.lab1024.sa.base.module.support.reload.domain.ReloadResultVO; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * reload (内存热加载、钩子等) + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2015-03-02 19:11:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@RestController +@Tag(name = SwaggerTagConst.Support.RELOAD) +public class AdminReloadController extends SupportBaseController { + + @Resource + private ReloadService reloadService; + + @Operation(summary = "查询reload列表 @author 开云") + @GetMapping("/reload/query") + public ResponseDTO> query() { + return reloadService.query(); + } + + @Operation(summary = "获取reload result @author 开云") + @GetMapping("/reload/result/{tag}") + @SaCheckPermission("support:reload:result") + public ResponseDTO> queryReloadResult(@PathVariable("tag") String tag) { + return reloadService.queryReloadItemResult(tag); + } + + @Operation(summary = "通过tag更新标识 @author 开云") + @PostMapping("/reload/update") + @SaCheckPermission("support:reload:update") + public ResponseDTO updateByTag(@RequestBody @Valid ReloadForm reloadForm) { + return reloadService.updateByTag(reloadForm); + } +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminSerialNumberController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminSerialNumberController.java new file mode 100644 index 0000000..a405d33 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminSerialNumberController.java @@ -0,0 +1,74 @@ +package net.lab1024.sa.admin.module.system.support; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import net.lab1024.sa.base.common.controller.SupportBaseController; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartEnumUtil; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import net.lab1024.sa.base.module.support.serialnumber.constant.SerialNumberIdEnum; +import net.lab1024.sa.base.module.support.serialnumber.dao.SerialNumberDao; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberEntity; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberGenerateForm; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberRecordEntity; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberRecordQueryForm; +import net.lab1024.sa.base.module.support.serialnumber.service.SerialNumberRecordService; +import net.lab1024.sa.base.module.support.serialnumber.service.SerialNumberService; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * 单据序列号 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-25 21:46:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Tag(name = SwaggerTagConst.Support.SERIAL_NUMBER) +@RestController +public class AdminSerialNumberController extends SupportBaseController { + + @Resource + private SerialNumberDao serialNumberDao; + + @Resource + private SerialNumberService serialNumberService; + + @Resource + private SerialNumberRecordService serialNumberRecordService; + + @Operation(summary = "生成单号 @author 卓大") + @PostMapping("/serialNumber/generate") + @SaCheckPermission("support:serialNumber:generate") + public ResponseDTO> generate(@RequestBody @Valid SerialNumberGenerateForm generateForm) { + SerialNumberIdEnum serialNumberIdEnum = SmartEnumUtil.getEnumByValue(generateForm.getSerialNumberId(), SerialNumberIdEnum.class); + if (null == serialNumberIdEnum) { + return ResponseDTO.userErrorParam("SerialNumberId,不存在" + generateForm.getSerialNumberId()); + } + return ResponseDTO.ok(serialNumberService.generate(serialNumberIdEnum, generateForm.getCount())); + } + + @Operation(summary = "获取所有单号定义 @author 卓大") + @GetMapping("/serialNumber/all") + public ResponseDTO> getAll() { + return ResponseDTO.ok(serialNumberDao.selectList(null)); + } + + @Operation(summary = "获取生成记录 @author 卓大") + @PostMapping("/serialNumber/queryRecord") + @SaCheckPermission("support:serialNumber:record") + public ResponseDTO> queryRecord(@RequestBody @Valid SerialNumberRecordQueryForm queryForm) { + return ResponseDTO.ok(serialNumberRecordService.query(queryForm)); + } + +} diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminSmartJobController.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminSmartJobController.java new file mode 100644 index 0000000..02e5cea --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/system/support/AdminSmartJobController.java @@ -0,0 +1,94 @@ +package net.lab1024.sa.admin.module.system.support; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import net.lab1024.sa.base.common.controller.SupportBaseController; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.RequestUser; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartRequestUtil; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import net.lab1024.sa.base.module.support.job.api.SmartJobService; +import net.lab1024.sa.base.module.support.job.api.domain.*; +import net.lab1024.sa.base.module.support.job.config.SmartJobAutoConfiguration; +import net.lab1024.sa.base.module.support.repeatsubmit.annoation.RepeatSubmit; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.web.bind.annotation.*; + +/** + * 定时任务 管理接口 + * + * @author huke + * @date 2024/6/17 20:41 + */ +@Tag(name = SwaggerTagConst.Support.JOB) +@RestController +@ConditionalOnBean(SmartJobAutoConfiguration.class) +public class AdminSmartJobController extends SupportBaseController { + + @Autowired + private SmartJobService jobService; + + @Operation(summary = "定时任务-立即执行 @huke") + @PostMapping("/job/execute") + @RepeatSubmit + public ResponseDTO execute(@RequestBody @Valid SmartJobExecuteForm executeForm) { + RequestUser requestUser = SmartRequestUtil.getRequestUser(); + executeForm.setUpdateName(requestUser.getUserName()); + return jobService.execute(executeForm); + } + + @Operation(summary = "定时任务-查询详情 @huke") + @GetMapping("/job/{jobId}") + public ResponseDTO queryJobInfo(@PathVariable Integer jobId) { + return jobService.queryJobInfo(jobId); + } + + @Operation(summary = "定时任务-分页查询 @huke") + @PostMapping("/job/query") + public ResponseDTO> queryJob(@RequestBody @Valid SmartJobQueryForm queryForm) { + return jobService.queryJob(queryForm); + } + + @Operation(summary = "定时任务-添加任务 @huke") + @PostMapping("/job/add") + @RepeatSubmit + public ResponseDTO addJob(@RequestBody @Valid SmartJobAddForm addForm) { + RequestUser requestUser = SmartRequestUtil.getRequestUser(); + addForm.setUpdateName(requestUser.getUserName()); + return jobService.addJob(addForm); + } + + @Operation(summary = "定时任务-更新-任务信息 @huke") + @PostMapping("/job/update") + @RepeatSubmit + public ResponseDTO updateJob(@RequestBody @Valid SmartJobUpdateForm updateForm) { + RequestUser requestUser = SmartRequestUtil.getRequestUser(); + updateForm.setUpdateName(requestUser.getUserName()); + return jobService.updateJob(updateForm); + } + + @Operation(summary = "定时任务-更新-开启状态 @huke") + @PostMapping("/job/update/enabled") + @RepeatSubmit + public ResponseDTO updateJobEnabled(@RequestBody @Valid SmartJobEnabledUpdateForm updateForm) { + RequestUser requestUser = SmartRequestUtil.getRequestUser(); + updateForm.setUpdateName(requestUser.getUserName()); + return jobService.updateJobEnabled(updateForm); + } + + @Operation(summary = "定时任务-删除 @zhuoda") + @GetMapping("/job/delete") + @RepeatSubmit + public ResponseDTO deleteJob(@RequestParam Integer jobId) { + return jobService.deleteJob(jobId, SmartRequestUtil.getRequestUser()); + } + + @Operation(summary = "定时任务-执行记录-分页查询 @huke") + @PostMapping("/job/log/query") + public ResponseDTO> queryJobLog(@RequestBody @Valid SmartJobLogQueryForm queryForm) { + return jobService.queryJobLog(queryForm); + } +} \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/util/AdminRequestUtil.java b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/util/AdminRequestUtil.java new file mode 100644 index 0000000..c7e466b --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/util/AdminRequestUtil.java @@ -0,0 +1,29 @@ +package net.lab1024.sa.admin.util; + +import net.lab1024.sa.admin.module.system.login.domain.RequestEmployee; +import net.lab1024.sa.base.common.domain.RequestUser; +import net.lab1024.sa.base.common.util.SmartRequestUtil; + +/** + * admin 端的请求工具类 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/7/28 19:39:21 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室,Since 2012 + */ +public final class AdminRequestUtil { + + + public static RequestEmployee getRequestUser() { + return (RequestEmployee) SmartRequestUtil.getRequestUser(); + } + + public static Long getRequestUserId() { + RequestUser requestUser = getRequestUser(); + return null == requestUser ? null : requestUser.getUserId(); + } + + +} diff --git a/smart-flow-api/sa-admin/src/main/resources/dev/application.yaml b/smart-flow-api/sa-admin/src/main/resources/dev/application.yaml new file mode 100644 index 0000000..3bf5b99 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/resources/dev/application.yaml @@ -0,0 +1,33 @@ +############################################################################################################# +# # +# 为了减少重复配置,本配置文件为此sa-admin的独有配置,更多配置请查看 sa-base 项目中的 sa-base.yaml 通用配置文件。 # +# 其中此文件中配置可以覆盖 sa-base.yaml 中的通用配置,具体实现类请看类:YamlProcessor.java # +# # +############################################################################################################# + +# 项目配置:名称、日志目录 +project: + name: sa-admin + log-directory: D:/logs/smart_admin_v3/${project.name}/${spring.profiles.active} + +# 项目端口和url根路径 +server: + port: 1024 + servlet: + context-path: / + +management: + health: + mail: + enabled: false + +# 环境 +spring: + profiles: + active: '@profiles.active@' + +# 员工模块配置 +smart: + employee: + # 默认密码,若未配置则使用随机密码 + defaultPassword: 1 \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/resources/dev/log4j2-spring.xml b/smart-flow-api/sa-admin/src/main/resources/dev/log4j2-spring.xml new file mode 100644 index 0000000..14e3eea --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/resources/dev/log4j2-spring.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/resources/dev/spy.properties b/smart-flow-api/sa-admin/src/main/resources/dev/spy.properties new file mode 100644 index 0000000..667e1ea --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/resources/dev/spy.properties @@ -0,0 +1,18 @@ +#相关的包 +modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory +# 日志格式 +logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger +#日志输出到控制台 +appender=com.p6spy.engine.spy.appender.StdoutLogger +# 设置 p6spy driver 代理 +deregisterdrivers=true +# 取消JDBC URL前缀 +useprefix=true +# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset. +excludecategories=info,debug,result,commit,resultset +# 日期格式 +dateformat=yyyy-MM-dd HH:mm:ss +# 开启慢sql +outagedetection=true +# 慢SQL记录标准(单位秒) +outagedetectioninterval=2 \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/resources/mapper/business/category/CategoryMapper.xml b/smart-flow-api/sa-admin/src/main/resources/mapper/business/category/CategoryMapper.xml new file mode 100644 index 0000000..908509c --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/resources/mapper/business/category/CategoryMapper.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/resources/mapper/business/goods/GoodsMapper.xml b/smart-flow-api/sa-admin/src/main/resources/mapper/business/goods/GoodsMapper.xml new file mode 100644 index 0000000..05316d8 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/resources/mapper/business/goods/GoodsMapper.xml @@ -0,0 +1,41 @@ + + + + + update t_goods + set deleted_flag = #{deletedFlag} + WHERE goods_id IN + + #{item} + + + + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/resources/mapper/business/oa/bank/BankMapper.xml b/smart-flow-api/sa-admin/src/main/resources/mapper/business/oa/bank/BankMapper.xml new file mode 100644 index 0000000..79b505d --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/resources/mapper/business/oa/bank/BankMapper.xml @@ -0,0 +1,58 @@ + + + + + UPDATE t_oa_bank + SET deleted_flag = #{deletedFlag} + WHERE bank_id = #{bankId} + + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/resources/mapper/business/oa/enterprise/EnterpriseEmployeeMapper.xml b/smart-flow-api/sa-admin/src/main/resources/mapper/business/oa/enterprise/EnterpriseEmployeeMapper.xml new file mode 100644 index 0000000..62acf08 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/resources/mapper/business/oa/enterprise/EnterpriseEmployeeMapper.xml @@ -0,0 +1,94 @@ + + + + + + delete from t_oa_enterprise_employee where enterprise_id = #{enterpriseId} and employee_id in + + #{item} + + + + + delete from t_oa_enterprise_employee where employee_id = #{employeeId} + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/resources/mapper/business/oa/enterprise/EnterpriseMapper.xml b/smart-flow-api/sa-admin/src/main/resources/mapper/business/oa/enterprise/EnterpriseMapper.xml new file mode 100644 index 0000000..1484e71 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/resources/mapper/business/oa/enterprise/EnterpriseMapper.xml @@ -0,0 +1,89 @@ + + + + + UPDATE t_oa_enterprise + SET deleted_flag = #{deletedFlag} + WHERE enterprise_id = #{enterpriseId} + + + + + + + + + + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/resources/mapper/business/oa/invoice/InvoiceMapper.xml b/smart-flow-api/sa-admin/src/main/resources/mapper/business/oa/invoice/InvoiceMapper.xml new file mode 100644 index 0000000..0001d92 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/resources/mapper/business/oa/invoice/InvoiceMapper.xml @@ -0,0 +1,56 @@ + + + + + UPDATE t_oa_invoice + SET deleted_flag = #{deletedFlag} + WHERE invoice_id = #{invoiceId} + + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/resources/mapper/business/oa/leave/LeaveMapper.xml b/smart-flow-api/sa-admin/src/main/resources/mapper/business/oa/leave/LeaveMapper.xml new file mode 100644 index 0000000..ccd1e63 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/resources/mapper/business/oa/leave/LeaveMapper.xml @@ -0,0 +1,57 @@ + + + + + + + t_leave.leave_id, + t_leave.type, + t_leave.reason, + t_leave.start_time, + t_leave.end_time, + t_leave.day, + t_leave.instance_id, + t_leave.node_code, + t_leave.node_name, + t_leave.node_type, + t_leave.flow_status, + t_leave.create_user_id, + t_leave.create_time, + t_leave.update_time, + t_leave.deleted_flag + + + + + + + diff --git a/smart-flow-api/sa-admin/src/main/resources/mapper/business/oa/notice/NoticeMapper.xml b/smart-flow-api/sa-admin/src/main/resources/mapper/business/oa/notice/NoticeMapper.xml new file mode 100644 index 0000000..07eb19c --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/resources/mapper/business/oa/notice/NoticeMapper.xml @@ -0,0 +1,275 @@ + + + + + + + t_notice.notice_id, + t_notice.notice_type_id, + t_notice.title, + t_notice.all_visible_flag, + t_notice.scheduled_publish_flag, + t_notice.publish_time, + t_notice.content_text, + t_notice.content_html, + t_notice.attachment, + t_notice.page_view_count, + t_notice.user_view_count, + t_notice.source, + t_notice.author, + t_notice.document_number, + t_notice.instance_id, + t_notice.node_name, + t_notice.flow_status, + t_notice.deleted_flag, + t_notice.create_user_id, + t_notice.update_time, + t_notice.create_time + + + + + + insert into t_notice_visible_range + (notice_id, data_type, data_id) + values + + ( #{noticeId} , #{item.dataType}, #{item.dataId} ) + + + + delete + from t_notice_visible_range + where notice_id = #{noticeId} + + + + + + + update t_notice + set deleted_flag = true + where notice_id = #{noticeId} + + + + + + + + + + + + + + insert into t_notice_view_record (notice_id, employee_id, first_ip, first_user_agent, page_view_count) + values (#{noticeId}, #{employeeId}, #{ip}, #{userAgent}, #{pageViewCount}) + + + update t_notice_view_record + set page_view_count = page_view_count + 1, + last_ip = #{ip}, + last_user_agent = #{userAgent} + where notice_id = #{noticeId} + and employee_id = #{employeeId} + + + update t_notice + set page_view_count = page_view_count + #{pageViewCountIncrement}, + user_view_count = user_view_count + #{userViewCountIncrement} + where notice_id = #{noticeId} + + + \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/resources/mapper/flow/FlowTaskMapper.xml b/smart-flow-api/sa-admin/src/main/resources/mapper/flow/FlowTaskMapper.xml new file mode 100644 index 0000000..9a40376 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/resources/mapper/flow/FlowTaskMapper.xml @@ -0,0 +1,54 @@ + + + + + + + + select t.id, + t.node_code, + t.node_name, + t.node_type, + uu.processed_by as approver, + t.definition_id, + t.instance_id, + t.id as task_id, + t.flow_status, + t.form_custom, + t.form_path, + t.create_time, + t.update_time, + t.tenant_id, + t.del_flag + from flow_task t + LEFT JOIN flow_user uu ON uu.associated = t.id + where t.node_type = 1 AND t.del_flag = 0 + AND t.instance_id = #{instanceId} + + + update flow_user set processed_by = #{employeeId} where associated = + (select t.id from flow_task t where t.node_type = 1 AND t.del_flag = 0 AND t.instance_id = #{instanceId}) + + diff --git a/smart-flow-api/sa-admin/src/main/resources/mapper/flow/WarmFLowMapper.xml b/smart-flow-api/sa-admin/src/main/resources/mapper/flow/WarmFLowMapper.xml new file mode 100644 index 0000000..ea3e42b --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/resources/mapper/flow/WarmFLowMapper.xml @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/smart-flow-api/sa-admin/src/main/resources/mapper/system/PositionMapper.xml b/smart-flow-api/sa-admin/src/main/resources/mapper/system/PositionMapper.xml new file mode 100644 index 0000000..bdc93ea --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/resources/mapper/system/PositionMapper.xml @@ -0,0 +1,25 @@ + + + + + + + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/resources/mapper/system/department/DepartmentMapper.xml b/smart-flow-api/sa-admin/src/main/resources/mapper/system/department/DepartmentMapper.xml new file mode 100644 index 0000000..7206cb6 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/resources/mapper/system/department/DepartmentMapper.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/resources/mapper/system/employee/EmployeeMapper.xml b/smart-flow-api/sa-admin/src/main/resources/mapper/system/employee/EmployeeMapper.xml new file mode 100644 index 0000000..999ab73 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/resources/mapper/system/employee/EmployeeMapper.xml @@ -0,0 +1,436 @@ + + + + + + + + UPDATE t_employee + SET disabled_flag = #{disabledFlag} + WHERE employee_id = #{employeeId} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + UPDATE t_employee + SET login_pwd = #{password} + WHERE employee_id = #{employeeId} + + + + + + + + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/resources/mapper/system/menu/MenuMapper.xml b/smart-flow-api/sa-admin/src/main/resources/mapper/system/menu/MenuMapper.xml new file mode 100644 index 0000000..68fbede --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/resources/mapper/system/menu/MenuMapper.xml @@ -0,0 +1,78 @@ + + + + + + + + update t_menu + set deleted_flag = #{deletedFlag}, + update_user_id = #{updateUserId} + where menu_id = #{item} + + + + + + + + + + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/resources/mapper/system/region/RegionMapper.xml b/smart-flow-api/sa-admin/src/main/resources/mapper/system/region/RegionMapper.xml new file mode 100644 index 0000000..0bd2c82 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/resources/mapper/system/region/RegionMapper.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + diff --git a/smart-flow-api/sa-admin/src/main/resources/mapper/system/role/RoleDataScopeMapper.xml b/smart-flow-api/sa-admin/src/main/resources/mapper/system/role/RoleDataScopeMapper.xml new file mode 100644 index 0000000..05c3e9d --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/resources/mapper/system/role/RoleDataScopeMapper.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + DELETE FROM t_role_data_scope + WHERE role_id = #{roleId} + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/resources/mapper/system/role/RoleEmployeeMapper.xml b/smart-flow-api/sa-admin/src/main/resources/mapper/system/role/RoleEmployeeMapper.xml new file mode 100644 index 0000000..390a19c --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/resources/mapper/system/role/RoleEmployeeMapper.xml @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + DELETE + FROM t_role_employee + WHERE employee_id = #{employeeId} + + + + + DELETE + FROM t_role_employee + WHERE role_id = #{roleId} + + + + DELETE + FROM t_role_employee + WHERE role_id = #{roleId} + and employee_id = #{employeeId} + + + + + DELETE FROM t_role_employee + WHERE role_id = #{roleId} and employee_id in + + #{item} + + + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/resources/mapper/system/role/RoleMapper.xml b/smart-flow-api/sa-admin/src/main/resources/mapper/system/role/RoleMapper.xml new file mode 100644 index 0000000..b2fb9db --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/resources/mapper/system/role/RoleMapper.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/resources/mapper/system/role/RoleMenuMapper.xml b/smart-flow-api/sa-admin/src/main/resources/mapper/system/role/RoleMenuMapper.xml new file mode 100644 index 0000000..448fa58 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/resources/mapper/system/role/RoleMenuMapper.xml @@ -0,0 +1,39 @@ + + + + + delete + from t_role_menu + where role_id = #{roleId} + + + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/resources/pre/application.yaml b/smart-flow-api/sa-admin/src/main/resources/pre/application.yaml new file mode 100644 index 0000000..fa5409f --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/resources/pre/application.yaml @@ -0,0 +1,28 @@ +############################################################################################################# +# # +# 为了减少重复配置,本配置文件为此sa-admin的独有配置,更多配置请查看 sa-base 项目中的 sa-base.yaml 通用配置文件。 # +# 其中此文件中配置可以覆盖 sa-base.yaml 中的通用配置,具体实现类请看类:YamlProcessor.java # +# # +############################################################################################################# + +# 项目配置: 名称、日志目录 +project: + name: sa-admin + log-directory: /home/logs/smart_admin_v3/${project.name}/${spring.profiles.active} + +# 项目端口和url根路径 +server: + port: 1024 + servlet: + context-path: / + +# 环境 +spring: + profiles: + active: '@profiles.active@' + +# 员工模块配置 +smart: + employee: + # 默认密码,若未配置则使用随机密码 + defaultPassword: 1 \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/resources/pre/log4j2-spring.xml b/smart-flow-api/sa-admin/src/main/resources/pre/log4j2-spring.xml new file mode 100644 index 0000000..d03ceca --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/resources/pre/log4j2-spring.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/resources/prod/application.yaml b/smart-flow-api/sa-admin/src/main/resources/prod/application.yaml new file mode 100644 index 0000000..fa5409f --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/resources/prod/application.yaml @@ -0,0 +1,28 @@ +############################################################################################################# +# # +# 为了减少重复配置,本配置文件为此sa-admin的独有配置,更多配置请查看 sa-base 项目中的 sa-base.yaml 通用配置文件。 # +# 其中此文件中配置可以覆盖 sa-base.yaml 中的通用配置,具体实现类请看类:YamlProcessor.java # +# # +############################################################################################################# + +# 项目配置: 名称、日志目录 +project: + name: sa-admin + log-directory: /home/logs/smart_admin_v3/${project.name}/${spring.profiles.active} + +# 项目端口和url根路径 +server: + port: 1024 + servlet: + context-path: / + +# 环境 +spring: + profiles: + active: '@profiles.active@' + +# 员工模块配置 +smart: + employee: + # 默认密码,若未配置则使用随机密码 + defaultPassword: 1 \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/resources/prod/log4j2-spring.xml b/smart-flow-api/sa-admin/src/main/resources/prod/log4j2-spring.xml new file mode 100644 index 0000000..e0b752b --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/resources/prod/log4j2-spring.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/resources/sql/add_victim_gender_birthday.sql b/smart-flow-api/sa-admin/src/main/resources/sql/add_victim_gender_birthday.sql new file mode 100644 index 0000000..94a3713 --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/resources/sql/add_victim_gender_birthday.sql @@ -0,0 +1,22 @@ +-- 为 t_rights_defense_victim 表添加性别和出生日期字段 + +-- 添加性别字段(1-男,2-女) +ALTER TABLE t_rights_defense_victim ADD COLUMN victim_gender VARCHAR(1) COMMENT '性别 (1-男,2-女)'; + +-- 添加出生日期字段 +ALTER TABLE t_rights_defense_victim ADD COLUMN victim_birthday TIMESTAMP COMMENT '出生日期'; + +-- 可选:为现有数据填充性别和生日(如果有身份证号数据) +UPDATE t_rights_defense_victim +SET victim_gender = CASE + WHEN SUBSTR(victim_id_card, 17, 1) IN ('1', '3', '5', '7', '9') THEN '1' + WHEN SUBSTR(victim_id_card, 17, 1) IN ('0', '2', '4', '6', '8') THEN '2' + ELSE NULL +END, +victim_birthday = CASE + WHEN LENGTH(victim_id_card) >= 18 THEN + TO_TIMESTAMP(SUBSTR(victim_id_card, 7, 8), 'YYYYMMDD') + ELSE NULL +END +WHERE victim_id_card IS NOT NULL +AND LENGTH(victim_id_card) >= 18; diff --git a/smart-flow-api/sa-admin/src/main/resources/template/澄清正名通知书.doc b/smart-flow-api/sa-admin/src/main/resources/template/澄清正名通知书.doc new file mode 100644 index 0000000..1586e7d Binary files /dev/null and b/smart-flow-api/sa-admin/src/main/resources/template/澄清正名通知书.doc differ diff --git a/smart-flow-api/sa-admin/src/main/resources/test/application.yaml b/smart-flow-api/sa-admin/src/main/resources/test/application.yaml new file mode 100644 index 0000000..0dad83c --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/resources/test/application.yaml @@ -0,0 +1,28 @@ +############################################################################################################# +# # +# 为了减少重复配置,本配置文件为此sa-admin的独有配置,更多配置请查看 sa-base 项目中的 sa-base.yaml 通用配置文件。 # +# 其中此文件中配置可以覆盖 sa-base.yaml 中的通用配置,具体实现类请看类:YamlProcessor.java # +# # +############################################################################################################# + +# 项目配置: 名称、日志目录 +project: + name: sa-admin + log-directory: /home/project/smartadmin/test/log + +# 项目端口和url根路径 +server: + port: 1024 + servlet: + context-path: / + +# 环境 +spring: + profiles: + active: '@profiles.active@' + +# 员工模块配置 +smart: + employee: + # 默认密码,若未配置则使用随机密码 + defaultPassword: 1 \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/resources/test/log4j2-spring.xml b/smart-flow-api/sa-admin/src/main/resources/test/log4j2-spring.xml new file mode 100644 index 0000000..d03ceca --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/resources/test/log4j2-spring.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-admin/src/main/resources/test/spy.properties b/smart-flow-api/sa-admin/src/main/resources/test/spy.properties new file mode 100644 index 0000000..667e1ea --- /dev/null +++ b/smart-flow-api/sa-admin/src/main/resources/test/spy.properties @@ -0,0 +1,18 @@ +#相关的包 +modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory +# 日志格式 +logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger +#日志输出到控制台 +appender=com.p6spy.engine.spy.appender.StdoutLogger +# 设置 p6spy driver 代理 +deregisterdrivers=true +# 取消JDBC URL前缀 +useprefix=true +# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset. +excludecategories=info,debug,result,commit,resultset +# 日期格式 +dateformat=yyyy-MM-dd HH:mm:ss +# 开启慢sql +outagedetection=true +# 慢SQL记录标准(单位秒) +outagedetectioninterval=2 \ No newline at end of file diff --git a/smart-flow-api/sa-base/pom.xml b/smart-flow-api/sa-base/pom.xml new file mode 100644 index 0000000..71a1f77 --- /dev/null +++ b/smart-flow-api/sa-base/pom.xml @@ -0,0 +1,314 @@ + + 4.0.0 + + net.lab1024 + sa-parent + 3.0.1 + ../pom.xml + + + sa-base + 3.0.1 + + sa-base + sa-base project + + + + + org.springframework.boot + spring-boot-starter-aop + + + org.springframework.boot + spring-boot-starter-logging + + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + org.springframework.boot + spring-boot-starter + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + + + + org.springframework.security + spring-security-crypto + + + + + cn.dev33 + sa-token-spring-boot3-starter + + + + cn.dev33 + sa-token-redis-jackson + + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + + org.springframework.boot + spring-boot-starter-validation + + + + org.springframework.boot + spring-boot-starter-test + + + + com.mysql + mysql-connector-j + + + cn.com.kingbase + kingbase8 + + + com.github.ben-manes.caffeine + caffeine + + + error_prone_annotations + com.google.errorprone + + + + + + org.projectlombok + lombok + + + + org.apache.commons + commons-pool2 + + + + org.apache.commons + commons-text + + + + com.baomidou + mybatis-plus-spring-boot3-starter + + + org.springframework.boot + spring-boot-starter-logging + + + + + + + com.baomidou + mybatis-plus-extension + ${mybatis-plus.version} + + + + + org.mybatis + mybatis + + + + p6spy + p6spy + + + + com.github.xiaoymin + knife4j-openapi3-jakarta-spring-boot-starter + + + + com.squareup.okhttp3 + okhttp + + + + com.alibaba + fastjson + + + + com.alibaba + druid-spring-boot-3-starter + + + + com.google.guava + guava + + + + com.googlecode.concurrentlinkedhashmap + concurrentlinkedhashmap-lru + + + + org.reflections + reflections + + + + com.amazonaws + aws-java-sdk-s3 + + + + org.apache.commons + commons-lang3 + + + org.apache.commons + commons-collections4 + + + + commons-io + commons-io + + + + org.apache.commons + commons-compress + + + + cn.hutool + hutool-all + + + + io.jsonwebtoken + jjwt + + + + com.auth0 + jwks-rsa + + + + org.apache.velocity + velocity-engine-core + + + + org.apache.velocity.tools + velocity-tools-generic + + + + org.lionsoul + ip2region + + + + org.bouncycastle + bcprov-jdk18on + + + + cn.idev.excel + fastexcel + + + logback-classic + ch.qos.logback + + + + + + org.apache.poi + poi + + + + org.apache.poi + poi-ooxml + + + + org.apache.poi + poi-scratchpad + + + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + + net.1024lab + smartdb + ${smartdb.version} + + + + org.redisson + redisson-spring-boot-starter + + + + org.yaml + snakeyaml + + + + org.springframework.boot + spring-boot-starter-mail + + + + org.jsoup + jsoup + + + + org.freemarker + freemarker + + + + org.apache.tika + tika-core + + + + + + diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/annoation/NoNeedLogin.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/annoation/NoNeedLogin.java new file mode 100644 index 0000000..87e10e1 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/annoation/NoNeedLogin.java @@ -0,0 +1,20 @@ +package net.lab1024.sa.base.common.annoation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 不需要登录注解 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022-05-30 21:22:12 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface NoNeedLogin { +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/code/ErrorCode.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/code/ErrorCode.java new file mode 100644 index 0000000..31eed10 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/code/ErrorCode.java @@ -0,0 +1,47 @@ +package net.lab1024.sa.base.common.code; + +/** + * 错误码
+ * 一共分为三种: 1)系统错误、2)用户级别错误、3)未预期到的错误 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021-09-02 20:21:10 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public interface ErrorCode { + + /** + * 系统等级 + */ + String LEVEL_SYSTEM = "system"; + + /** + * 用户等级 + */ + String LEVEL_USER = "user"; + + /** + * 未预期到的等级 + */ + String LEVEL_UNEXPECTED = "unexpected"; + + /** + * 错误码 + */ + int getCode(); + + /** + * 错误消息 + * + */ + String getMsg(); + + /** + * 错误等级 + */ + String getLevel(); + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/code/ErrorCodeRangeContainer.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/code/ErrorCodeRangeContainer.java new file mode 100644 index 0000000..73c5b3a --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/code/ErrorCodeRangeContainer.java @@ -0,0 +1,111 @@ +package net.lab1024.sa.base.common.code; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 错误码 注册容器 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021/09/27 22:09 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +class ErrorCodeRangeContainer { + + /** + * 所有的错误码均大于10000 + */ + static final int MIN_START_CODE = 10000; + + static final Map, ImmutablePair> CODE_RANGE_MAP = new ConcurrentHashMap<>(); + + /** + * 用于统计数量 + */ + static int errorCounter = 0; + + /** + * 注册状态码 + * 校验是否重复 是否越界 + * + */ + static void register(Class clazz, int start, int end) { + String simpleName = clazz.getSimpleName(); + if (!clazz.isEnum()) { + throw new ExceptionInInitializerError(String.format("<> error: %s not Enum class !", simpleName)); + } + if (start > end) { + throw new ExceptionInInitializerError(String.format("<> error: %s start must be less than the end !", simpleName)); + } + + if (start <= MIN_START_CODE) { + throw new ExceptionInInitializerError(String.format("<> error: %s start must be more than %s !", simpleName, MIN_START_CODE)); + } + + // 校验是否重复注册 + boolean containsKey = CODE_RANGE_MAP.containsKey(clazz); + if (containsKey) { + throw new ExceptionInInitializerError(String.format("<> error: Enum %s already exist !", simpleName)); + } + + // 校验 开始结束值 是否越界 + CODE_RANGE_MAP.forEach((k, v) -> { + if (isExistOtherRange(start, end, v)) { + throw new IllegalArgumentException(String.format("<> error: %s[%d,%d] has intersection with class:%s[%d,%d]", simpleName, start, end, + k.getSimpleName(), v.getLeft(), v.getRight())); + } + }); + + // 循环校验code并存储 + List codeList = Stream.of(clazz.getEnumConstants()).map(codeEnum -> { + int code = codeEnum.getCode(); + if (code < start || code > end) { + throw new IllegalArgumentException(String.format("<> error: %s[%d,%d] code %d out of range", simpleName, start, end, code)); + } + return code; + }).collect(Collectors.toList()); + + // 校验code是否重复 + List distinctCodeList = codeList.stream().distinct().collect(Collectors.toList()); + Collection subtract = CollectionUtils.subtract(codeList, distinctCodeList); + if (CollectionUtils.isNotEmpty(subtract)) { + throw new IllegalArgumentException(String.format("<> error: %s code %s is repeat!", simpleName, subtract)); + } + + CODE_RANGE_MAP.put(clazz, ImmutablePair.of(start, end)); + // 统计 + errorCounter = errorCounter + distinctCodeList.size(); + } + + /** + * 是否存在于其他范围 + */ + private static boolean isExistOtherRange(int start, int end, ImmutablePair range) { + if (start >= range.getLeft() && start <= range.getRight()) { + return true; + } + + if (end >= range.getLeft() && end <= range.getRight()) { + return true; + } + + return false; + } + + /** + * 进行初始化 + */ + static int initialize() { + return errorCounter; + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/code/ErrorCodeRegister.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/code/ErrorCodeRegister.java new file mode 100644 index 0000000..4fa925a --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/code/ErrorCodeRegister.java @@ -0,0 +1,41 @@ +package net.lab1024.sa.base.common.code; + +import static net.lab1024.sa.base.common.code.ErrorCodeRangeContainer.register; + +/** + * 注册code状态码
+ * ps:为什么要在此处不那么优雅的手动注册? + * 主要是为了能统一、清晰、浏览当前定义的所有状态码 + * 方便后续维护 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021/09/27 23:09 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class ErrorCodeRegister { + + static { + + // 系统 错误码 + register(SystemErrorCode.class, 10001, 20000); + + // 意外 错误码 + register(UnexpectedErrorCode.class, 20001, 30000); + + // 用户 通用错误码 + register(UserErrorCode.class, 30001, 40000); + + } + + + public static int initialize() { + return ErrorCodeRangeContainer.initialize(); + } + + public static void main(String[] args) { + ErrorCodeRegister.initialize(); + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/code/SystemErrorCode.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/code/SystemErrorCode.java new file mode 100644 index 0000000..f3e0c39 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/code/SystemErrorCode.java @@ -0,0 +1,39 @@ +package net.lab1024.sa.base.common.code; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 系统错误状态码(此类返回码应该高度重视) + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021/10/24 20:09 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Getter +@AllArgsConstructor +public enum SystemErrorCode implements ErrorCode { + + /** + * 系统错误 + */ + SYSTEM_ERROR(10001, "系统似乎出现了点小问题"), + + ; + + private final int code; + + private final String msg; + + private final String level; + + SystemErrorCode(int code, String msg) { + this.code = code; + this.msg = msg; + this.level = LEVEL_SYSTEM; + } + +} + diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/code/UnexpectedErrorCode.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/code/UnexpectedErrorCode.java new file mode 100644 index 0000000..c1517e7 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/code/UnexpectedErrorCode.java @@ -0,0 +1,43 @@ +package net.lab1024.sa.base.common.code; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 未预期的错误码(即发生了不可能发生的事情,此类返回码应该高度重视) + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021/09/27 22:10:46 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Getter +@AllArgsConstructor +public enum UnexpectedErrorCode implements ErrorCode { + + /** + * 业务错误 + */ + BUSINESS_HANDING(20001, "呃~ 业务繁忙,请稍后重试"), + + /** + * id错误 + */ + PAY_ORDER_ID_ERROR(20002, "付款单id发生了异常,请联系技术人员排查"), + + ; + + private final int code; + + private final String msg; + + private final String level; + + UnexpectedErrorCode(int code, String msg) { + this.code = code; + this.msg = msg; + this.level = LEVEL_UNEXPECTED; + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/code/UserErrorCode.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/code/UserErrorCode.java new file mode 100644 index 0000000..f6dcfeb --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/code/UserErrorCode.java @@ -0,0 +1,53 @@ +package net.lab1024.sa.base.common.code; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 用户级别的错误码(用户引起的错误返回码,可以不用关注) + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021/09/21 22:12:27 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Getter +@AllArgsConstructor +public enum UserErrorCode implements ErrorCode { + + PARAM_ERROR(30001, "参数错误"), + + DATA_NOT_EXIST(30002, "左翻右翻,数据竟然找不到了~"), + + ALREADY_EXIST(30003, "数据已存在了呀~"), + + REPEAT_SUBMIT(30004, "亲~您操作的太快了,请稍等下再操作~"), + + NO_PERMISSION(30005, "对不起,您没有权限访问此内容哦~"), + + DEVELOPING(30006, "系統正在紧急开发中,敬请期待~"), + + LOGIN_STATE_INVALID(30007, "您还未登录或登录失效,请重新登录!"), + + USER_STATUS_ERROR(30008, "用户状态异常"), + + FORM_REPEAT_SUBMIT(30009, "请勿重复提交"), + + LOGIN_FAIL_LOCK(30010, "登录连续失败已经被锁定,无法登录"), + LOGIN_FAIL_WILL_LOCK(30011, "登录连续失败将会锁定提醒"), + + LOGIN_ACTIVE_TIMEOUT(30012, "长时间未操作系统,需要重新登录"); + + private final int code; + + private final String msg; + + private final String level; + + UserErrorCode(int code, String msg) { + this.code = code; + this.msg = msg; + this.level = LEVEL_USER; + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/constant/RequestHeaderConst.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/constant/RequestHeaderConst.java new file mode 100644 index 0000000..d0e4060 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/constant/RequestHeaderConst.java @@ -0,0 +1,18 @@ +package net.lab1024.sa.base.common.constant; + +/** + * 请求消息头常量 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022-05-15 20:46:27 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class RequestHeaderConst { + + public static final String TOKEN = "Authorization"; + + public static final String USER_AGENT = "user-agent"; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/constant/StringConst.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/constant/StringConst.java new file mode 100644 index 0000000..e9e8fdf --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/constant/StringConst.java @@ -0,0 +1,49 @@ +package net.lab1024.sa.base.common.constant; + +/** + * 字符串常量 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021-10-14 23:16:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class StringConst { + + /** + * 全局通用分隔符 + */ + public static final String SEPARATOR = ","; + + /** + * 全局通用分隔符 下划线 + */ + public static final String UNDERLINE = "_"; + + /** + * 全局通用 横杠 + */ + public static final String HORIZONTAL = "-"; + + /** + * 全局通用分隔符 + */ + public static final Character SEPARATOR_CHAR = ','; + + /** + * 全局通用分隔符 斜杠 + */ + public static final String SEPARATOR_SLASH = "/"; + + /** + * 空字符串 + */ + public static final String EMPTY = ""; + + /** + * 全局通用 冒号 + */ + public static final String COLON = ":"; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/controller/SupportBaseController.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/controller/SupportBaseController.java new file mode 100644 index 0000000..4449af2 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/controller/SupportBaseController.java @@ -0,0 +1,17 @@ +package net.lab1024.sa.base.common.controller; + +import net.lab1024.sa.base.constant.SwaggerTagConst; +import org.springframework.web.bind.annotation.RequestMapping; + +/** + * 支撑类业务路由基类 + * + * @Author 1024创新实验室: 胡克 + * @Date 2022-04-24 20:43:55 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@RequestMapping(SwaggerTagConst.Support.URL_PREFIX) +public class SupportBaseController { +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/domain/DataScopePlugin.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/domain/DataScopePlugin.java new file mode 100644 index 0000000..0fe704c --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/domain/DataScopePlugin.java @@ -0,0 +1,15 @@ +package net.lab1024.sa.base.common.domain; + +import org.apache.ibatis.plugin.Interceptor; + +/** + * 数据范围 插件 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2021-11-15 17:20:04 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public abstract class DataScopePlugin implements Interceptor { +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/domain/PageParam.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/domain/PageParam.java new file mode 100644 index 0000000..59a6dc4 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/domain/PageParam.java @@ -0,0 +1,58 @@ +package net.lab1024.sa.base.common.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import java.util.List; + +/** + * 分页基础参数 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2020/04/28 16:19 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class PageParam { + + @Schema(description = "页码(不能为空)", example = "1") + @NotNull(message = "分页参数不能为空") + private Long pageNum; + + @Schema(description = "每页数量(不能为空)", example = "10") + @NotNull(message = "每页数量不能为空") + @Max(value = 500, message = "每页最大为500") + private Long pageSize; + + @Schema(description = "是否查询总条数") + protected Boolean searchCount; + + @Schema(description = "排序字段集合") + @Size(max = 10, message = "排序字段最多10") + @Valid + private List sortItemList; + + /** + * 排序DTO类 + */ + @Data + public static class SortItem { + + @Schema(description = "true正序|false倒序") + @NotNull(message = "排序规则不能为空") + private Boolean isAsc; + + @Schema(description = "排序字段") + @NotBlank(message = "排序字段不能为空") + @Length(max = 30, message = "排序字段最多30") + private String column; + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/domain/PageResult.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/domain/PageResult.java new file mode 100644 index 0000000..14274b3 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/domain/PageResult.java @@ -0,0 +1,53 @@ +package net.lab1024.sa.base.common.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +/** + * 分页返回对象 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2020/04/28 16:19 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class PageResult { + + /** + * 当前页 + */ + @Schema(description = "当前页") + private Long pageNum; + + /** + * 每页的数量 + */ + @Schema(description = "每页的数量") + private Long pageSize; + + /** + * 总记录数 + */ + @Schema(description = "总记录数") + private Long total; + + /** + * 总页数 + */ + @Schema(description = "总页数") + private Long pages; + + /** + * 结果集 + */ + @Schema(description = "结果集") + private List list; + + @Schema(description = "是否为空") + private Boolean emptyFlag; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/domain/RequestUrlVO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/domain/RequestUrlVO.java new file mode 100644 index 0000000..4669c2d --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/domain/RequestUrlVO.java @@ -0,0 +1,26 @@ +package net.lab1024.sa.base.common.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 请求url返回对象 + * + * @Author 1024创新实验室: 李善逸 + * @Date 2021/9/1 20:15 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class RequestUrlVO { + + @Schema(description = "注释说明") + private String comment; + + @Schema(description = "controller.method") + private String name; + + @Schema(description = "url") + private String url; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/domain/RequestUser.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/domain/RequestUser.java new file mode 100644 index 0000000..9886436 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/domain/RequestUser.java @@ -0,0 +1,49 @@ +package net.lab1024.sa.base.common.domain; + +import net.lab1024.sa.base.common.enumeration.UserTypeEnum; + +/** + * 请求用户 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021-12-21 19:55:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public interface RequestUser { + + /** + * 请求用户id + * + * @return + */ + Long getUserId(); + + /** + * 请求用户名称 + * + * @return + */ + String getUserName(); + + /** + * 获取用户类型 + */ + UserTypeEnum getUserType(); + + /** + * 获取请求的IP + * + * @return + */ + String getIp(); + + /** + * 获取请求 user-agent + * + * @return + */ + String getUserAgent(); + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/domain/ResponseDTO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/domain/ResponseDTO.java new file mode 100644 index 0000000..513091a --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/domain/ResponseDTO.java @@ -0,0 +1,121 @@ +package net.lab1024.sa.base.common.domain; + + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.code.ErrorCode; +import net.lab1024.sa.base.common.code.UserErrorCode; +import net.lab1024.sa.base.common.enumeration.DataTypeEnum; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import org.apache.commons.lang3.StringUtils; + +/** + * 请求返回对象 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021-10-31 21:06:11 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@Schema +public class ResponseDTO { + + public static final int OK_CODE = 0; + + public static final String OK_MSG = "操作成功"; + + @Schema(description = "返回码") + private Integer code; + + @Schema(description = "级别") + private String level; + + private String msg; + + private Boolean ok; + + @Schema(description = "返回数据") + private T data; + + @SchemaEnum(value = DataTypeEnum.class,desc = "数据类型") + private Integer dataType; + + public ResponseDTO(Integer code, String level, boolean ok, String msg, T data) { + this.code = code; + this.level = level; + this.ok = ok; + this.msg = msg; + this.data = data; + this.dataType = DataTypeEnum.NORMAL.getValue(); + } + + public ResponseDTO(Integer code, String level, boolean ok, String msg) { + this.code = code; + this.level = level; + this.ok = ok; + this.msg = msg; + this.dataType = DataTypeEnum.NORMAL.getValue(); + } + + public ResponseDTO(ErrorCode errorCode, boolean ok, String msg, T data) { + this.code = errorCode.getCode(); + this.level = errorCode.getLevel(); + this.ok = ok; + if (StringUtils.isNotBlank(msg)) { + this.msg = msg; + } else { + this.msg = errorCode.getMsg(); + } + this.data = data; + this.dataType = DataTypeEnum.NORMAL.getValue(); + } + + public static ResponseDTO ok() { + return new ResponseDTO<>(OK_CODE, null, true, OK_MSG, null); + } + + public static ResponseDTO ok(T data) { + return new ResponseDTO<>(OK_CODE, null, true, OK_MSG, data); + } + + public static ResponseDTO okMsg(String msg) { + return new ResponseDTO<>(OK_CODE, null, true, msg, null); + } + + // -------------------------------------------- 最常用的 用户参数 错误码 -------------------------------------------- + + public static ResponseDTO userErrorParam() { + return new ResponseDTO<>(UserErrorCode.PARAM_ERROR, false, null, null); + } + + + public static ResponseDTO userErrorParam(String msg) { + return new ResponseDTO<>(UserErrorCode.PARAM_ERROR, false, msg, null); + } + + // -------------------------------------------- 错误码 -------------------------------------------- + + public static ResponseDTO error(ErrorCode errorCode) { + return new ResponseDTO<>(errorCode, false, null, null); + } + + public static ResponseDTO error(ErrorCode errorCode, boolean ok) { + return new ResponseDTO<>(errorCode, ok, null, null); + } + + public static ResponseDTO error(ResponseDTO responseDTO) { + return new ResponseDTO<>(responseDTO.getCode(), responseDTO.getLevel(), responseDTO.getOk(), responseDTO.getMsg(), null); + } + + public static ResponseDTO error(ErrorCode errorCode, String msg) { + return new ResponseDTO<>(errorCode, false, msg, null); + } + + public static ResponseDTO errorData(ErrorCode errorCode, T data) { + return new ResponseDTO<>(errorCode, false, null, data); + } + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/domain/SystemEnvironment.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/domain/SystemEnvironment.java new file mode 100644 index 0000000..232b292 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/domain/SystemEnvironment.java @@ -0,0 +1,35 @@ +package net.lab1024.sa.base.common.domain; + + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.lab1024.sa.base.common.enumeration.SystemEnvironmentEnum; + +/** + * 系统环境 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021/8/13 21:06:11 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@AllArgsConstructor +@Getter +public class SystemEnvironment { + + /** + * 是否位生产环境 + */ + private boolean isProd; + + /** + * 项目名称 + */ + private String projectName; + + /** + * 当前环境 + */ + private SystemEnvironmentEnum currentEnvironment; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/domain/UserPermission.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/domain/UserPermission.java new file mode 100644 index 0000000..c10897d --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/domain/UserPermission.java @@ -0,0 +1,31 @@ +package net.lab1024.sa.base.common.domain; + +import lombok.Data; + +import java.util.List; + +/** + * sa-token 所需的权限信息 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/8/26 15:23:10 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室,Since 2012 + */ + +@Data +public class UserPermission { + + /** + * 权限列表 + */ + private List permissionList; + + /** + * 角色列表 + */ + private List roleList; + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/domain/ValidateData.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/domain/ValidateData.java new file mode 100644 index 0000000..15fbf00 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/domain/ValidateData.java @@ -0,0 +1,20 @@ +package net.lab1024.sa.base.common.domain; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 校验数据是否为空的包装类 + * + * @Author 1024创新实验室: 胡克 + * @Date 2020/10/16 21:06:11 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class ValidateData { + + @NotNull(message = "数据不能为空哦") + private T data; +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/domain/ValidateList.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/domain/ValidateList.java new file mode 100644 index 0000000..ec302c1 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/domain/ValidateList.java @@ -0,0 +1,154 @@ +package net.lab1024.sa.base.common.domain; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; + +import java.util.*; + +/** + * 校验集合是否为空的包装类 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2020-02-03 17:37 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class ValidateList implements List { + + @Valid + @NotEmpty(message = "数据长度不能为空哦") + private List list; + + public ValidateList() { + this.list = new ArrayList<>(); + } + + public ValidateList(List list) { + this.list = list; + } + + public List getList() { + return list; + } + + public void setList(List list) { + this.list = list; + } + + @Override + public int size() { + return list.size(); + } + + @Override + public boolean isEmpty() { + return list.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return list.contains(o); + } + + @Override + public Iterator iterator() { + return list.iterator(); + } + + @Override + public Object[] toArray() { + return list.toArray(); + } + + @Override + public T[] toArray(T[] a) { + return list.toArray(a); + } + + @Override + public boolean add(E e) { + return list.add(e); + } + + @Override + public boolean remove(Object o) { + return list.remove(o); + } + + @Override + public boolean containsAll(Collection c) { + return list.containsAll(c); + } + + @Override + public boolean addAll(Collection c) { + return list.addAll(c); + } + + @Override + public boolean addAll(int index, Collection c) { + return list.addAll(index, c); + } + + @Override + public boolean removeAll(Collection c) { + return list.removeAll(c); + } + + @Override + public boolean retainAll(Collection c) { + return list.retainAll(c); + } + + @Override + public void clear() { + list.clear(); + } + + @Override + public E get(int index) { + return list.get(index); + } + + @Override + public E set(int index, E element) { + return list.set(index, element); + } + + @Override + public void add(int index, E element) { + list.add(index, element); + } + + @Override + public E remove(int index) { + return list.remove(index); + } + + @Override + public int indexOf(Object o) { + return list.indexOf(o); + } + + @Override + public int lastIndexOf(Object o) { + return list.lastIndexOf(o); + } + + @Override + public ListIterator listIterator() { + return list.listIterator(); + } + + @Override + public ListIterator listIterator(int index) { + return list.listIterator(index); + } + + @Override + public List subList(int fromIndex, int toIndex) { + return list.subList(fromIndex, toIndex); + } + +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/enumeration/BaseEnum.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/enumeration/BaseEnum.java new file mode 100644 index 0000000..6593aa9 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/enumeration/BaseEnum.java @@ -0,0 +1,99 @@ +package net.lab1024.sa.base.common.enumeration; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONAware; +import com.alibaba.fastjson.JSONObject; +import com.google.common.base.CaseFormat; +import lombok.Data; + +import java.util.LinkedHashMap; +import java.util.Objects; + +/** + * 枚举类接口 + * + * @Author 1024创新实验室: 胡克 + * @Date 2018-07-17 21:22:12 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public interface BaseEnum { + + /** + * 获取枚举类的值 + * + * @return + */ + Object getValue(); + + /** + * 获取枚举类的说明 + * + * @return String + */ + String getDesc(); + + /** + * 比较参数是否与枚举类的value相同 + * + * @param value + * @return boolean + */ + default boolean equalsValue(Object value) { + return Objects.equals(getValue(), value); + } + + /** + * 比较枚举类是否相同 + * + * @param baseEnum + * @return boolean + */ + default boolean equals(BaseEnum baseEnum) { + return Objects.equals(getValue(), baseEnum.getValue()) && Objects.equals(getDesc(), baseEnum.getDesc()); + } + + /** + * 返回枚举类的说明 + * + * @param clazz 枚举类类对象 + * @return + */ + static String getInfo(Class clazz) { + BaseEnum[] enums = clazz.getEnumConstants(); + LinkedHashMap json = new LinkedHashMap<>(enums.length); + for (BaseEnum e : enums) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("value", new DeletedQuotationAware(e.getValue())); + jsonObject.put("desc", new DeletedQuotationAware(e.getDesc())); + json.put(e.toString(), jsonObject); + } + + String enumJson = JSON.toJSONString(json, true); + enumJson = enumJson.replaceAll("\"", ""); + enumJson = enumJson.replaceAll("\t", "  "); + enumJson = enumJson.replaceAll("\n", "
"); + String prefix = "
export const " + CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, clazz.getSimpleName() + " =
"); + return prefix + enumJson + "
"; + } + + @Data + class DeletedQuotationAware implements JSONAware { + + private String value; + + public DeletedQuotationAware(Object value) { + if (value instanceof String) { + this.value = "'" + value + "'"; + } else { + this.value = value.toString(); + } + } + + @Override + public String toJSONString() { + return value; + } + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/enumeration/DataTypeEnum.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/enumeration/DataTypeEnum.java new file mode 100644 index 0000000..6cb068c --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/enumeration/DataTypeEnum.java @@ -0,0 +1,32 @@ +package net.lab1024.sa.base.common.enumeration; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/10/25 09:47:13 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室,Since 2012 + */ + +@Getter +@AllArgsConstructor +public enum DataTypeEnum implements BaseEnum { + + /** + *普通数据 + */ + NORMAL(1, "普通数据"), + + /** + * 加密数据 + */ + ENCRYPT(10, "加密数据"), + ; + private final Integer value; + + private final String desc; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/enumeration/GenderEnum.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/enumeration/GenderEnum.java new file mode 100644 index 0000000..cbfbc39 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/enumeration/GenderEnum.java @@ -0,0 +1,37 @@ +package net.lab1024.sa.base.common.enumeration; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 性别枚举类 + * + * @Author 1024创新实验室: 胡克 + * @Date 2019/09/24 16:50 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@AllArgsConstructor +@Getter +public enum GenderEnum implements BaseEnum { + + /** + * 0 未知 + */ + UNKNOWN(0, "未知"), + + /** + * 男 1 奇数为阳 + */ + MAN(1, "男"), + + /** + * 女 2 偶数为阴 + */ + WOMAN(2, "女"); + + private final Integer value; + + private final String desc; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/enumeration/SystemEnvironmentEnum.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/enumeration/SystemEnvironmentEnum.java new file mode 100644 index 0000000..c4167fd --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/enumeration/SystemEnvironmentEnum.java @@ -0,0 +1,50 @@ +package net.lab1024.sa.base.common.enumeration; + + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 系统环境枚举类 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2020-10-15 22:45:04 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@AllArgsConstructor +@Getter +public enum SystemEnvironmentEnum implements BaseEnum { + /** + * dev + */ + DEV(SystemEnvironmentNameConst.DEV, "开发环境"), + + /** + * test + */ + TEST(SystemEnvironmentNameConst.TEST, "测试环境"), + + /** + * pre + */ + PRE(SystemEnvironmentNameConst.PRE, "预发布环境"), + + /** + * prod + */ + PROD(SystemEnvironmentNameConst.PROD, "生产环境"); + + private final String value; + + private final String desc; + + public static final class SystemEnvironmentNameConst { + public static final String DEV = "dev"; + public static final String TEST = "test"; + public static final String PRE = "pre"; + public static final String PROD = "prod"; + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/enumeration/UserTypeEnum.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/enumeration/UserTypeEnum.java new file mode 100644 index 0000000..60bec4d --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/enumeration/UserTypeEnum.java @@ -0,0 +1,37 @@ +package net.lab1024.sa.base.common.enumeration; + +/** + * 用户类型 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/10/19 21:46:24 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public enum UserTypeEnum implements BaseEnum { + + /** + * 管理端 员工用户 + */ + ADMIN_EMPLOYEE(1, "员工"); + + private Integer type; + + private String desc; + + UserTypeEnum(Integer type, String desc) { + this.type = type; + this.desc = desc; + } + + @Override + public Integer getValue() { + return type; + } + + @Override + public String getDesc() { + return desc; + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/exception/BusinessException.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/exception/BusinessException.java new file mode 100644 index 0000000..481210d --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/exception/BusinessException.java @@ -0,0 +1,38 @@ +package net.lab1024.sa.base.common.exception; + +import net.lab1024.sa.base.common.code.ErrorCode; + +/** + * 业务逻辑异常,全局异常拦截后统一返回ResponseCodeConst.SYSTEM_ERROR + * + * @Author 1024创新实验室: 罗伊 + * @Date 2020/8/25 21:57 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class BusinessException extends RuntimeException { + + public BusinessException() { + } + + public BusinessException(ErrorCode errorCode) { + super(errorCode.getMsg()); + } + + public BusinessException(String message) { + super(message); + } + + public BusinessException(String message, Throwable cause) { + super(message, cause); + } + + public BusinessException(Throwable cause) { + super(cause); + } + + public BusinessException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/json/deserializer/DictDataDeserializer.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/json/deserializer/DictDataDeserializer.java new file mode 100644 index 0000000..3369045 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/json/deserializer/DictDataDeserializer.java @@ -0,0 +1,49 @@ +package net.lab1024.sa.base.common.json.deserializer; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * 字典反序列化 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022-08-12 22:17:53 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +public class DictDataDeserializer extends JsonDeserializer { + + @Override + public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { + List list = new ArrayList<>(); + ObjectCodec objectCodec = jsonParser.getCodec(); + JsonNode listOrObjectNode = objectCodec.readTree(jsonParser); + String deserialize = ""; + try { + if (listOrObjectNode.isArray()) { + for (JsonNode node : listOrObjectNode) { + list.add(node.asText()); + } + } else { + list.add(listOrObjectNode.asText()); + } + deserialize = String.join(",", list); + } catch (Exception e) { + log.error(e.getMessage(), e); + deserialize = listOrObjectNode.asText(); + } + return deserialize; + } + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/json/deserializer/FileKeyVoDeserializer.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/json/deserializer/FileKeyVoDeserializer.java new file mode 100644 index 0000000..f2ebab1 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/json/deserializer/FileKeyVoDeserializer.java @@ -0,0 +1,53 @@ +package net.lab1024.sa.base.common.json.deserializer; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.module.support.file.domain.vo.FileVO; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 文件key反序列化
+ * 由于前端接收到的是序列化过的字段, 这边入库需要进行反序列化操作比较方便处理 + * + * @Author 1024创新实验室: 胡克 + * @Date 2022-11-24 17:15:23 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +public class FileKeyVoDeserializer extends JsonDeserializer { + + @Override + public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + List list = new ArrayList<>(); + ObjectCodec objectCodec = jsonParser.getCodec(); + JsonNode listOrObjectNode = objectCodec.readTree(jsonParser); + String deserialize = ""; + try { + if (listOrObjectNode.isArray()) { + for (JsonNode node : listOrObjectNode) { + list.add(objectCodec.treeToValue(node, FileVO.class)); + } + } else { + list.add(objectCodec.treeToValue(listOrObjectNode, FileVO.class)); + } + deserialize = list.stream().map(FileVO::getFileKey).collect(Collectors.joining(",")); + } catch (Exception e) { + log.error(e.getMessage(), e); + deserialize = listOrObjectNode.asText(); + } + return deserialize; + } + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/json/deserializer/LongJsonDeserializer.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/json/deserializer/LongJsonDeserializer.java new file mode 100644 index 0000000..01117eb --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/json/deserializer/LongJsonDeserializer.java @@ -0,0 +1,30 @@ +package net.lab1024.sa.base.common.json.deserializer; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.io.IOException; + +/** + * Long类型序列化 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2020-06-02 22:55:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class LongJsonDeserializer extends JsonDeserializer { + + @Override + public Long deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + String value = jsonParser.getText(); + try { + return value == null ? null : Long.parseLong(value); + } catch (NumberFormatException e) { + return null; + } + } +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/BigDecimalNullZeroSerializer.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/BigDecimalNullZeroSerializer.java new file mode 100644 index 0000000..aac0566 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/BigDecimalNullZeroSerializer.java @@ -0,0 +1,29 @@ +package net.lab1024.sa.base.common.json.serializer; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.math.BigDecimal; + +/** + * 数字序列化 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2020/8/20 21:04 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class BigDecimalNullZeroSerializer extends JsonSerializer { + + @Override + public void serialize(BigDecimal value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + if (value == null) { + jsonGenerator.writeNumber(BigDecimal.ZERO); + return; + } + jsonGenerator.writeNumber(value); + } +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/DataMaskingSerializer.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/DataMaskingSerializer.java new file mode 100644 index 0000000..ffe7c84 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/DataMaskingSerializer.java @@ -0,0 +1,59 @@ +package net.lab1024.sa.base.common.json.serializer; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.ContextualSerializer; +import net.lab1024.sa.base.module.support.datamasking.DataMasking; +import net.lab1024.sa.base.module.support.datamasking.DataMaskingTypeEnum; +import net.lab1024.sa.base.module.support.datamasking.SmartDataMaskingUtil; +import org.apache.commons.lang3.ObjectUtils; + +import java.io.IOException; + +/** + * 脱敏序列化 + * + * @author 罗伊 + * @description: + * @date 2024/7/21 4:39 下午 + */ +public class DataMaskingSerializer extends JsonSerializer implements ContextualSerializer { + + private DataMaskingTypeEnum typeEnum; + + @Override + public void serialize(Object value, JsonGenerator jsonGenerator, SerializerProvider serializers) throws IOException { + + if (ObjectUtils.isEmpty(value)) { + jsonGenerator.writeObject(value); + return; + } + + if (typeEnum == null) { + jsonGenerator.writeObject(SmartDataMaskingUtil.dataMasking(String.valueOf(value))); + return; + } + + jsonGenerator.writeObject(SmartDataMaskingUtil.dataMasking(value, typeEnum)); + } + + @Override + public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException { + // 判断beanProperty是不是空 + if (null == property) { + return prov.findNullValueSerializer(property); + } + + DataMasking annotation = property.getAnnotation(DataMasking.class); + if (null == annotation) { + return prov.findValueSerializer(property.getType(), property); + } + + typeEnum = annotation.value(); + return this; + } + +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/FileKeySerializer.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/FileKeySerializer.java new file mode 100644 index 0000000..0c751f2 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/FileKeySerializer.java @@ -0,0 +1,45 @@ +package net.lab1024.sa.base.common.json.serializer; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.module.support.file.service.FileService; +import org.apache.commons.lang3.StringUtils; + +import java.io.IOException; + +/** + * 文件key进行序列化对象 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2020/8/15 22:06 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class FileKeySerializer extends JsonSerializer { + + @Resource + private FileService fileService; + + + @Override + public void serialize(String value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + if (StringUtils.isEmpty(value)) { + jsonGenerator.writeString(value); + return; + } + if (fileService == null) { + jsonGenerator.writeString(value); + return; + } + ResponseDTO responseDTO = fileService.getFileUrl(value); + if (responseDTO.getOk()) { + jsonGenerator.writeString(responseDTO.getData()); + return; + } + jsonGenerator.writeString(value); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/FileKeyVoSerializer.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/FileKeyVoSerializer.java new file mode 100644 index 0000000..2970566 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/FileKeyVoSerializer.java @@ -0,0 +1,46 @@ +package net.lab1024.sa.base.common.json.serializer; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.google.common.collect.Lists; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.module.support.file.domain.vo.FileVO; +import net.lab1024.sa.base.module.support.file.service.FileService; +import org.apache.commons.lang3.StringUtils; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +/** + * 文件key进行序列化对象 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2020/8/15 22:06 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class FileKeyVoSerializer extends JsonSerializer { + + @Resource + private FileService fileService; + + + @Override + public void serialize(String value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + if (StringUtils.isEmpty(value)) { + jsonGenerator.writeObject(Lists.newArrayList()); + return; + } + if(fileService == null){ + jsonGenerator.writeString(value); + return; + } + String[] fileKeyArray = value.split(","); + List fileKeyList = Arrays.asList(fileKeyArray); + List fileKeyVOList = fileService.getFileList(fileKeyList); + jsonGenerator.writeObject(fileKeyVOList); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/LongJsonSerializer.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/LongJsonSerializer.java new file mode 100644 index 0000000..aa29548 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/LongJsonSerializer.java @@ -0,0 +1,37 @@ +package net.lab1024.sa.base.common.json.serializer; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; + +/** + * Long类型序列化 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2020-06-02 22:55:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class LongJsonSerializer extends JsonSerializer { + + public static final LongJsonSerializer INSTANCE = new LongJsonSerializer(); + + @Override + public void serialize(Long value, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException, JsonProcessingException { + if (null == value) { + gen.writeNull(); + return; + } + // js中最大安全整数16位 Number.MAX_SAFE_INTEGER + String longStr = String.valueOf(value); + if (longStr.length() > 16) { + gen.writeString(longStr); + } else { + gen.writeNumber(value); + } + } +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/enumeration/EnumSerialize.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/enumeration/EnumSerialize.java new file mode 100644 index 0000000..ace962b --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/enumeration/EnumSerialize.java @@ -0,0 +1,25 @@ +package net.lab1024.sa.base.common.json.serializer.enumeration; + +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 枚举类 序列化 注解 + * + * @author huke + * @date 2024年6月29日 + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@JacksonAnnotationsInside +@JsonSerialize(using = EnumSerializer.class, nullsUsing = EnumSerializer.class) +public @interface EnumSerialize { + + Class value(); +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/enumeration/EnumSerializer.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/enumeration/EnumSerializer.java new file mode 100644 index 0000000..9cd2324 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/enumeration/EnumSerializer.java @@ -0,0 +1,53 @@ +package net.lab1024.sa.base.common.json.serializer.enumeration; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.ContextualSerializer; +import net.lab1024.sa.base.common.constant.StringConst; +import net.lab1024.sa.base.common.enumeration.BaseEnum; +import net.lab1024.sa.base.common.util.SmartEnumUtil; +import net.lab1024.sa.base.common.util.SmartStringUtil; + +import java.io.IOException; +import java.util.stream.Collectors; + +/** + * 枚举 序列化 + * + * @author huke + * @date 2024年6月29日 + */ +public class EnumSerializer extends JsonSerializer implements ContextualSerializer { + + private Class enumClazz; + + @Override + public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + gen.writeObject(value); + String fieldName = gen.getOutputContext().getCurrentName() + "Desc"; + Object desc; + // 多个枚举类 逗号分割 + if (value instanceof String && String.valueOf(value).contains(StringConst.SEPARATOR)) { + desc = SmartStringUtil.splitConvertToIntList(String.valueOf(value), StringConst.SEPARATOR) + .stream().map(e -> SmartEnumUtil.getEnumDescByValue(e, enumClazz)).collect(Collectors.toList()); + + } else { + BaseEnum anEnum = SmartEnumUtil.getEnumByValue(value, enumClazz); + desc = null != anEnum ? anEnum.getDesc() : null; + } + gen.writeObjectField(fieldName, desc); + } + + @Override + public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException { + EnumSerialize annotation = property.getAnnotation(EnumSerialize.class); + if (null == annotation) { + return prov.findValueSerializer(property.getType(), property); + } + enumClazz = annotation.value(); + return this; + } +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/swagger/SchemaEnum.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/swagger/SchemaEnum.java new file mode 100644 index 0000000..b0a9421 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/swagger/SchemaEnum.java @@ -0,0 +1,39 @@ +package net.lab1024.sa.base.common.swagger; + +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 枚举类字段属性的 自定义 swagger 注解 + * + * @Author 1024创新实验室: 胡克 + * @Date 2019/05/16 23:18 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface SchemaEnum { + + /** + * 枚举类对象 + * + */ + Class value(); + + String example() default ""; + + boolean hidden() default false; + + boolean required() default true; + + String dataType() default ""; + + String desc() default ""; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/swagger/SchemaEnumPropertyCustomizer.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/swagger/SchemaEnumPropertyCustomizer.java new file mode 100644 index 0000000..d487b0e --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/swagger/SchemaEnumPropertyCustomizer.java @@ -0,0 +1,52 @@ +package net.lab1024.sa.base.common.swagger; + +import io.swagger.v3.core.converter.AnnotatedType; +import io.swagger.v3.oas.models.media.Schema; +import net.lab1024.sa.base.common.enumeration.BaseEnum; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; +import org.springdoc.core.customizers.PropertyCustomizer; +import org.springframework.stereotype.Component; + +import java.lang.annotation.Annotation; + +/** + * + * 自定义枚举类文档 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/12/25 23:28:51 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Component +public class SchemaEnumPropertyCustomizer implements PropertyCustomizer { + + @Override + public Schema customize(Schema schema, AnnotatedType type) { + if (type.getCtxAnnotations() == null) { + return schema; + } + + StringBuilder description = new StringBuilder(); + for (Annotation ctxAnnotation : type.getCtxAnnotations()) { + if (ctxAnnotation.annotationType().equals(CheckEnum.class) && ((CheckEnum) ctxAnnotation).required()) { + description.append("【必填】"); + } + } + + for (Annotation ctxAnnotation : type.getCtxAnnotations()) { + if (ctxAnnotation.annotationType().equals(SchemaEnum.class)) { + description.append(((SchemaEnum) ctxAnnotation).desc()); + Class clazz = ((SchemaEnum) ctxAnnotation).value(); + description.append(BaseEnum.getInfo(clazz)); + } + } + + if (description.length() > 0) { + schema.setDescription(description.toString()); + } + return schema; + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/swagger/SmartOperationCustomizer.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/swagger/SmartOperationCustomizer.java new file mode 100644 index 0000000..041182d --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/swagger/SmartOperationCustomizer.java @@ -0,0 +1,114 @@ +package net.lab1024.sa.base.common.swagger; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.dev33.satoken.annotation.SaCheckRole; +import cn.dev33.satoken.annotation.SaMode; +import io.swagger.v3.oas.models.Operation; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.module.support.apiencrypt.annotation.ApiDecrypt; +import net.lab1024.sa.base.module.support.apiencrypt.annotation.ApiEncrypt; +import org.springdoc.core.customizers.OperationCustomizer; +import org.springframework.web.method.HandlerMethod; + +import java.util.ArrayList; +import java.util.List; + +/** + * 权限、接口加解密等 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/12/26 13:47:39 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +public class SmartOperationCustomizer implements OperationCustomizer { + + @Override + public Operation customize(Operation operation, HandlerMethod handlerMethod) { + + List noteList = new ArrayList<>(); + + // 请求参数加密 + List encryptBuilderList = new ArrayList<>(); + + if (handlerMethod.getMethodAnnotation(ApiDecrypt.class) != null || + handlerMethod.getBeanType().getAnnotation(ApiDecrypt.class) != null) { + encryptBuilderList.add("【请求参数加密】"); + } + + if (handlerMethod.getMethodAnnotation(ApiEncrypt.class) != null || + handlerMethod.getBeanType().getAnnotation(ApiEncrypt.class) != null) { + encryptBuilderList.add("【返回结果加密】"); + } + + if (!encryptBuilderList.isEmpty()) { + noteList.add("
接口安全:" + SmartStringUtil.join(",", encryptBuilderList) + ""); + } + + // 权限 + noteList.addAll(getPermission(handlerMethod)); + + // 更新 + operation.setDescription(SmartStringUtil.join("
", noteList)); + + return operation; + } + + + private List getPermission(HandlerMethod handlerMethod) { + List values = new ArrayList<>(); + + StringBuilder permissionStringBuilder = new StringBuilder(); + SaCheckPermission classPermissions = handlerMethod.getBeanType().getAnnotation(SaCheckPermission.class); + if (classPermissions != null) { + permissionStringBuilder.append(""); + permissionStringBuilder.append("类:").append(getAnnotationNote(classPermissions.value(), classPermissions.mode())); + permissionStringBuilder.append("
"); + } + + SaCheckPermission methodPermission = handlerMethod.getMethodAnnotation(SaCheckPermission.class); + if (methodPermission != null) { + permissionStringBuilder.append(""); + permissionStringBuilder.append("方法:").append(getAnnotationNote(methodPermission.value(), methodPermission.mode())); + permissionStringBuilder.append("
"); + } + + if (permissionStringBuilder.length() > 0) { + permissionStringBuilder.insert(0, "权限校验:
"); + values.add(permissionStringBuilder.toString()); + } + + + StringBuilder roleStringBuilder = new StringBuilder(); + SaCheckRole classCheckRole = handlerMethod.getBeanType().getAnnotation(SaCheckRole.class); + if (classCheckRole != null) { + roleStringBuilder.append(""); + roleStringBuilder.append("类:").append(getAnnotationNote(classCheckRole.value(), classCheckRole.mode())); + roleStringBuilder.append("
"); + } + + SaCheckPermission methodCheckRole = handlerMethod.getMethodAnnotation(SaCheckPermission.class); + if (methodCheckRole != null) { + roleStringBuilder.append(""); + roleStringBuilder.append("方法:").append(getAnnotationNote(methodCheckRole.value(), methodCheckRole.mode())); + roleStringBuilder.append("
"); + } + + if (roleStringBuilder.length() > 0) { + roleStringBuilder.insert(0, "角色校验:
"); + values.add(roleStringBuilder.toString()); + } + + return values; + } + + private String getAnnotationNote(String[] values, SaMode mode) { + if (mode.equals(SaMode.AND)) { + return String.join(" 且 ", values); + } else { + return String.join(" 或 ", values); + } + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartBeanUtil.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartBeanUtil.java new file mode 100644 index 0000000..eb97688 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartBeanUtil.java @@ -0,0 +1,94 @@ +package net.lab1024.sa.base.common.util; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import org.springframework.beans.BeanUtils; + +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * bean相关工具类 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2018-01-15 10:48:23 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class SmartBeanUtil { + + /** + * 验证器 + */ + private static final Validator VALIDATOR = Validation.buildDefaultValidatorFactory().getValidator(); + + /** + * 复制bean的属性 + * + * @param source 源 要复制的对象 + * @param target 目标 复制到此对象 + */ + public static void copyProperties(Object source, Object target) { + BeanUtils.copyProperties(source, target); + } + + /** + * 复制对象 + * + * @param source 源 要复制的对象 + * @param target 目标 复制到此对象 + * @param + * @return + */ + public static T copy(Object source, Class target) { + if (source == null || target == null) { + return null; + } + try { + T newInstance = target.newInstance(); + BeanUtils.copyProperties(source, newInstance); + return newInstance; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * 复制list + * + * @param source + * @param target + * @param + * @param + * @return + */ + public static List copyList(List source, Class target) { + if (null == source || source.isEmpty()) { + return Collections.emptyList(); + } + return source.stream().map(e -> copy(e, target)).collect(Collectors.toList()); + } + + /** + * 手动验证对象 Model的属性 + * 需要配合 hibernate-validator 校验注解 + * + * @param t + * @return String 返回null代表验证通过,否则返回错误的信息 + */ + public static String verify(T t) { + // 获取验证结果 + Set> validate = VALIDATOR.validate(t); + if (validate.isEmpty()) { + // 验证通过 + return null; + } + // 返回错误信息 + List messageList = validate.stream().map(ConstraintViolation::getMessage).collect(Collectors.toList()); + return messageList.toString(); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartBigDecimalUtil.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartBigDecimalUtil.java new file mode 100644 index 0000000..fdd2a65 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartBigDecimalUtil.java @@ -0,0 +1,247 @@ +package net.lab1024.sa.base.common.util; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +/** + * BigDecimal 工具类 + * + * @Author 1024创新实验室: 胡克 + * @Date 2018/01/17 13:54 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class SmartBigDecimalUtil { + + /** + * 金额 保留小数点 2 + */ + public static final int AMOUNT_DECIMAL_POINT = 2; + + public static final BigDecimal ONE_HUNDRED = new BigDecimal("100"); + + /** + * 金额相关计算方法:四舍五入 保留2位小数点 + */ + public static class Amount { + + public static BigDecimal add(BigDecimal num1, BigDecimal num2) { + return setScale(num1.add(num2), AMOUNT_DECIMAL_POINT); + } + + public static BigDecimal multiply(BigDecimal num1, BigDecimal num2) { + return setScale(num1.multiply(num2), AMOUNT_DECIMAL_POINT); + } + + public static BigDecimal subtract(BigDecimal num1, BigDecimal num2) { + return setScale(num1.subtract(num2), AMOUNT_DECIMAL_POINT); + } + + public static BigDecimal divide(BigDecimal num1, BigDecimal num2) { + return setScale(num1.divide(num2, RoundingMode.HALF_UP), AMOUNT_DECIMAL_POINT); + } + } + + + /** + * BigDecimal 加法 num1 + num2 + * 未做非空校验 + * + * @param num1 + * @param num2 + * @param point 请使用BigDecimalUtils.PRICE_DECIMAL_POINT | BigDecimalUtils.WEIGHT_DECIMAL_POINT + * @return BigDecimal + */ + public static BigDecimal add(BigDecimal num1, BigDecimal num2, int point) { + return setScale(num1.add(num2), point); + } + + /** + * 累加 + * + * @param point + * @param array + * @return + */ + public static BigDecimal add(int point, BigDecimal... array) { + BigDecimal res = new BigDecimal(0); + for (BigDecimal item : array) { + if (item == null) { + res = res.add(BigDecimal.ZERO); + } else { + res = res.add(item); + } + } + return setScale(res, point); + } + + /** + * BigDecimal 乘法 num1 x num2 + * 未做非空校验 + * + * @param num1 + * @param num2 + * @param point 请使用BigDecimalUtils.PRICE_DECIMAL_POINT | BigDecimalUtils.WEIGHT_DECIMAL_POINT + * @return BigDecimal + */ + public static BigDecimal multiply(BigDecimal num1, BigDecimal num2, int point) { + return setScale(num1.multiply(num2), point); + } + + /** + * BigDecimal 减法 num1 - num2 + * 未做非空校验 + * + * @param num1 + * @param num2 + * @param point 请使用BigDecimalUtils.PRICE_DECIMAL_POINT | BigDecimalUtils.WEIGHT_DECIMAL_POINT + * @return BigDecimal + */ + public static BigDecimal subtract(BigDecimal num1, BigDecimal num2, int point) { + return setScale(num1.subtract(num2), point); + } + + /** + * BigDecimal 除法 num1/num2 + * 未做非空校验 + * + * @param num1 + * @param num2 + * @param point 请使用BigDecimalUtils.PRICE_DECIMAL_POINT | BigDecimalUtils.WEIGHT_DECIMAL_POINT + * @return BigDecimal + */ + public static BigDecimal divide(BigDecimal num1, BigDecimal num2, int point) { + return num1.divide(num2, point, RoundingMode.HALF_UP); + } + + /** + * 设置小数点类型为 四舍五入 + * + * @param num + * @param point + * @return BigDecimal + */ + public static BigDecimal setScale(BigDecimal num, int point) { + return num.setScale(point, RoundingMode.HALF_UP); + } + + /** + * 比较 num1 是否大于 num2 + * + * @param num1 + * @param num2 + * @return boolean + */ + public static boolean isGreaterThan(BigDecimal num1, BigDecimal num2) { + return num1.compareTo(num2) > 0; + } + + /** + * 比较 num1 是否大于等于 num2 + * + * @param num1 + * @param num2 + * @return boolean + */ + public static boolean isGreaterOrEqual(BigDecimal num1, BigDecimal num2) { + return isGreaterThan(num1, num2) || equals(num1, num2); + } + + /** + * 比较 num1 是否小于 num2 + * + * @param num1 + * @param num2 + * @return boolean + */ + public static boolean isLessThan(BigDecimal num1, BigDecimal num2) { + return num1.compareTo(num2) < 0; + } + + /** + * 比较 num1 是否小于等于 num2 + * + * @param num1 + * @param num2 + * @return boolean + */ + public static boolean isLessOrEqual(BigDecimal num1, BigDecimal num2) { + return isLessThan(num1, num2) || equals(num1, num2); + } + + /** + * 比较 num1 是否等于 num2 + * + * @param num1 + * @param num2 + * @return + */ + public static boolean equals(BigDecimal num1, BigDecimal num2) { + return num1.compareTo(num2) == 0; + } + + /** + * 计算 num1 / num2 的百分比 + * + * @param num1 + * @param num2 + * @param point 保留几位小数 + * @return String + */ + public static BigDecimal percent(Integer num1, Integer num2, int point) { + if (num1 == null || num2 == null) { + return BigDecimal.ZERO; + } + if (num2.equals(0)) { + return BigDecimal.ZERO; + } + return percent(new BigDecimal(num1), new BigDecimal(num2), point); + } + + /** + * 计算 num1 / num2 的百分比 + * + * @param num1 + * @param num2 + * @param point 保留几位小数 + * @return String + */ + public static BigDecimal percent(BigDecimal num1, BigDecimal num2, int point) { + if (num1 == null || num2 == null) { + return BigDecimal.ZERO; + } + if (equals(BigDecimal.ZERO, num2)) { + return BigDecimal.ZERO; + } + BigDecimal percent = num1.divide(num2, point + 2, RoundingMode.HALF_UP); + return percent.multiply(ONE_HUNDRED).setScale(point); + } + + /** + * 比较 num1,num2 返回最大的值 + * + * @param num1 + * @param num2 + * @return BigDecimal + */ + public static BigDecimal max(BigDecimal num1, BigDecimal num2) { + return num1.compareTo(num2) > 0 ? num1 : num2; + } + + /** + * 比较 num1,num2 返回最小的值 + * + * @param num1 + * @param num2 + * @return BigDecimal + */ + public static BigDecimal min(BigDecimal num1, BigDecimal num2) { + return num1.compareTo(num2) < 0 ? num1 : num2; + } + + public static void main(String[] args) { + System.out.println(percent(new BigDecimal("3"), new BigDecimal("11"), 2)); + System.out.println(percent(new BigDecimal("8"), new BigDecimal("11"), 2)); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartDateFormatterEnum.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartDateFormatterEnum.java new file mode 100644 index 0000000..c19c07b --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartDateFormatterEnum.java @@ -0,0 +1,79 @@ +package net.lab1024.sa.base.common.util; + + +import java.time.format.DateTimeFormatter; + +/** + * @Author 1024创新实验室: 胡克 + * @Date 2023/12/5 21:26:25 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * 1024创新实验室 ( https://1024lab.net ),2012-2023 + */ +public enum SmartDateFormatterEnum { + /** + * 日期格式 :年月日 yyyy-MM-dd + * 例:2021-10-15 + */ + YMD(DateTimeFormatter.ofPattern("yyyy-MM-dd")), + + /** + * 日期格式 :年月日 时分 yyyy-MM-dd HH:mm + * 例:2021-10-15 10:15 + */ + YMD_HM(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")), + + /** + * 日期格式 :年月日 时分秒 yyyy-MM-dd HH:mm:ss + * 例:2021-10-15 10:15:00 + */ + YMD_HMS(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), + + /** + * 日期格式 :年月 yyyy-MM + * 例:2021-10 + */ + YM(DateTimeFormatter.ofPattern("yyyy-MM")), + + /** + * 日期格式:年月 MM-dd + * 例:10-15 + */ + MD(DateTimeFormatter.ofPattern("MM-dd")), + + /** + * 日期格式:年月 MM月dd日 + * 例:10月15日 + */ + CHINESE_MD(DateTimeFormatter.ofPattern("MM月dd日")), + + /** + * 日期格式 : yyyyMMddHHmmss + * 例:20091225091010 + */ + YMDHMS(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")), + + /** + * 日期格式 :时分秒 HH:mm:ss + * 例:10:15:00 + */ + HMS(DateTimeFormatter.ofPattern("HH:mm:ss")), + + /** + * 日期格式 :时分 HH:mm + * 例:10:15 + */ + HM(DateTimeFormatter.ofPattern("HH:mm")) + + ; + + private final DateTimeFormatter formatter; + + SmartDateFormatterEnum(DateTimeFormatter formatter) { + this.formatter = formatter; + } + + public DateTimeFormatter getFormatter() { + return formatter; + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartEnumUtil.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartEnumUtil.java new file mode 100644 index 0000000..290c4ad --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartEnumUtil.java @@ -0,0 +1,165 @@ +package net.lab1024.sa.base.common.util; + +import net.lab1024.sa.base.common.enumeration.BaseEnum; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 枚举工具类 + * + * @Author 1024创新实验室: 胡克 + * @Date 2017/10/10 18:17 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class SmartEnumUtil { + + /** + * 校验参数与枚举类比较是否合法 + * + * @param value 参数 + * @param enumClass 枚举类必须实现BaseEnum接口 + * @return boolean + * @Author 胡克 + */ + public static boolean checkEnum(Object value, Class enumClass) { + if (null == value) { + return false; + } + return Stream.of(enumClass.getEnumConstants()).anyMatch(e -> e.equalsValue(value)); + } + + /** + * 创建一个具有唯一array值的数组,每个值不包含在其他给定的数组中。 + * + * @param enumClass + * @param exclude + * @param + * @return + */ + public static List differenceValueList(Class enumClass, T... exclude) { + HashSet valueSet = new HashSet<>(); + if (exclude != null) { + valueSet.addAll(Stream.of(exclude).map(BaseEnum::getValue).collect(Collectors.toSet())); + } + + return Stream.of(enumClass.getEnumConstants()) + .filter(e -> !valueSet.contains(e.getValue())) + .map(BaseEnum::getValue) + .collect(Collectors.toList()); + } + + /** + * 获取枚举类的说明 value : info 的形式 + * + * @param enumClass + * @return String + */ + public static String getEnumDesc(Class enumClass) { + BaseEnum[] enums = enumClass.getEnumConstants(); + // value : info 的形式 + StringBuilder sb = new StringBuilder(); + for (BaseEnum baseEnum : enums) { + sb.append(baseEnum.getValue()).append(":").append(baseEnum.getDesc()).append(","); + } + return sb.toString(); + } + + /** + * 获取与参数相匹配的枚举类实例的 说明 + * + * @param value 参数 + * @param enumClass 枚举类必须实现BaseEnum接口 + * @return String 如无匹配枚举则返回null + */ + public static String getEnumDescByValue(Object value, Class enumClass) { + if (null == value) { + return null; + } + return Stream.of(enumClass.getEnumConstants()) + .filter(e -> e.equalsValue(value)) + .findFirst() + .map(BaseEnum::getDesc) + .orElse(null); + } + + public static String getEnumDescByValueList(Collection values, Class enumClass) { + if (CollectionUtils.isEmpty(values)) { + return ""; + } + return Stream.of(enumClass.getEnumConstants()).filter(e -> values.contains(e.getValue())).map(BaseEnum::getDesc).collect(Collectors.joining(",")); + } + + /** + * 根据参数获取枚举类的实例 + * + * @param value 参数 + * @param enumClass 枚举类必须实现BaseEnum接口 + * @return BaseEnum 无匹配值返回null + * @Author 胡克 + */ + public static T getEnumByValue(Object value, Class enumClass) { + if (null == value) { + return null; + } + return Stream.of(enumClass.getEnumConstants()) + .filter(e -> e.equalsValue(value)) + .findFirst() + .orElse(null); + } + + /** + * 根据实例描述与获取枚举类的实例 + * + * @param desc 参数描述 + * @param enumClass 枚举类必须实现BaseEnum接口 + * @return BaseEnum 无匹配值返回null + * @Author 胡克 + */ + public static T getEnumByDesc(String desc, Class enumClass) { + return Stream.of(enumClass.getEnumConstants()) + .filter(e -> Objects.equals(e.getDesc(), desc)) + .findFirst() + .orElse(null); + } + + + public static T getEnumByName(String name, Class enumClass) { + return Stream.of(enumClass.getEnumConstants()) + .filter(e -> StringUtils.equalsIgnoreCase(e.toString(), name)) + .findFirst() + .orElse(null); + } + + + /** + * 根据lambda getter/setter 注入 + * + * @param list + * @param getter + * @param setter + * @param enumClass + * @param + */ + public static void inject(List list, Function getter, BiConsumer setter, Class enumClass) { + if (list == null || list.isEmpty()) { + return; + } + for (T t : list) { + Integer enumValue = getter.apply(t); + if (enumValue != null) { + setter.accept(t, getEnumDescByValue(enumValue, enumClass)); + } + } + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartExcelUtil.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartExcelUtil.java new file mode 100644 index 0000000..c917028 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartExcelUtil.java @@ -0,0 +1,226 @@ +package net.lab1024.sa.base.common.util; + +import cn.idev.excel.FastExcel; +import cn.idev.excel.write.handler.SheetWriteHandler; +import cn.idev.excel.write.metadata.holder.WriteSheetHolder; +import cn.idev.excel.write.metadata.holder.WriteWorkbookHolder; +import jakarta.servlet.http.HttpServletResponse; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.openxml4j.opc.PackagePartName; +import org.apache.poi.openxml4j.opc.PackageRelationship; +import org.apache.poi.openxml4j.opc.TargetMode; +import org.apache.poi.xssf.usermodel.XSSFPictureData; +import org.apache.poi.xssf.usermodel.XSSFRelation; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; + +import javax.imageio.ImageIO; +import javax.swing.*; +import java.awt.*; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Collection; + +/** + * + * excel 工具类 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2024/4/22 22:49:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),2012-2024 + */ +public final class SmartExcelUtil { + + /** + * 通用单sheet导出 + */ + public static void exportExcel(HttpServletResponse response, String fileName, String sheetName, Class head,Collection data) throws IOException { + // 设置下载消息头 + SmartResponseUtil.setDownloadFileHeader(response, fileName, null); + // 下载 + FastExcel.write(response.getOutputStream(), head) + .autoCloseStream(Boolean.FALSE) + .sheet(sheetName) + .doWrite(data); + } + + /** + * 通用单 sheet水印 导出 + */ + public static void exportExcelWithWatermark(HttpServletResponse response, String fileName, String sheetName, Class head,Collection data, String watermarkString) throws IOException { + // 设置下载消息头 + SmartResponseUtil.setDownloadFileHeader(response, fileName, null); + // 水印 + Watermark watermark = new Watermark(watermarkString); + // 一定要inMemory + FastExcel.write(response.getOutputStream(), head) + .inMemory(true) + .sheet(sheetName) + .registerWriteHandler(new CustomWaterMarkHandler(watermark)) + .doWrite(data); + } + + + @Slf4j + private static class CustomWaterMarkHandler implements SheetWriteHandler { + + private final Watermark watermark; + + public CustomWaterMarkHandler(Watermark watermark) { + this.watermark = watermark; + } + + @Override + public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) { + BufferedImage bufferedImage = createWatermarkImage(); + XSSFWorkbook workbook = (XSSFWorkbook) writeSheetHolder.getParentWriteWorkbookHolder().getWorkbook(); + try { + // 添加水印的具体操作 + addWatermarkToSheet(workbook, bufferedImage); + } catch (Exception e) { + log.error("添加水印出错:", e); + } + + } + + /** + * 创建水印图片 + * + * @return + */ + private BufferedImage createWatermarkImage() { + // 获取水印相关参数 + Font font = watermark.getFont(); + int width = watermark.getWidth(); + int height = watermark.getHeight(); + Color color = watermark.getColor(); + String text = watermark.getContent(); + + // 创建带有透明背景的 BufferedImage + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = image.createGraphics(); + + // 设置画笔字体、平滑、颜色 + g.setFont(font); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setColor(color); + + // 计算水印位置和角度 + int y = watermark.getYAxis(); + int x = watermark.getXAxis(); + AffineTransform transform = AffineTransform.getRotateInstance(Math.toRadians(-watermark.getAngle()), 0, y); + g.setTransform(transform); + // 绘制水印文字 + g.drawString(text, x, y); + + // 释放资源 + g.dispose(); + + return image; + } + + private void addWatermarkToSheet(XSSFWorkbook workbook, BufferedImage watermarkImage) { + try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { + ImageIO.write(watermarkImage, "png", os); + int pictureIdx = workbook.addPicture(os.toByteArray(), XSSFWorkbook.PICTURE_TYPE_PNG); + XSSFPictureData pictureData = workbook.getAllPictures().get(pictureIdx); + for (int i = 0; i < workbook.getNumberOfSheets(); i++) { + // 获取每个Sheet表 + XSSFSheet sheet = workbook.getSheetAt(i); + PackagePartName ppn = pictureData.getPackagePart().getPartName(); + String relType = XSSFRelation.IMAGES.getRelation(); + PackageRelationship pr = sheet.getPackagePart().addRelationship(ppn, TargetMode.INTERNAL, relType, null); + sheet.getCTWorksheet().addNewPicture().setId(pr.getId()); + } + } catch (Exception e) { + // 处理ImageIO.write可能抛出的异常 + log.error("添加水印图片时发生错误", e); + } + } + } + + @Data + private static class Watermark { + + public Watermark(String content) { + this.content = content; + init(); + } + + public Watermark(String content, Color color, Font font, double angle) { + this.content = content; + this.color = color; + this.font = font; + this.angle = angle; + init(); + } + + /** + * 根据水印内容长度自适应水印图片大小,简单的三角函数 + */ + private void init() { + FontMetrics fontMetrics = new JLabel().getFontMetrics(this.font); + int stringWidth = fontMetrics.stringWidth(this.content); + int charWidth = fontMetrics.charWidth('A'); + this.width = (int) Math.abs(stringWidth * Math.cos(Math.toRadians(this.angle))) + 5 * charWidth; + this.height = (int) Math.abs(stringWidth * Math.sin(Math.toRadians(this.angle))) + 5 * charWidth; + this.yAxis = this.height; + this.xAxis = charWidth; + } + + /** + * 水印内容 + */ + private String content; + + /** + * 画笔颜色 + */ + private Color color = new Color(239,239,239); + + /** + * 字体样式 + */ + private Font font = new Font("Microsoft YaHei", Font.BOLD, 26); + + /** + * 水印宽度 + */ + private int width; + + /** + * 水印高度 + */ + private int height; + + /** + * 倾斜角度,非弧度制 + */ + private double angle = 25; + + /** + * 字体的y轴位置 + */ + private int yAxis = 50; + + /** + * 字体的X轴位置 + */ + private int xAxis; + + /** + * 水平倾斜度 + */ + private double shearX = 0.1; + + /** + * 垂直倾斜度 + */ + private double shearY = -0.26; + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartIpUtil.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartIpUtil.java new file mode 100644 index 0000000..254cdb7 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartIpUtil.java @@ -0,0 +1,123 @@ +package net.lab1024.sa.base.common.util; + +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.constant.StringConst; +import org.lionsoul.ip2region.xdb.Searcher; + +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.List; + +/** + * IP工具类 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/9/14 15:35:11 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室,Since 2012 + */ +@Slf4j +public class SmartIpUtil { + + private static Searcher IP_SEARCHER; + + /** + * 初始化数据 + * + * @param filePath + */ + public static void init(String filePath) { + + try { + byte[] cBuff = Searcher.loadContentFromFile(filePath); + IP_SEARCHER = Searcher.newWithBuffer(cBuff); + + } catch (Throwable e) { + log.error("初始化ip2region.xdb文件失败,报错信息:[{}]", e.getMessage(), e); + throw new RuntimeException("系统异常!"); + } + } + + + /** + * 自定义解析ip地址 + * + * @param ipStr ipStr + * @return 返回结果例 [河南省, 洛阳市, 洛龙区] + */ + public static List getRegionList(String ipStr) { + List regionList = new ArrayList<>(); + try { + if (SmartStringUtil.isEmpty(ipStr)) { + return regionList; + } + ipStr = ipStr.trim(); + String region = IP_SEARCHER.search(ipStr); + String[] split = region.split("\\|"); + regionList.addAll(Arrays.asList(split)); + } catch (Exception e) { + log.error("解析ip地址出错", e); + } + return regionList; + } + + /** + * 自定义解析ip地址 + * + * @param ipStr ipStr + * @return 返回结果例 河南省|洛阳市|洛龙区 + */ + public static String getRegion(String ipStr) { + try { + if (SmartStringUtil.isEmpty(ipStr)) { + return StringConst.EMPTY; + } + ipStr = ipStr.trim(); + return IP_SEARCHER.search(ipStr); + } catch (Exception e) { + log.error("解析ip地址出错", e); + return StringConst.EMPTY; + } + } + + /** + * 获取本机第一个ip + * + * @return + */ + public static String getLocalFirstIp() { + List list = getLocalIp(); + return list.size() > 0 ? list.get(0) : null; + } + + /** + * 获取本机ip + * + * @return + */ + public static List getLocalIp() { + List ipList = new ArrayList<>(); + try { + Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); + while (networkInterfaces.hasMoreElements()) { + NetworkInterface networkInterface = networkInterfaces.nextElement(); + Enumeration inetAddresses = networkInterface.getInetAddresses(); + while (inetAddresses.hasMoreElements()) { + InetAddress inetAddress = inetAddresses.nextElement(); + // 排除回环地址和IPv6地址 + if (!inetAddress.isLoopbackAddress() && !inetAddress.getHostAddress().contains(StringConst.COLON)) { + ipList.add(inetAddress.getHostAddress()); + } + } + } + } catch (SocketException e) { + e.printStackTrace(); + } + return ipList; + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartLocalDateUtil.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartLocalDateUtil.java new file mode 100644 index 0000000..101c6ed --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartLocalDateUtil.java @@ -0,0 +1,124 @@ +package net.lab1024.sa.base.common.util; + +import java.time.*; +import java.time.format.TextStyle; +import java.time.temporal.ChronoUnit; +import java.util.Date; +import java.util.Locale; + + +/** + * @Author 1024创新实验室:胡克 + * @Date 2023/12/5 22:25:43 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * 1024创新实验室 ( https://1024lab.net ),2012-2023 + */ +public class SmartLocalDateUtil { + + + /** + * 格式化 LocalDateTime 返回对应格式字符串 + * + * @param time + * @param formatterEnum {@link SmartDateFormatterEnum} + * @return + */ + public static String format(LocalDateTime time, SmartDateFormatterEnum formatterEnum) { + return time.format(formatterEnum.getFormatter()); + } + + /** + * 格式化 LocalDate返回对应格式字符串 + * + * @param date + * @param formatterEnum {@link SmartDateFormatterEnum} + * @return + */ + public static String format(LocalDate date, SmartDateFormatterEnum formatterEnum) { + return date.format(formatterEnum.getFormatter()); + } + + /** + * 解析时间字符串 返回LocalDateTime + * + * @param time + * @param formatterEnum {@link SmartDateFormatterEnum} + * @return + */ + public static LocalDateTime parse(String time, SmartDateFormatterEnum formatterEnum) { + return LocalDateTime.parse(time, formatterEnum.getFormatter()); + } + + /** + * 解析时间字符串 返回 LocalDate + * + * @param time + * @param formatterEnum {@link SmartDateFormatterEnum} + * @return + */ + public static LocalDate parseDate(String time, SmartDateFormatterEnum formatterEnum) { + return LocalDate.parse(time, formatterEnum.getFormatter()); + } + + /** + * 获取指定日期时间戳 + * + * @param time + * @return + */ + public static Long getTimestamp(LocalDateTime time) { + return time.toInstant(ZoneOffset.ofHours(8)).toEpochMilli(); + } + + /** + * 获取当前时间戳(秒) + * + * @return + */ + public static long nowSecond() { + return System.currentTimeMillis() / 1000; + } + + /** + * 将时间格式化为 星期几,例:星期一 ... 星期日 + * + * @param localDate + * @return + */ + public static String formatToChineseWeek(LocalDate localDate) { + return localDate.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.CHINESE); + } + + /** + * 将时间格式化为 周几,例:周一 ... 周日 + * + * @param localDate + * @return + */ + public static String formatToChineseWeekZhou(LocalDate localDate) { + return formatToChineseWeek(localDate).replace("星期", "周"); + } + + public static LocalDateTime toLocalDateTime(Date date) { + return Instant.ofEpochMilli(date.getTime()).atZone(ZoneId.systemDefault()).toLocalDateTime(); + } + + /** + * 获取当天剩余时间 单位 + * + * @param unit 时间单位 + * @return + */ + public static Long getDayBalanceTime(ChronoUnit unit) { + LocalDateTime now = LocalDateTime.now(); + return Duration.between(now, now.plusDays(1L).with(LocalTime.MIN)).get(unit); + } + + public static void main(String[] args) { + System.out.println(SmartLocalDateUtil.format(LocalDateTime.now(), SmartDateFormatterEnum.YMD_HMS)); + System.out.println(SmartLocalDateUtil.format(LocalDateTime.now(), SmartDateFormatterEnum.YMD_HM)); + System.out.println(SmartLocalDateUtil.parse("2021-10-15 10:10:00", SmartDateFormatterEnum.YMD_HMS)); + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartPageUtil.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartPageUtil.java new file mode 100644 index 0000000..edd4768 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartPageUtil.java @@ -0,0 +1,122 @@ +package net.lab1024.sa.base.common.util; + +import com.baomidou.mybatisplus.core.metadata.OrderItem; +import com.baomidou.mybatisplus.core.toolkit.sql.SqlInjectionUtils; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.google.common.collect.Lists; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.domain.PageParam; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.exception.BusinessException; +import org.apache.commons.collections4.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * 分页工具类 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2020-04-23 20:51:40 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +public class SmartPageUtil { + + /** + * 转换为查询参数 + */ + public static Page convert2PageQuery(PageParam pageParam) { + Page page = new Page<>(pageParam.getPageNum(), pageParam.getPageSize()); + + if (pageParam.getSearchCount() != null) { + page.setSearchCount(pageParam.getSearchCount()); + } + + List sortItemList = pageParam.getSortItemList(); + if (CollectionUtils.isEmpty(sortItemList)) { + return page; + } + + // 设置排序字段并检测是否含有sql注入 + List orderItemList = new ArrayList<>(); + for (PageParam.SortItem sortItem : sortItemList) { + + if (SmartStringUtil.isEmpty(sortItem.getColumn())) { + continue; + } + + if (SqlInjectionUtils.check(sortItem.getColumn())) { + log.error("《存在SQL注入:》 : {}", sortItem.getColumn()); + throw new BusinessException("存在SQL注入风险,请联系技术工作人员!"); + } + + OrderItem orderItem = new OrderItem(); + orderItem.setColumn(sortItem.getColumn()); + orderItem.setAsc(sortItem.getIsAsc()); + orderItemList.add(orderItem); + } + page.setOrders(orderItemList); + return page; + } + + /** + * 转换为 PageResult 对象 + */ + public static PageResult convert2PageResult(Page page, List sourceList, Class targetClazz) { + return convert2PageResult(page, SmartBeanUtil.copyList(sourceList, targetClazz)); + } + + /** + * 转换为 PageResult 对象 + */ + public static PageResult convert2PageResult(Page page, List sourceList) { + PageResult pageResult = new PageResult<>(); + pageResult.setPageNum(page.getCurrent()); + pageResult.setPageSize(page.getSize()); + pageResult.setTotal(page.getTotal()); + pageResult.setPages(page.getPages()); + pageResult.setList(sourceList); + pageResult.setEmptyFlag(CollectionUtils.isEmpty(sourceList)); + return pageResult; + } + + /** + * 转换分页结果对象 + */ + public static PageResult convert2PageResult(PageResult pageResult, Class targetClazz) { + PageResult newPageResult = new PageResult<>(); + newPageResult.setPageNum(pageResult.getPageNum()); + newPageResult.setPageSize(pageResult.getPageSize()); + newPageResult.setTotal(pageResult.getTotal()); + newPageResult.setPages(pageResult.getPages()); + newPageResult.setEmptyFlag(pageResult.getEmptyFlag()); + newPageResult.setList(SmartBeanUtil.copyList(pageResult.getList(), targetClazz)); + return newPageResult; + } + + public static PageResult subListPage(Integer pageNum, Integer pageSize, List list) { + PageResult pageRet = new PageResult(); + //总条数 + int count = list.size(); + int pages = count % pageSize == 0 ? count / pageSize : (count / pageSize + 1); + int fromIndex = (pageNum - 1) * pageSize; + int toIndex = Math.min(pageNum * pageSize, count); + + if (pageNum > pages) { + pageRet.setList(Lists.newLinkedList()); + pageRet.setPageNum(pageNum.longValue()); + pageRet.setPages((long) pages); + pageRet.setTotal((long) count); + return pageRet; + } + List pageList = list.subList(fromIndex, toIndex); + pageRet.setList(pageList); + pageRet.setPageNum(pageNum.longValue()); + pageRet.setPages((long) pages); + pageRet.setTotal((long) count); + return pageRet; + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartRequestUtil.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartRequestUtil.java new file mode 100644 index 0000000..ab6e574 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartRequestUtil.java @@ -0,0 +1,39 @@ +package net.lab1024.sa.base.common.util; + +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.domain.RequestUser; + +/** + * 请求用户 工具类 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022-05-30 21:22:12 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +public class SmartRequestUtil { + + private static final ThreadLocal REQUEST_THREAD_LOCAL = new ThreadLocal<>(); + + public static void setRequestUser(RequestUser requestUser) { + REQUEST_THREAD_LOCAL.set(requestUser); + } + + public static RequestUser getRequestUser() { + return REQUEST_THREAD_LOCAL.get(); + } + + public static Long getRequestUserId() { + RequestUser requestUser = getRequestUser(); + return null == requestUser ? null : requestUser.getUserId(); + } + + + public static void remove() { + REQUEST_THREAD_LOCAL.remove(); + } + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartResponseUtil.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartResponseUtil.java new file mode 100644 index 0000000..2594fa6 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartResponseUtil.java @@ -0,0 +1,62 @@ +package net.lab1024.sa.base.common.util; + +import com.alibaba.fastjson.JSON; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.MediaTypeFactory; + +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +import static cn.hutool.core.util.CharsetUtil.UTF_8; + +/** + * 返回工具栏 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/11/25 18:51:32 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室,Since 2012 + */ + +@Slf4j +public class SmartResponseUtil { + + public static void write(HttpServletResponse response, ResponseDTO responseDTO) { + // 重置response + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setCharacterEncoding(UTF_8); + + try { + response.getWriter().write(JSON.toJSONString(responseDTO)); + response.flushBuffer(); + } catch (IOException ex) { + log.error(ex.getMessage(), ex); + throw new RuntimeException(ex); + } + } + + public static void setDownloadFileHeader(HttpServletResponse response, String fileName) { + setDownloadFileHeader(response, fileName, null); + } + + public static void setDownloadFileHeader(HttpServletResponse response, String fileName, Long fileSize) { + response.setCharacterEncoding(UTF_8); + if (fileSize != null) { + response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(fileSize)); + } + + if (SmartStringUtil.isNotEmpty(fileName)) { + response.setHeader(HttpHeaders.CONTENT_TYPE, MediaTypeFactory.getMediaType(fileName).orElse(MediaType.APPLICATION_OCTET_STREAM) + ";charset=utf-8"); + response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8).replaceAll("\\+", "%20")); + response.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.CONTENT_DISPOSITION); + } + } + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartStringUtil.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartStringUtil.java new file mode 100644 index 0000000..c89faef --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartStringUtil.java @@ -0,0 +1,442 @@ +package net.lab1024.sa.base.common.util; + + +import cn.hutool.core.util.StrUtil; + +import java.util.*; + +/** + * 独有的字符串工具类 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021-09-02 20:21:10 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class SmartStringUtil extends StrUtil { + + /** 空字符串 */ + private static final String NULLSTR = ""; + public static final String SEPARATOR = ","; + + public static final String SLASH = "/"; + + // ===============split ======================= + + public static Set splitConvertToSet(String str, String split) { + if (isEmpty(str)) { + return new HashSet(); + } + String[] splitArr = str.split(split); + HashSet set = new HashSet(splitArr.length); + Collections.addAll(set, splitArr); + return set; + } + + public static List splitConvertToList(String str, String split) { + if (isEmpty(str)) { + return new ArrayList(); + } + String[] splitArr = str.split(split); + ArrayList list = new ArrayList(splitArr.length); + list.addAll(Arrays.asList(splitArr)); + return list; + } + + // ===============split Integer======================= + + public static List splitConvertToIntList(String str, String split, int defaultVal) { + if (isEmpty(str)) { + return new ArrayList(); + } + String[] strArr = str.split(split); + List list = new ArrayList(strArr.length); + for (int i = 0; i < strArr.length; i++) { + try { + int parseInt = Integer.parseInt(strArr[i]); + list.add(parseInt); + } catch (NumberFormatException e) { + list.add(defaultVal); + continue; + } + } + return list; + } + + public static Set splitConvertToIntSet(String str, String split, int defaultVal) { + if (isEmpty(str)) { + return new HashSet(); + } + String[] strArr = str.split(split); + HashSet set = new HashSet(strArr.length); + for (int i = 0; i < strArr.length; i++) { + try { + int parseInt = Integer.parseInt(strArr[i]); + set.add(parseInt); + } catch (NumberFormatException e) { + set.add(defaultVal); + continue; + } + } + return set; + } + + public static Set splitConvertToIntSet(String str, String split) { + return splitConvertToIntSet(str, split, 0); + } + + public static List splitConvertToIntList(String str, String split) { + return splitConvertToIntList(str, split, 0); + } + + public static int[] splitConvertToIntArray(String str, String split, int defaultVal) { + if (isEmpty(str)) { + return new int[0]; + } + String[] strArr = str.split(split); + int[] result = new int[strArr.length]; + for (int i = 0; i < strArr.length; i++) { + try { + result[i] = Integer.parseInt(strArr[i]); + } catch (NumberFormatException e) { + result[i] = defaultVal; + continue; + } + } + return result; + } + + public static int[] splitConvertToIntArray(String str, String split) { + return splitConvertToIntArray(str, split, 0); + } + + // ===============split 2 Long======================= + + public static List splitConvertToLongList(String str, String split, long defaultVal) { + if (isEmpty(str)) { + return new ArrayList(); + } + String[] strArr = str.split(split); + List list = new ArrayList(strArr.length); + for (int i = 0; i < strArr.length; i++) { + try { + long parseLong = Long.parseLong(strArr[i]); + list.add(parseLong); + } catch (NumberFormatException e) { + list.add(defaultVal); + continue; + } + } + return list; + } + + public static List splitConvertToLongList(String str, String split) { + return splitConvertToLongList(str, split, 0L); + } + + public static long[] splitConvertToLongArray(String str, String split, long defaultVal) { + if (isEmpty(str)) { + return new long[0]; + } + String[] strArr = str.split(split); + long[] result = new long[strArr.length]; + for (int i = 0; i < strArr.length; i++) { + try { + result[i] = Long.parseLong(strArr[i]); + } catch (NumberFormatException e) { + result[i] = defaultVal; + continue; + } + } + return result; + } + + public static long[] splitConvertToLongArray(String str, String split) { + return splitConvertToLongArray(str, split, 0L); + } + + // ===============split convert byte======================= + + public static List splitConvertToByteList(String str, String split, byte defaultVal) { + if (isEmpty(str)) { + return new ArrayList(); + } + String[] strArr = str.split(split); + List list = new ArrayList(strArr.length); + for (int i = 0; i < strArr.length; i++) { + try { + byte parseByte = Byte.parseByte(strArr[i]); + list.add(parseByte); + } catch (NumberFormatException e) { + list.add(defaultVal); + continue; + } + } + return list; + } + + public static List splitConvertToByteList(String str, String split) { + return splitConvertToByteList(str, split, (byte) 0); + } + + public static byte[] splitConvertToByteArray(String str, String split, byte defaultVal) { + if (isEmpty(str)) { + return new byte[0]; + } + String[] strArr = str.split(split); + byte[] result = new byte[strArr.length]; + for (int i = 0; i < strArr.length; i++) { + try { + result[i] = Byte.parseByte(strArr[i]); + } catch (NumberFormatException e) { + result[i] = defaultVal; + continue; + } + } + return result; + } + + public static byte[] splitConvertToByteArray(String str, String split) { + return splitConvertToByteArray(str, split, (byte) 0); + } + + // ===============split convert double======================= + + public static List splitConvertToDoubleList(String str, String split, double defaultVal) { + if (isEmpty(str)) { + return new ArrayList(); + } + String[] strArr = str.split(split); + List list = new ArrayList(strArr.length); + for (int i = 0; i < strArr.length; i++) { + try { + double parseByte = Double.parseDouble(strArr[i]); + list.add(parseByte); + } catch (NumberFormatException e) { + list.add(defaultVal); + continue; + } + } + return list; + } + + public static List splitConvertToDoubleList(String str, String split) { + return splitConvertToDoubleList(str, split, 0); + } + + public static double[] splitConvertToDoubleArray(String str, String split, double defaultVal) { + if (isEmpty(str)) { + return new double[0]; + } + String[] strArr = str.split(split); + double[] result = new double[strArr.length]; + for (int i = 0; i < strArr.length; i++) { + try { + result[i] = Double.parseDouble(strArr[i]); + } catch (NumberFormatException e) { + result[i] = defaultVal; + continue; + } + } + return result; + } + + public static double[] splitConvertToDoubleArray(String str, String split) { + return splitConvertToDoubleArray(str, split, 0); + } + + // ===============split convert float======================= + + public static List splitConvertToFloatList(String str, String split, float defaultVal) { + if (isEmpty(str)) { + return new ArrayList(); + } + String[] strArr = str.split(split); + List list = new ArrayList(strArr.length); + for (int i = 0; i < strArr.length; i++) { + try { + float parseByte = Float.parseFloat(strArr[i]); + list.add(parseByte); + } catch (NumberFormatException e) { + list.add(defaultVal); + continue; + } + } + return list; + } + + public static List splitConvertToFloatList(String str, String split) { + return splitConvertToFloatList(str, split, 0f); + } + + public static float[] splitConvertToFloatArray(String str, String split, float defaultVal) { + if (isEmpty(str)) { + return new float[0]; + } + String[] strArr = str.split(split); + float[] result = new float[strArr.length]; + for (int i = 0; i < strArr.length; i++) { + try { + result[i] = Float.parseFloat(strArr[i]); + } catch (NumberFormatException e) { + result[i] = defaultVal; + continue; + } + } + return result; + } + + public static float[] splitConvertToFloatArray(String str, String split) { + return splitConvertToFloatArray(str, split, 0f); + } + + + public static String upperCaseFirstChar(String str) { + if (str != null && !str.isEmpty()) { + char firstChar = str.charAt(0); + if (Character.isUpperCase(firstChar)) { + return str; + } else { + char[] values = str.toCharArray(); + values[0] = Character.toUpperCase(firstChar); + return new String(values); + } + } else { + return str; + } + } + + public static String replace(String content, int begin, int end, String newStr) { + if (begin < content.length() && begin >= 0) { + if (end <= content.length() && end >= 0) { + if (begin > end) { + return content; + } else { + StringBuilder starStr = new StringBuilder(); + + for (int i = begin; i < end; ++i) { + starStr.append(newStr); + } + + return content.substring(0, begin) + starStr + content.substring(end); + } + } else { + return content; + } + } else { + return content; + } + } + + /** + * * 判断一个Collection是否为空, 包含List,Set,Queue + * + * @param coll 要判断的Collection + * @return true:为空 false:非空 + */ + public static boolean isEmpty(Collection coll) + { + return isNull(coll) || coll.isEmpty(); + } + + /** + * * 判断一个Collection是否非空,包含List,Set,Queue + * + * @param coll 要判断的Collection + * @return true:非空 false:空 + */ + public static boolean isNotEmpty(Collection coll) + { + return !isEmpty(coll); + } + + /** + * * 判断一个对象数组是否为空 + * + * @param objects 要判断的对象数组 + ** @return true:为空 false:非空 + */ + public static boolean isEmpty(Object[] objects) + { + return isNull(objects) || (objects.length == 0); + } + + /** + * * 判断一个对象数组是否非空 + * + * @param objects 要判断的对象数组 + * @return true:非空 false:空 + */ + public static boolean isNotEmpty(Object[] objects) + { + return !isEmpty(objects); + } + + /** + * * 判断一个Map是否为空 + * + * @param map 要判断的Map + * @return true:为空 false:非空 + */ + public static boolean isEmpty(Map map) + { + return isNull(map) || map.isEmpty(); + } + + /** + * * 判断一个Map是否为空 + * + * @param map 要判断的Map + * @return true:非空 false:空 + */ + public static boolean isNotEmpty(Map map) + { + return !isEmpty(map); + } + + /** + * * 判断一个字符串是否为空串 + * + * @param str String + * @return true:为空 false:非空 + */ + public static boolean isEmpty(String str) + { + return isNull(str) || NULLSTR.equals(str.trim()); + } + + /** + * * 判断一个字符串是否为非空串 + * + * @param str String + * @return true:非空串 false:空串 + */ + public static boolean isNotEmpty(String str) + { + return !isEmpty(str); + } + + /** + * * 判断一个对象是否为空 + * + * @param object Object + * @return true:为空 false:非空 + */ + public static boolean isNull(Object object) + { + return object == null; + } + + /** + * * 判断一个对象是否非空 + * + * @param object Object + * @return true:非空 false:空 + */ + public static boolean isNotNull(Object object) + { + return !isNull(object); + } + +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartTableMetadataUtil.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartTableMetadataUtil.java new file mode 100644 index 0000000..3fe6904 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartTableMetadataUtil.java @@ -0,0 +1,215 @@ +package net.lab1024.sa.base.common.util; + +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import com.baomidou.mybatisplus.core.metadata.TableFieldInfo; +import com.baomidou.mybatisplus.core.metadata.TableInfo; +import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Component; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.util.HashMap; +import java.util.Map; + +@Component +public class SmartTableMetadataUtil { + + @Resource + private DataSource dataSource; // 注入的数据源 + + private static DataSource staticDataSource; // 静态变量保存数据源 + + @PostConstruct + public void init() { + staticDataSource = this.dataSource; + } + + + /** + * 根据实体类获取表元数据 + * @param entityClass 实体类Class对象 + * @return JSON格式的表元数据 + */ + public static JSONObject getTableMetadata(Class entityClass) throws Exception { + TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass); + if (tableInfo == null) { + throw new RuntimeException("实体类未配置MyBatis-Plus注解"); + } + return getTableMetadataByTableInfo(tableInfo); + } + + /** + * 根据表名获取表元数据 + * @param tableName 数据库表名 + * @return JSON格式的表元数据 + */ + public static JSONObject getTableMetadata(String tableName) throws Exception { + if (staticDataSource == null) { + throw new RuntimeException("数据源未初始化"); + } + return getTableMetadataByTableName(tableName); + } + + /** + * 根据TableInfo获取表元数据(内部方法) + * @param tableInfo MyBatis-Plus表信息 + * @return JSON格式的表元数据 + */ + private static JSONObject getTableMetadataByTableInfo(TableInfo tableInfo) throws Exception { + String tableName = tableInfo.getTableName(); + + // 1. 获取表注释 + String tableComment = getTableCommentFromDB(tableName); + JSONObject tableJson = new JSONObject(); + tableJson.set("englishName", tableName); + tableJson.set("chineseName", tableComment); + + // 2. 获取字段注释 + Map fieldComments = getFieldCommentsFromDB(tableName); + JSONArray columns = new JSONArray(); + for (TableFieldInfo field : tableInfo.getFieldList()) { + JSONObject column = new JSONObject(); + String columnName = field.getColumn(); + column.set("englishName", columnName); + column.set("chineseName", fieldComments.getOrDefault(columnName, "无注释")); + column.set("javaType", field.getPropertyType().getSimpleName()); + columns.add(column); + } + + // 3. 构建结果 + JSONObject result = new JSONObject(); + result.set("table", tableJson); + result.set("columns", columns); + return result; + } + + /** + * 根据表名获取表元数据(内部方法) + * @param tableName 数据库表名 + * @return JSON格式的表元数据 + */ + private static JSONObject getTableMetadataByTableName(String tableName) throws Exception { + // 1. 获取表注释 + String tableComment = getTableCommentFromDB(tableName); + JSONObject tableJson = new JSONObject(); + tableJson.set("englishName", tableName); + tableJson.set("chineseName", tableComment); + + // 2. 获取字段信息和注释 + Map fieldComments = getFieldCommentsFromDB(tableName); + Map fieldTypes = getFieldTypesFromDB(tableName); + + JSONArray columns = new JSONArray(); + for (Map.Entry entry : fieldComments.entrySet()) { + String columnName = entry.getKey(); + String comment = entry.getValue(); + String sqlType = fieldTypes.getOrDefault(columnName, "VARCHAR"); + + JSONObject column = new JSONObject(); + column.set("englishName", columnName); + column.set("chineseName", comment); + column.set("sqlType", sqlType); + column.set("javaType", sqlTypeToJavaType(sqlType)); + columns.add(column); + } + + // 3. 构建结果 + JSONObject result = new JSONObject(); + result.set("table", tableJson); + result.set("columns", columns); + return result; + } + + // 从数据库获取表注释(基于JDBC元数据) + private static String getTableCommentFromDB(String tableName) throws Exception { + try (Connection conn = staticDataSource.getConnection()) { + DatabaseMetaData metaData = conn.getMetaData(); + try (ResultSet rs = metaData.getTables(null, null, tableName, new String[]{"TABLE"})) { + if (rs.next()) return rs.getString("REMARKS"); + } + } + return tableName; + } + + // 批量获取字段注释 + private static Map getFieldCommentsFromDB(String tableName) throws Exception { + Map comments = new HashMap<>(); + try (Connection conn = staticDataSource.getConnection()) { + DatabaseMetaData metaData = conn.getMetaData(); + try (ResultSet rs = metaData.getColumns(null, null, tableName, null)) { + while (rs.next()) { + String columnName = rs.getString("COLUMN_NAME"); + String remark = rs.getString("REMARKS"); + comments.put(columnName, remark != null ? remark : "无注释"); + } + } + } + return comments; + } + + // 批量获取字段类型 + private static Map getFieldTypesFromDB(String tableName) throws Exception { + Map types = new HashMap<>(); + try (Connection conn = staticDataSource.getConnection()) { + DatabaseMetaData metaData = conn.getMetaData(); + try (ResultSet rs = metaData.getColumns(null, null, tableName, null)) { + while (rs.next()) { + String columnName = rs.getString("COLUMN_NAME"); + String typeName = rs.getString("TYPE_NAME"); + int columnSize = rs.getInt("COLUMN_SIZE"); + int decimalDigits = rs.getInt("DECIMAL_DIGITS"); + + // 构建完整的类型信息 + String fullType = typeName; + if (columnSize > 0) { + if (decimalDigits > 0) { + fullType = typeName + "(" + columnSize + "," + decimalDigits + ")"; + } else { + fullType = typeName + "(" + columnSize + ")"; + } + } + types.put(columnName, fullType); + } + } + } + return types; + } + + // SQL类型转Java类型 + private static String sqlTypeToJavaType(String sqlType) { + if (sqlType == null) return "String"; + + String upperType = sqlType.toUpperCase(); + if (upperType.startsWith("VARCHAR") || upperType.startsWith("CHAR") || + upperType.startsWith("TEXT") || upperType.startsWith("LONGTEXT")) { + return "String"; + } else if (upperType.startsWith("INT") || upperType.startsWith("TINYINT") || + upperType.startsWith("SMALLINT") || upperType.startsWith("MEDIUMINT")) { + return "Integer"; + } else if (upperType.startsWith("BIGINT")) { + return "Long"; + } else if (upperType.startsWith("DECIMAL") || upperType.startsWith("NUMERIC")) { + return "BigDecimal"; + } else if (upperType.startsWith("FLOAT")) { + return "Float"; + } else if (upperType.startsWith("DOUBLE")) { + return "Double"; + } else if (upperType.startsWith("DATE")) { + return "Date"; + } else if (upperType.startsWith("DATETIME") || upperType.startsWith("TIMESTAMP")) { + return "Date"; + } else if (upperType.startsWith("TIME")) { + return "Time"; + } else if (upperType.startsWith("BOOLEAN") || upperType.startsWith("BIT")) { + return "Boolean"; + } else { + return "String"; + } + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartVerificationUtil.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartVerificationUtil.java new file mode 100644 index 0000000..aec6a8e --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartVerificationUtil.java @@ -0,0 +1,98 @@ +package net.lab1024.sa.base.common.util; + +import java.util.regex.Pattern; + +/** + * 验证工具类 + * + * @Author 1024创新实验室: 胡克 + * @Date 2017/11/06 10:54 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class SmartVerificationUtil { + + /** + * 手机号码验证规则 + */ + public static final String PHONE_REGEXP = "^1[0-9]{10}"; + + /** + * 固定号码验证规则 + */ + public static final String FIXED_PHONE_REGEXP = "^0\\d{2,3}-[1-9]\\d{6,7}$"; + + /** + * 密码正则校验 + */ + public static final String PWD_REGEXP = "^[A-Za-z0-9.]{6,15}$"; + + /** + * 车牌号 + */ + public static final String CAR_NUMBER = + "([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼]{1}(([A-HJ-Z]{1}[A-HJ-NP-Z0-9]{5})|([A-HJ-Z]{1}(([DF]{1}[A-HJ-NP-Z0-9]{1}[0-9]{4})|([0-9]{5}[DF]{1})))|" + "([A-HJ-Z" + "]{1}[A-D0-9]{1}[0-9]{3}警)))|" + + "([0-9]{6}使)|((([沪粤川云桂鄂陕蒙藏黑辽渝]{1}A)|鲁B|闽D|蒙E|蒙H)[0-9]{4}领)|(WJ[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼·•]{1}[0-9]{4}[TDSHBXJ0-9]{1})|" + "([VKHBSLJNGCE]{1}[A-DJ-PR" + "-TVY]{1}[0-9]{5})"; + + /** + * 日期年月日校验 yyyy-MM-dd HH:mm:ss + */ + public static final String DATE_TIME = "^((([0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]{1}|[0-9]{1}[1-9][0-9]{2}|[1-9][0-9]{3})-(((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01]))|((0[469]|11)-(0[1-9" + + "]|[12][0-9]|30))|(02-(0[1-9]|[1][0-9]|2[0-8]))))|((([0-9]{2})(0[48]|[2468][048]|[13579][26])|((0[48]|[2468][048]|[3579][26])00))-02-29))\\s+([0-1]?[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$"; + + /** + * 日期校验 yyyy-MM-dd + */ + public static final String DATE = "(([0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]{1}|[0-9]{1}[1-9][0-9]{2}|[1-9][0-9]{3})-(((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01]))|((0[469]|11)-(0[1-9]|[12][0-9]|30))" + + "|(02-(0[1-9]|[1][0-9]|2[0-8])))|((([0-9]{2})(0[48]|[2468][048]|[13579][26])|((0[48]|[2468][048]|[3579][26])00))-02-29)" + "([0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]{1}|[0-9]{1}[1-9][0-9]{2}|[1-9" + + "][0-9]{3})-(((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01]))|((0[469]|11)-(0[1-9]|[12][0-9]|30))|(02-(0[1-9]|[1][0-9]|2[0-8]))))|(" + "(([0-9]{2})(0[48]|[2468][048]|[13579][26])|(" + "(0[48" + "]|[2468][048]|[3579][26])00))-02-29)"; + + public static final String DATE_TIME_HM = "^((([0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]{1}|[0-9]{1}[1-9][0-9]{2}|[1-9][0-9]{3})-(((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01]))|((0[469]|11)-(0[1-9]|[12][0-9]|30))|(02-(0[1-9]|[1][0-9]|2[0-8]))))|((([0-9]{2})(0[48]|[2468][048]|[13579][26])|((0[48]|[2468][048]|[3579][26])00))-02-29))\\s+([0-1]?[0-9]|2[0-3]):([0-5][0-9])$"; + + /** + * 年月校验 例: 2019-10 + */ + public static final String YEAR_MONTH = "^\\d{4}-((0([1-9]))|(1(0|1|2)))$"; + + /** + * 时间区间验证 10:23-19:00 + */ + public static final String TIME_SECTION = "^(0[0-9]|1[0-9]|2[0-3]):(0[0-9]|[1-5][0-9])-(0[0-9]|1[0-9]|2[0-3]):(0[0-9]|[1-5][0-9])$"; + + /** + * 时间验证 10:23 + */ + public static final String TIME = "^(0[0-9]|1[0-9]|2[0-3]):(0[0-9]|[1-5][0-9])$"; + + /** + * 身份证号 + */ + public static final String ID_CARD = "(^\\d{15}$)|(^\\d{18}$)|(^\\d{17}(\\d|X|x)$)"; + + /** + * URL + */ + public static final String URL = "[a-zA-z]+://[^\\s]*"; + + /** + * 邮箱 + */ + public static final String EMAIL = "[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[\\w](?:[\\w-]*[\\w])?"; + + /** + * 整数 + */ + public static final String INTEGER = "^-?[1-9]\\d*$"; + + /** + * 小数 + */ + public static final String DOUBLE = "^-?[1-9]\\d*\\.\\d*|0\\.\\d*[1-9]\\d*$"; + + + public static void main(String[] args) { + boolean matches = Pattern.matches(INTEGER, "1"); + System.out.println(matches); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SpringUtils.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SpringUtils.java new file mode 100644 index 0000000..cbc0fb7 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/util/SpringUtils.java @@ -0,0 +1,174 @@ +package net.lab1024.sa.base.common.util; + +import org.springframework.aop.framework.AopContext; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +import java.util.Map; + +/** + * spring工具类 方便在非spring管理环境中获取bean + * + * @author ruoyi + */ +@Component +public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware +{ + /** Spring应用上下文环境 */ + private static ConfigurableListableBeanFactory beanFactory; + + private static ApplicationContext applicationContext; + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException + { + SpringUtils.beanFactory = beanFactory; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException + { + SpringUtils.applicationContext = applicationContext; + } + + public static ApplicationContext getApplicationContext() { + return applicationContext; + } + + /** + * 获取对象 + * + * @param name + * @return Object 一个以所给名字注册的bean的实例 + * @throws BeansException + * + */ + @SuppressWarnings("unchecked") + public static T getBean(String name) throws BeansException + { + return (T) beanFactory.getBean(name); + } + + /** + * 获取类型为requiredType的对象 + * + * @param clz + * @return + * @throws BeansException + * + */ + public static T getBean(Class clz) throws BeansException + { + T result = (T) beanFactory.getBean(clz); + return result; + } + + /** + * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true + * + * @param name + * @return boolean + */ + public static boolean containsBean(String name) + { + return beanFactory.containsBean(name); + } + + /** + * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException) + * + * @param name + * @return boolean + * @throws NoSuchBeanDefinitionException + * + */ + public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException + { + return beanFactory.isSingleton(name); + } + + /** + * @param name + * @return Class 注册对象的类型 + * @throws NoSuchBeanDefinitionException + * + */ + public static Class getType(String name) throws NoSuchBeanDefinitionException + { + return beanFactory.getType(name); + } + + /** + * 如果给定的bean名字在bean定义中有别名,则返回这些别名 + * + * @param name + * @return + * @throws NoSuchBeanDefinitionException + * + */ + public static String[] getAliases(String name) throws NoSuchBeanDefinitionException + { + return beanFactory.getAliases(name); + } + + /** + * 获取aop代理对象 + * + * @param invoker + * @return + */ + @SuppressWarnings("unchecked") + public static T getAopProxy(T invoker) + { + return (T) AopContext.currentProxy(); + } + + /** + * 获取当前的环境配置,无配置返回null + * + * @return 当前的环境配置 + */ + public static String[] getActiveProfiles() + { + return applicationContext.getEnvironment().getActiveProfiles(); + } + + /** + * 获取当前的环境配置,当有多个环境配置时,只获取第一个 + * + * @return 当前的环境配置 + */ + public static String getActiveProfile() + { + final String[] activeProfiles = getActiveProfiles(); + return SmartStringUtil.isNotEmpty(activeProfiles) ? activeProfiles[0] : null; + } + + /** + * 获取配置文件中的值 + * + * @param key 配置文件的key + * @return 当前的配置文件的值 + * + */ + public static String getRequiredProperty(String key) + { + return applicationContext.getEnvironment().getRequiredProperty(key); + } + + /** + * 获取clazz类型所有的实现类,key为类名 + * + * @param clazz 类类型 + * @param T + * @return 所有实现类 + */ + public static Map getBeanNamesForType(Class clazz) { + return beanFactory.getBeansOfType(clazz); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/validator/enumeration/CheckEnum.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/validator/enumeration/CheckEnum.java new file mode 100644 index 0000000..b578b12 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/validator/enumeration/CheckEnum.java @@ -0,0 +1,51 @@ +package net.lab1024.sa.base.common.validator.enumeration; + + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 自定义的属性校验注解,为了方便与校验属性的值是否为合法的枚举值 + * + * @Author 1024创新实验室: 胡克 + * @Date 2017/11/11 15:31 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = EnumValidator.class)// 自定义验证的处理类 +public @interface CheckEnum { + + /** + * 默认的错误提示信息 + * + * @return String + */ + String message(); + + /** + * 枚举类对象 必须实现BaseEnum接口 + * + */ + Class value(); + + /** + * 是否必须 + * + * @return boolean + */ + boolean required() default false; + + //下面这两个属性必须添加 :不然会报错 + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/validator/enumeration/EnumValidator.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/validator/enumeration/EnumValidator.java new file mode 100644 index 0000000..c6e1fe2 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/common/validator/enumeration/EnumValidator.java @@ -0,0 +1,73 @@ +package net.lab1024.sa.base.common.validator.enumeration; + + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 枚举类校验器 + * + * @Author 1024创新实验室: 胡克 + * @Date 2017/11/11 15:34 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class EnumValidator implements ConstraintValidator { + + /** + * 枚举类实例集合 + */ + private List enumValList; + + /** + * 是否必须 + */ + private boolean required; + + @Override + public void initialize(CheckEnum constraintAnnotation) { + // 获取注解传入的枚举类对象 + required = constraintAnnotation.required(); + Class enumClass = constraintAnnotation.value(); + enumValList = Stream.of(enumClass.getEnumConstants()).map(BaseEnum::getValue).collect(Collectors.toList()); + } + + @Override + public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) { + // 判断是否必须 + if (null == value) { + return !required; + } + + if (value instanceof List) { + // 如果为 List 集合数据 + return this.checkList((List) value); + } + + // 校验是否为合法的枚举值 + return enumValList.contains(value); + } + + /** + * 校验集合类型 + * + */ + private boolean checkList(List list) { + if (required && list.isEmpty()) { + // 必须的情况下 list 不能为空 + return false; + } + // 校验是否重复 + long count = list.stream().distinct().count(); + if (count != list.size()) { + return false; + } + return enumValList.containsAll(list); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/AsyncConfig.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/AsyncConfig.java new file mode 100644 index 0000000..4a14ab4 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/AsyncConfig.java @@ -0,0 +1,71 @@ +package net.lab1024.sa.base.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.AsyncTaskExecutor; +import org.springframework.scheduling.annotation.AsyncConfigurer; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.lang.reflect.Method; +import java.util.Arrays; + +/** + * 异步调用线程配置 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021-09-02 20:21:10 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@Configuration +public class AsyncConfig { + + /** + * 线程池 配置bean名称 + */ + public static final String ASYNC_EXECUTOR_THREAD_NAME = "smart-async-executor"; + + /** + * 配置线程池 + * + * @return + */ + @Bean(name = ASYNC_EXECUTOR_THREAD_NAME) + public AsyncTaskExecutor executor() { + int processors = Runtime.getRuntime().availableProcessors(); + int threadCount = Math.max(1, processors - 1); + ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); + // 核心线程数量 + taskExecutor.setCorePoolSize(threadCount); + // 最大线程数量 + taskExecutor.setMaxPoolSize(threadCount); + taskExecutor.setThreadNamePrefix(ASYNC_EXECUTOR_THREAD_NAME); + taskExecutor.initialize(); + return taskExecutor; + } + + /** + * spring 异步任务 异常配置 + */ + @Configuration + public static class AsyncExceptionConfig implements AsyncConfigurer { + @Override + public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { + return new AsyncExceptionHandler(); + } + } + + /** + * 自定义异常处理 + */ + public static class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler { + @Override + public void handleUncaughtException(Throwable throwable, Method method, Object... objects) { + log.error("异步任务发生异常:{}, 参数:{}, ", method.getDeclaringClass().getSimpleName() + "." + method.getName(), Arrays.toString(objects), throwable); + } + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/CacheConfig.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/CacheConfig.java new file mode 100644 index 0000000..bf6de16 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/CacheConfig.java @@ -0,0 +1,35 @@ +package net.lab1024.sa.base.config; + +import net.lab1024.sa.base.module.support.cache.CacheService; +import net.lab1024.sa.base.module.support.cache.CaffeineCacheServiceImpl; +import net.lab1024.sa.base.module.support.cache.RedisCacheServiceImpl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 缓存配置 + * + * @author zhoumingfa + * @date 2025/03/28 + */ +@Configuration +public class CacheConfig { + + private static final String REDIS_CACHE = "redis"; + private static final String CAFFEINE_CACHE = "caffeine"; + + + @Bean + @ConditionalOnProperty(prefix = "spring.cache", name = {"type"}, havingValue = REDIS_CACHE) + public CacheService redisCacheService() { + return new RedisCacheServiceImpl(); + } + + @Bean + @ConditionalOnProperty(prefix = "spring.cache", name = {"type"}, havingValue = CAFFEINE_CACHE) + public CacheService caffeineCacheService() { + return new CaffeineCacheServiceImpl(); + } + +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/CorsFilterConfig.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/CorsFilterConfig.java new file mode 100644 index 0000000..7ccccff --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/CorsFilterConfig.java @@ -0,0 +1,45 @@ +package net.lab1024.sa.base.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; + +/** + * 跨域配置 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2021/11/15 20:38 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Configuration +@Conditional(SystemEnvironmentConfig.class) +public class CorsFilterConfig { + + @Value("${access-control-allow-origin}") + private String accessControlAllowOrigin; + + /** + * 跨域配置 + */ + @Bean + public CorsFilter corsFilter () { + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + // 设置访问源地址 + config.addAllowedOriginPattern(accessControlAllowOrigin); + // 设置访问源请求头 + config.addAllowedHeader("*"); + // 设置访问源请求方法 + config.addAllowedMethod("*"); + // 对接口配置跨域设置 + source.registerCorsConfiguration("/**", config); + return new CorsFilter(source); + } +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/DataSourceConfig.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/DataSourceConfig.java new file mode 100644 index 0000000..71dd342 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/DataSourceConfig.java @@ -0,0 +1,214 @@ +package net.lab1024.sa.base.config; + +import com.alibaba.druid.filter.Filter; +import com.alibaba.druid.filter.stat.StatFilter; +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.druid.support.jakarta.StatViewServlet; +import com.alibaba.druid.support.jakarta.WebStatFilter; +import com.alibaba.druid.support.spring.stat.DruidStatInterceptor; +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.core.config.GlobalConfig; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.domain.DataScopePlugin; +import net.lab1024.sa.base.handler.MybatisPlusFillHandler; +import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.session.SqlSessionFactory; +import org.springframework.aop.support.DefaultPointcutAdvisor; +import org.springframework.aop.support.JdkRegexpMethodPointcut; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.boot.web.servlet.ServletRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; + +import javax.sql.DataSource; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 数据源配置 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2017-11-28 15:21:10 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@Configuration +public class DataSourceConfig { + + @Value("${spring.datasource.driver-class-name}") + String driver; + + @Value("${spring.datasource.url}") + String url; + + @Value("${spring.datasource.username}") + String username; + + @Value("${spring.datasource.password}") + String password; + + @Value("${spring.datasource.initial-size}") + int initialSize; + + @Value("${spring.datasource.min-idle}") + int minIdle; + + @Value("${spring.datasource.max-active}") + int maxActive; + + @Value("${spring.datasource.max-wait}") + long maxWait; + + @Value("${spring.datasource.time-between-eviction-runs-millis}") + long timeBetweenEvictionRunsMillis; + + @Value("${spring.datasource.min-evictable-idle-time-millis}") + long minEvictableIdleTimeMillis; + + @Value("${spring.datasource.filters}") + String filters; + + @Value("${spring.datasource.druid.username}") + String druidUserName; + + @Value("${spring.datasource.druid.password}") + String druidPassword; + + @Value("${spring.datasource.druid.login.enabled}") + boolean druidLoginEnable; + + @Value("${spring.datasource.druid.method.pointcut}") + String methodPointcut; + + @jakarta.annotation.Resource + private MybatisPlusInterceptor paginationInterceptor; + + @jakarta.annotation.Resource + private DataScopePlugin dataScopePlugin; + + @Bean + @Primary + public DataSource druidDataSource() { + DruidDataSource druidDataSource = new DruidDataSource(); + // 根据驱动类名自动设置对应的数据库类型 + if (driver.contains("kingbase")) { + druidDataSource.setDbType(DbType.KINGBASE_ES.getDb()); + } else if (driver.contains("mysql")) { + druidDataSource.setDbType(DbType.MYSQL.getDb()); + } else if (driver.contains("oracle")) { + druidDataSource.setDbType(DbType.ORACLE.getDb()); + } else if (driver.contains("postgresql")) { + druidDataSource.setDbType(DbType.POSTGRE_SQL.getDb()); + } else if (driver.contains("sqlserver")) { + druidDataSource.setDbType(DbType.SQL_SERVER.getDb()); + } else { + druidDataSource.setDbType(DbType.MYSQL.getDb()); + } + druidDataSource.setDriverClassName(driver); + druidDataSource.setUrl(url); + druidDataSource.setUsername(username); + druidDataSource.setPassword(password); + druidDataSource.setInitialSize(initialSize); + druidDataSource.setMinIdle(minIdle); + druidDataSource.setMaxActive(maxActive); + druidDataSource.setMaxWait(maxWait); + druidDataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); + druidDataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); + druidDataSource.setValidationQuery("SELECT 1"); + try { + druidDataSource.setFilters(filters); + ArrayList arrayList = new ArrayList<>(); + StatFilter statFilter = new StatFilter(); + statFilter.setMergeSql(true); + statFilter.setSlowSqlMillis(1000); + statFilter.setLogSlowSql(true); + arrayList.add(statFilter); + druidDataSource.setProxyFilters(arrayList); + druidDataSource.init(); + } catch (SQLException e) { + log.error("初始化数据源出错", e); + } + + return druidDataSource; + } + + @Bean + public SqlSessionFactory sqlSessionFactory() throws Exception { + MybatisSqlSessionFactoryBean factoryBean = new MybatisSqlSessionFactoryBean(); + factoryBean.setDataSource(druidDataSource()); + PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + Resource[] resources = resolver.getResources("classpath*:/mapper/**/*.xml"); + factoryBean.setMapperLocations(resources); + + // 设置 MyBatis-Plus 分页插件 注意此处myBatisPlugin一定要放在后面 + List pluginsList = new ArrayList<>(); + pluginsList.add(paginationInterceptor); + if (dataScopePlugin != null) { + pluginsList.add(dataScopePlugin); + } + factoryBean.setPlugins(pluginsList.toArray(new Interceptor[0])); + // 添加字段自动填充处理 + factoryBean.setGlobalConfig(new GlobalConfig().setBanner(false).setMetaObjectHandler(new MybatisPlusFillHandler())); + + return factoryBean.getObject(); + } + + /** + * 非正式环境 才加载 + * + * @return + */ + @Conditional(SystemEnvironmentConfig.class) + @Bean + public ServletRegistrationBean druidServlet() { + ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean<>(); + servletRegistrationBean.setServlet(new StatViewServlet()); + servletRegistrationBean.addUrlMappings("/druid/*"); + Map initParameters = new HashMap(); + //不设置用户名密码可以直接通过druid/index.html访问 + if (druidLoginEnable) { + initParameters.put("loginUsername", druidUserName); + initParameters.put("loginPassword", druidPassword); + } + initParameters.put("resetEnable", "false"); + servletRegistrationBean.setInitParameters(initParameters); + return servletRegistrationBean; + } + + @Bean + public FilterRegistrationBean filterRegistrationBean() { + FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); + filterRegistrationBean.setFilter(new WebStatFilter()); + filterRegistrationBean.addUrlPatterns("/*"); + filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/*"); + return filterRegistrationBean; + } + + @Bean + public JdkRegexpMethodPointcut jdkRegexpMethodPointcut() { + JdkRegexpMethodPointcut jdkRegexpMethodPointcut = new JdkRegexpMethodPointcut(); + jdkRegexpMethodPointcut.setPatterns(methodPointcut); + return jdkRegexpMethodPointcut; + } + + @Bean + public DefaultPointcutAdvisor defaultPointcutAdvisor() { + DefaultPointcutAdvisor pointcutAdvisor = new DefaultPointcutAdvisor(); + pointcutAdvisor.setPointcut(jdkRegexpMethodPointcut()); + pointcutAdvisor.setAdvice(new DruidStatInterceptor()); + return pointcutAdvisor; + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/FileConfig.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/FileConfig.java new file mode 100644 index 0000000..a929cca --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/FileConfig.java @@ -0,0 +1,104 @@ +package net.lab1024.sa.base.config; + +import com.amazonaws.ClientConfiguration; +import com.amazonaws.Protocol; +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.client.builder.AwsClientBuilder; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import lombok.Data; +import net.lab1024.sa.base.module.support.file.service.FileStorageCloudServiceImpl; +import net.lab1024.sa.base.module.support.file.service.FileStorageLocalServiceImpl; +import net.lab1024.sa.base.module.support.file.service.IFileStorageService; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * 文件上传 配置 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2019-09-02 23:21:10 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@Configuration +public class FileConfig implements WebMvcConfigurer { + + private static final String MODE_CLOUD = "cloud"; + + private static final String MODE_LOCAL = "local"; + + @Value("${file.storage.cloud.region}") + private String region; + + @Value("${file.storage.cloud.endpoint}") + private String endpoint; + + @Value("${file.storage.cloud.bucket-name}") + private String bucketName; + + @Value("${file.storage.cloud.access-key}") + private String accessKey; + + @Value("${file.storage.cloud.secret-key}") + private String secretKey; + + @Value("${file.storage.cloud.private-url-expire-seconds}") + private Long privateUrlExpireSeconds; + + @Value("${file.storage.cloud.url-prefix}") + private String urlPrefix; + + @Value("${file.storage.local.upload-path}") + private String uploadPath; + + @Value("${file.storage.mode}") + private String mode; + + /** + * 初始化 云oss client 配置 + * + * @return + */ + @Bean + @ConditionalOnProperty(prefix = "file.storage", name = {"mode"}, havingValue = MODE_CLOUD) + public AmazonS3 initAmazonS3() { + ClientConfiguration clientConfig = new ClientConfiguration(); + clientConfig.setProtocol(Protocol.HTTPS); + return AmazonS3ClientBuilder.standard() + .withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKey, secretKey))) + .withClientConfiguration(clientConfig) + .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(endpoint, region)) + .withPathStyleAccessEnabled(false) + .withChunkedEncodingDisabled(true) + .build(); + } + + @Bean + @ConditionalOnProperty(prefix = "file.storage", name = {"mode"}, havingValue = MODE_CLOUD) + public IFileStorageService initCloudFileService() { + return new FileStorageCloudServiceImpl(); + } + + @Bean + @ConditionalOnProperty(prefix = "file.storage", name = {"mode"}, havingValue = MODE_LOCAL) + public IFileStorageService initLocalFileService() { + return new FileStorageLocalServiceImpl(); + } + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + if (MODE_LOCAL.equals(mode)) { + String path = uploadPath.endsWith("/") ? uploadPath : uploadPath + "/"; + registry.addResourceHandler(FileStorageLocalServiceImpl.UPLOAD_MAPPING + "/**").addResourceLocations("file:" + path); + } + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/HeartBeatConfig.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/HeartBeatConfig.java new file mode 100644 index 0000000..0cae1c8 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/HeartBeatConfig.java @@ -0,0 +1,37 @@ +package net.lab1024.sa.base.config; + +import jakarta.annotation.Resource; +import net.lab1024.sa.base.module.support.heartbeat.core.HeartBeatManager; +import net.lab1024.sa.base.module.support.heartbeat.core.IHeartBeatRecordHandler; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 心跳配置 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2018/10/9 18:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Configuration +public class HeartBeatConfig { + + /** + * 间隔时间 + */ + @Value("${heart-beat.interval-seconds}") + private Long intervalSeconds; + + @Resource + private IHeartBeatRecordHandler heartBeatRecordHandler; + + @Bean + public HeartBeatManager heartBeatManager() { + return new HeartBeatManager(intervalSeconds * 1000L, heartBeatRecordHandler); + } + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/JsonConfig.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/JsonConfig.java new file mode 100644 index 0000000..9164b35 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/JsonConfig.java @@ -0,0 +1,90 @@ +package net.lab1024.sa.base.config; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.LocalDateTimeUtil; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import net.lab1024.sa.base.common.json.serializer.LongJsonSerializer; +import org.apache.commons.lang3.StringUtils; +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.converter.Converter; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeParseException; + +/** + * json 序列化配置 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2017-11-28 15:21:10 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Configuration +public class JsonConfig { + + @Bean + public Jackson2ObjectMapperBuilderCustomizer customizer() { + return builder -> { + builder.deserializers(new LocalDateDeserializer(DatePattern.NORM_DATE_FORMAT.getDateTimeFormatter())); + builder.deserializers(new LocalDateTimeDeserializer(DatePattern.NORM_DATETIME_FORMAT.getDateTimeFormatter())); + builder.serializers(new LocalDateSerializer(DatePattern.NORM_DATE_FORMAT.getDateTimeFormatter())); + builder.serializers(new LocalDateTimeSerializer(DatePattern.NORM_DATETIME_FORMAT.getDateTimeFormatter())); + builder.serializerByType(Long.class, LongJsonSerializer.INSTANCE); + }; + } + + + /** + * string 转为 LocalDateTime 配置类 + * + * @author 卓大 + */ + @Configuration + public static class StringToLocalDateTime implements Converter { + + @Override + public LocalDateTime convert(String str) { + if (StringUtils.isBlank(str)) { + return null; + } + LocalDateTime localDateTime; + try { + localDateTime = LocalDateTimeUtil.parse(str, DatePattern.NORM_DATETIME_FORMAT.getDateTimeFormatter()); + } catch (DateTimeParseException e) { + throw new RuntimeException("请输入正确的日期格式:yyyy-MM-dd HH:mm:ss"); + } + return localDateTime; + } + } + + + /** + * string 转为 LocalDate 配置类 + * + * @author 卓大 + */ + @Configuration + public static class StringToLocalDate implements Converter { + + @Override + public LocalDate convert(String str) { + if (StringUtils.isBlank(str)) { + return null; + } + LocalDate localDate; + try { + localDate = LocalDateTimeUtil.parseDate(str, DatePattern.NORM_DATE_FORMAT.getDateTimeFormatter()); + } catch (DateTimeParseException e) { + throw new RuntimeException("请输入正确的日期格式:yyyy-MM-dd"); + } + return localDate; + } + } +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/MybatisPlusConfig.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/MybatisPlusConfig.java new file mode 100644 index 0000000..59887aa --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/MybatisPlusConfig.java @@ -0,0 +1,33 @@ +package net.lab1024.sa.base.config; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +/** + * mp 插件 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021-09-02 20:21:10 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@EnableTransactionManagement +@Configuration +public class MybatisPlusConfig { + + /** + * 分页插件 + */ + @Bean + public MybatisPlusInterceptor paginationInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); + return interceptor; + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/RedisConfig.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/RedisConfig.java new file mode 100644 index 0000000..5d1357c --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/RedisConfig.java @@ -0,0 +1,85 @@ +package net.lab1024.sa.base.config; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import jakarta.annotation.Resource; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.*; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +/** + * redis配置 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021-09-02 20:21:10 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Configuration +public class RedisConfig { + + @Resource + private RedisConnectionFactory factory; + + @Bean + public RedisTemplate redisTemplate() { + Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); + ObjectMapper om = new ObjectMapper(); + om.registerModule(new JavaTimeModule()) + .configure(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS, false) + .configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, false) + .setSerializationInclusion(JsonInclude.Include.NON_NULL); + + om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + // enableDefaultTyping 官方已弃用 所以改为 activateDefaultTyping + om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL); + jackson2JsonRedisSerializer.setObjectMapper(om); + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(factory); + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(jackson2JsonRedisSerializer); + template.setHashKeySerializer(jackson2JsonRedisSerializer); + template.setHashValueSerializer(jackson2JsonRedisSerializer); + template.setDefaultSerializer(new StringRedisSerializer()); + template.afterPropertiesSet(); + return template; + } + + @Bean + public HashOperations hashOperations(RedisTemplate redisTemplate) { + return redisTemplate.opsForHash(); + } + + @Bean + public ValueOperations valueOperations(RedisTemplate redisTemplate) { + return redisTemplate.opsForValue(); + } + + @Bean + public ListOperations listOperations(RedisTemplate redisTemplate) { + return redisTemplate.opsForList(); + } + + @Bean + public SetOperations setOperations(RedisTemplate redisTemplate) { + return redisTemplate.opsForSet(); + } + + @Bean + public ZSetOperations zSetOperations(RedisTemplate redisTemplate) { + return redisTemplate.opsForZSet(); + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/RepeatSubmitConfig.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/RepeatSubmitConfig.java new file mode 100644 index 0000000..5a9130f --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/RepeatSubmitConfig.java @@ -0,0 +1,43 @@ +package net.lab1024.sa.base.config; + +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.constant.StringConst; +import net.lab1024.sa.base.common.util.SmartRequestUtil; +import net.lab1024.sa.base.module.support.repeatsubmit.RepeatSubmitAspect; +import net.lab1024.sa.base.module.support.repeatsubmit.ticket.RepeatSubmitRedisTicket; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.core.ValueOperations; + +/** + * 重复提交配置 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2021/10/9 18:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Configuration +public class RepeatSubmitConfig { + + @Resource + private ValueOperations valueOperations; + + @Bean + public RepeatSubmitAspect repeatSubmitAspect() { + RepeatSubmitRedisTicket caffeineTicket = new RepeatSubmitRedisTicket(valueOperations, this::ticket); + return new RepeatSubmitAspect(caffeineTicket); + } + + /** + * 获取指明某个用户的凭证 + */ + private String ticket(String servletPath) { + Long userId = SmartRequestUtil.getRequestUserId(); + if (null == userId) { + return StringConst.EMPTY; + } + return servletPath + "_" + userId; + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/RestTemplateConfig.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/RestTemplateConfig.java new file mode 100644 index 0000000..1a3cdf4 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/RestTemplateConfig.java @@ -0,0 +1,130 @@ +package net.lab1024.sa.base.config; + +import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; +import okhttp3.ConnectionPool; +import okhttp3.OkHttpClient; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.http.client.OkHttp3ClientHttpRequestFactory; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.web.client.RestTemplate; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import java.nio.charset.StandardCharsets; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * http请求配置 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022-05-30 21:22:12 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Configuration +public class RestTemplateConfig { + + @Value("${http.pool.max-total}") + private Integer maxTotal; + + @Value("${http.pool.connect-timeout}") + private Integer connectTimeout; + + @Value("${http.pool.read-timeout}") + private Integer readTimeout; + + @Value("${http.pool.write-timeout}") + private Integer writeTimeout; + + @Value("${http.pool.keep-alive}") + private Integer keepAlive; + + @Bean + public RestTemplate restTemplate() { + RestTemplate restTemplate = new RestTemplate(); + restTemplate.setRequestFactory(this.clientHttpRequestFactory()); + List> messageConverterList = restTemplate.getMessageConverters(); + messageConverterList.add(0, new StringHttpMessageConverter(StandardCharsets.UTF_8)); + messageConverterList.addAll(this.converters()); + return restTemplate; + } + + public List> converters() { + List> converters = new ArrayList<>(); + HttpMessageConverter converter = new StringHttpMessageConverter(StandardCharsets.UTF_8); + FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter(); + List fastMediaTypes = new ArrayList<>(); + fastMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED); + fastMediaTypes.add(MediaType.APPLICATION_JSON); + fastConverter.setSupportedMediaTypes(fastMediaTypes); + converters.add(converter); + converters.add(fastConverter); + return converters; + } + + + public OkHttp3ClientHttpRequestFactory clientHttpRequestFactory() { + return new OkHttp3ClientHttpRequestFactory(httpClientBuilder()); + } + + public OkHttpClient httpClientBuilder() { + return new OkHttpClient.Builder() + .retryOnConnectionFailure(true) + .connectionPool(this.pool()) + .connectTimeout(connectTimeout, TimeUnit.MILLISECONDS) + .readTimeout(readTimeout, TimeUnit.MILLISECONDS) + .writeTimeout(writeTimeout, TimeUnit.MILLISECONDS) + .build(); + } + + public ConnectionPool pool() { + return new ConnectionPool(maxTotal, keepAlive, TimeUnit.MILLISECONDS); + } + + + @Bean + public X509TrustManager x509TrustManager() { + return new X509TrustManager() { + @Override + public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { + } + + @Override + public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + }; + } + + @Bean + public SSLSocketFactory sslSocketFactory() { + try { + //信任任何链接 + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, new TrustManager[]{x509TrustManager()}, new SecureRandom()); + return sslContext.getSocketFactory(); + } catch (NoSuchAlgorithmException | KeyManagementException e) { + e.printStackTrace(); + } + return null; + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/ScheduleConfig.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/ScheduleConfig.java new file mode 100644 index 0000000..721428e --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/ScheduleConfig.java @@ -0,0 +1,46 @@ +package net.lab1024.sa.base.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.SchedulingConfigurer; +import org.springframework.scheduling.config.ScheduledTaskRegistrar; +import org.springframework.scheduling.config.Task; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 定时任务调度 配置 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022-05-30 21:22:12 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@Configuration +public class ScheduleConfig implements SchedulingConfigurer { + + private ScheduledTaskRegistrar taskRegistrar; + + @Override + public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { + this.taskRegistrar = taskRegistrar; + } + + public String destroy() { + List taskList = new ArrayList<>(); + taskList.addAll(taskRegistrar.getCronTaskList()); + taskList.addAll(taskRegistrar.getTriggerTaskList()); + taskList.addAll(taskRegistrar.getFixedDelayTaskList()); + taskList.addAll(taskRegistrar.getFixedRateTaskList()); + + taskRegistrar.destroy(); + + List taskNameList = taskList.stream().map(Task::toString).collect(Collectors.toList()); + return "已关闭 @Scheduled定时任务:" + taskNameList.size() + "个!"; + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/SwaggerConfig.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/SwaggerConfig.java new file mode 100644 index 0000000..4db1d99 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/SwaggerConfig.java @@ -0,0 +1,132 @@ +package net.lab1024.sa.base.config; + +import com.google.common.collect.Lists; +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.constant.RequestHeaderConst; +import net.lab1024.sa.base.common.swagger.SmartOperationCustomizer; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import org.apache.commons.lang3.StringUtils; +import org.springdoc.core.customizers.OpenApiBuilderCustomizer; +import org.springdoc.core.customizers.ServerBaseUrlCustomizer; +import org.springdoc.core.models.GroupedOpenApi; +import org.springdoc.core.properties.SpringDocConfigProperties; +import org.springdoc.core.providers.JavadocProvider; +import org.springdoc.core.service.OpenAPIService; +import org.springdoc.core.service.SecurityService; +import org.springdoc.core.utils.PropertyResolverUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; + +import java.util.List; +import java.util.Optional; + +/** + * springdoc-openapi 配置 + * nginx配置前缀时如果需要访问【/swagger-ui/index.html】需添加额外nginx配置 + * location /v3/api-docs/ { + * proxy_pass http://127.0.0.1:1024/v3/api-docs/; + * } + * @Author 1024创新实验室-主任: 卓大 + * @Date 2020-03-25 22:54:46 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@Configuration +@Conditional(SystemEnvironmentConfig.class) +public class SwaggerConfig { + /** + * 用于解决/swagger-ui/index.html页面ServersUrl 测试环境部署错误问题 + */ + @Value("${springdoc.swagger-ui.server-base-url}") + private String serverBaseUrl; + + public static final String[] SWAGGER_WHITELIST = { + "/swagger-ui/**", + "/swagger-ui/index.html", + "/swagger-ui.html", + "/swagger-ui.html/**", + "/v3/api-docs", + "/v3/api-docs/**", + "/doc.html", + }; + + @Bean + public OpenAPI api() { + return new OpenAPI() + .components(components()) + .info(new Info() + .title("SmartFlow 3.X 接口文档") + .contact(new Contact().name("lovefawn")) + .version("v3.X") + ) + .addSecurityItem(new SecurityRequirement().addList(RequestHeaderConst.TOKEN)); + } + + private Components components() { + return new Components() + .addSecuritySchemes(RequestHeaderConst.TOKEN, new SecurityScheme().scheme("Bearer").description("请输入token,格式为[Bearer xxxxxxxx]").type(SecurityScheme.Type.APIKEY).in(SecurityScheme.In.HEADER).name(RequestHeaderConst.TOKEN)); + } + + @Bean + public GroupedOpenApi businessApi() { + return GroupedOpenApi.builder() + .group("业务接口") + .pathsToMatch("/**") + .pathsToExclude(SwaggerTagConst.Support.URL_PREFIX + "/**") + .addOperationCustomizer(new SmartOperationCustomizer()) + .build(); + + } + + @Bean + public GroupedOpenApi supportApi() { + return GroupedOpenApi.builder() + .group("支撑接口(Support)") + .pathsToMatch(SwaggerTagConst.Support.URL_PREFIX + "/**") + .addOperationCustomizer(new SmartOperationCustomizer()) + .build(); + } + + /** + * 以下代码可以用于设置 /swagger-ui/index.html 的serverBaseUrl + * 如果使用knife4j则不需要 + * @param openAPI + * @param securityParser + * @param springDocConfigProperties + * @param propertyResolverUtils + * @param openApiBuilderCustomizers + * @param serverBaseUrlCustomizers + * @param javadocProvider + * @return + */ + @Bean + public OpenAPIService openApiBuilder(Optional openAPI, + SecurityService securityParser, + SpringDocConfigProperties springDocConfigProperties, + PropertyResolverUtils propertyResolverUtils, + Optional> openApiBuilderCustomizers, + Optional> serverBaseUrlCustomizers, + Optional javadocProvider) { + List list = Lists.newArrayList(new ServerBaseUrlCustomizer() { + @Override + public String customize(String baseUrl) { + if (StringUtils.isNotBlank(serverBaseUrl)) { + return serverBaseUrl; + } + return baseUrl; + } + }); + return new OpenAPIService(openAPI, securityParser, springDocConfigProperties, + propertyResolverUtils, openApiBuilderCustomizers, Optional.of(list), javadocProvider); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/SystemEnvironmentConfig.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/SystemEnvironmentConfig.java new file mode 100644 index 0000000..caa641d --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/SystemEnvironmentConfig.java @@ -0,0 +1,59 @@ +package net.lab1024.sa.base.config; + +import net.lab1024.sa.base.common.domain.SystemEnvironment; +import net.lab1024.sa.base.common.enumeration.SystemEnvironmentEnum; +import net.lab1024.sa.base.common.util.SmartEnumUtil; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * 系统环境 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021/08/13 18:56 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Configuration +public class SystemEnvironmentConfig implements Condition { + + @Value("${spring.profiles.active}") + private String systemEnvironment; + + @Value("${project.name}") + private String projectName; + + /** + * 判断是否开启swagger + */ + @Override + public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { + return isDevOrTest(conditionContext); + } + + /** + * 是否为:开发环境和 测试环境 + */ + private boolean isDevOrTest(ConditionContext conditionContext) { + String property = conditionContext.getEnvironment().getProperty("spring.profiles.active"); + return StringUtils.isNotBlank(property) && (SystemEnvironmentEnum.TEST.equalsValue(property) || SystemEnvironmentEnum.DEV.equalsValue(property)); + } + + @Bean("systemEnvironment") + public SystemEnvironment initEnvironment() { + SystemEnvironmentEnum currentEnvironment = SmartEnumUtil.getEnumByValue(systemEnvironment, SystemEnvironmentEnum.class); + if (currentEnvironment == null) { + throw new ExceptionInInitializerError("无法获取当前环境!请在 application.yaml 配置参数:spring.profiles.active"); + } + if (StringUtils.isBlank(projectName)) { + throw new ExceptionInInitializerError("无法获取当前项目名称!请在 application.yaml 配置参数:project.name"); + } + return new SystemEnvironment(currentEnvironment == SystemEnvironmentEnum.PROD, projectName, currentEnvironment); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/TokenConfig.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/TokenConfig.java new file mode 100644 index 0000000..8e3d36c --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/TokenConfig.java @@ -0,0 +1,33 @@ +package net.lab1024.sa.base.config; + +import cn.dev33.satoken.config.SaTokenConfig; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.module.support.securityprotect.service.Level3ProtectConfigService; +import org.springframework.context.annotation.Configuration; + +/** + * + * 三级等保配置初始化后最低活跃频率全局配置 + * + * @Author 1024创新实验室-创始人兼主任:卓大 + * @Date 2024/11/24 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 ,Since 2012 + */ + +@Configuration +public class TokenConfig { + + @Resource + private Level3ProtectConfigService level3ProtectConfigService; + + // 此配置会覆盖 sa-base.yaml 中的配置 + @Resource + public void configSaToken(SaTokenConfig config) { + + config.setActiveTimeout(level3ProtectConfigService.getLoginActiveTimeoutSeconds()); + } + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/UrlConfig.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/UrlConfig.java new file mode 100644 index 0000000..b1af7ae --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/UrlConfig.java @@ -0,0 +1,142 @@ +package net.lab1024.sa.base.config; + +import cn.dev33.satoken.annotation.SaIgnore; +import cn.hutool.core.util.StrUtil; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import io.swagger.v3.oas.annotations.Operation; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.annoation.NoNeedLogin; +import net.lab1024.sa.base.common.domain.RequestUrlVO; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.mvc.condition.PathPatternsRequestCondition; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * url配置 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022-05-30 21:22:12 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Configuration +@Slf4j +public class UrlConfig { + + @Resource + private RequestMappingHandlerMapping requestMappingHandlerMapping; + + /** + * 获取每个方法的请求路径 + */ + @Bean + public Map> methodUrlMap() { + Map> methodUrlMap = Maps.newHashMap(); + //获取url与类和方法的对应信息 + Map map = requestMappingHandlerMapping.getHandlerMethods(); + for (Map.Entry entry : map.entrySet()) { + RequestMappingInfo requestMappingInfo = entry.getKey(); + PathPatternsRequestCondition pathPatternsCondition = requestMappingInfo.getPathPatternsCondition(); + if(pathPatternsCondition == null){ + continue; + } + + Set urls = pathPatternsCondition.getPatternValues(); + if (CollectionUtils.isEmpty(urls)) { + continue; + } + HandlerMethod handlerMethod = entry.getValue(); + methodUrlMap.put(handlerMethod.getMethod(), urls); + } + return methodUrlMap; + } + + /** + * 需要进行url权限校验的方法 + * + * @param methodUrlMap + * @return + */ + @Bean + public List authUrl(Map> methodUrlMap) { + List authUrlList = Lists.newArrayList(); + for (Map.Entry> entry : methodUrlMap.entrySet()) { + Method method = entry.getKey(); + // 忽略权限 + SaIgnore ignore = method.getAnnotation(SaIgnore.class); + if (null != ignore) { + continue; + } + NoNeedLogin noNeedLogin = method.getAnnotation(NoNeedLogin.class); + if (null != noNeedLogin) { + continue; + } + Set urlSet = entry.getValue(); + List requestUrlList = this.buildRequestUrl(method, urlSet); + authUrlList.addAll(requestUrlList); + } + return authUrlList; + } + + private List buildRequestUrl(Method method, Set urlSet) { + List requestUrlList = Lists.newArrayList(); + if (CollectionUtils.isEmpty(urlSet)) { + return requestUrlList; + } + //url对应的方法名称 + String className = method.getDeclaringClass().getName(); + String methodName = method.getName(); + List list = StrUtil.split(className, "."); + String controllerName = list.get(list.size() - 1); + String name = controllerName + "." + methodName; + //swagger 说明信息 + String methodComment = null; + Operation apiOperation = method.getAnnotation(Operation.class); + if (apiOperation != null) { + methodComment = apiOperation.summary(); + } + for (String url : urlSet) { + RequestUrlVO requestUrlVO = new RequestUrlVO(); + requestUrlVO.setUrl(url); + requestUrlVO.setName(name); + requestUrlVO.setComment(methodComment); + requestUrlList.add(requestUrlVO); + } + return requestUrlList; + } + + + /** + * 获取无需登录可以匿名访问的url信息 + * + * @return + */ + @Bean + public List noNeedLoginUrlList(Map> methodUrlMap) { + List noNeedLoginUrlList = Lists.newArrayList(); + for (Map.Entry> entry : methodUrlMap.entrySet()) { + Method method = entry.getKey(); + NoNeedLogin noNeedLogin = method.getAnnotation(NoNeedLogin.class); + if (null == noNeedLogin) { + continue; + } + noNeedLoginUrlList.addAll(entry.getValue()); + } + log.info("不需要登录的URL:{}", noNeedLoginUrlList); + return noNeedLoginUrlList; + } + + +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/YamlProcessor.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/YamlProcessor.java new file mode 100644 index 0000000..2c8fdd0 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/config/YamlProcessor.java @@ -0,0 +1,65 @@ +package net.lab1024.sa.base.config; + +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.env.EnvironmentPostProcessor; +import org.springframework.boot.env.YamlPropertySourceLoader; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.PropertySource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; + +import java.io.IOException; +import java.util.List; + +/** + * yaml 读取配置 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022-05-30 21:22:12 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Configuration +@Slf4j +@Order(value = 0) +public class YamlProcessor implements EnvironmentPostProcessor { + + private final YamlPropertySourceLoader loader = new YamlPropertySourceLoader(); + + @Override + public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { + + String filePath = environment.getProperty("project.log-path"); + if (SmartStringUtil.isNotEmpty(filePath)) { + System.setProperty("project.log-path", filePath); + } + + MutablePropertySources propertySources = environment.getPropertySources(); + this.loadProperty(propertySources); + } + + private void loadProperty(MutablePropertySources propertySources) { + PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + try { + Resource[] resources = resolver.getResources("classpath*:sa-*.yaml"); + if (resources.length < 1) { + return; + } + for (Resource resource : resources) { + log.info("初始化系统配置:{}", resource.getFilename()); + List> load = loader.load(resource.getFilename(), resource); + load.forEach(propertySources::addLast); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/constant/CacheKeyConst.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/constant/CacheKeyConst.java new file mode 100644 index 0000000..17aa2d2 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/constant/CacheKeyConst.java @@ -0,0 +1,20 @@ +package net.lab1024.sa.base.constant; + +/** + * 缓存key常量 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022-05-30 21:22:12 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class CacheKeyConst { + + public static class Dict { + + public static final String DICT_DATA = "dict_data_cache"; + + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/constant/LoginDeviceEnum.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/constant/LoginDeviceEnum.java new file mode 100644 index 0000000..3b503be --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/constant/LoginDeviceEnum.java @@ -0,0 +1,43 @@ +package net.lab1024.sa.base.constant; + +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 登录设备类型 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021-11-29 19:48:35 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public enum LoginDeviceEnum implements BaseEnum { + + PC(1, "电脑端"), + + ANDROID(2, "安卓"), + + APPLE(3, "苹果"), + + H5(4, "H5"), + + WEIXIN_MP(5, "微信小程序"); + + LoginDeviceEnum(Integer value, String desc) { + this.value = value; + this.desc = desc; + } + + private Integer value; + private String desc; + + @Override + public Integer getValue() { + return value; + } + + @Override + public String getDesc() { + return desc; + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/constant/RedisKeyConst.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/constant/RedisKeyConst.java new file mode 100644 index 0000000..29733c9 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/constant/RedisKeyConst.java @@ -0,0 +1,29 @@ +package net.lab1024.sa.base.constant; + +/** + * redis key 常量类 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022-05-30 21:22:12 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class RedisKeyConst { + + public static final String SEPARATOR = ":"; + + public static class Support { + + public static final String FILE_PRIVATE_VO = "file:private:"; + + public static final String SERIAL_NUMBER_LAST_INFO = "serial-number:last-info"; + + public static final String SERIAL_NUMBER = "serial-number:"; + + public static final String CAPTCHA = "captcha:"; + + public static final String LOGIN_VERIFICATION_CODE = "login:verification-code:"; + + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/constant/ReloadConst.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/constant/ReloadConst.java new file mode 100644 index 0000000..bcdfc38 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/constant/ReloadConst.java @@ -0,0 +1,18 @@ +package net.lab1024.sa.base.constant; + +/** + * reload 项目 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-05-30 21:22:12 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class ReloadConst { + + public static final String CONFIG_RELOAD = "system_config"; + + public static final String CACHE_SERVICE = "cache_service"; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/constant/SwaggerTagConst.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/constant/SwaggerTagConst.java new file mode 100644 index 0000000..fa30161 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/constant/SwaggerTagConst.java @@ -0,0 +1,59 @@ +package net.lab1024.sa.base.constant; + +/** + * swagger + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022-05-30 21:22:12 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class SwaggerTagConst { + + public static class Support { + + public static final String URL_PREFIX = "/support"; + + public static final String CACHE = "业务支撑-缓存"; + + public static final String CAPTCHA = "业务支撑-验证码"; + + public static final String OPERATE_LOG = "业务支撑-用户操作记录"; + + public static final String LOGIN_LOG = "业务支撑-登录日志"; + + public static final String RELOAD = "业务支撑-reload"; + + public static final String SERIAL_NUMBER = "业务支撑-id生成器"; + + public static final String HEART_BEAT = "业务支撑-服务心跳"; + + public static final String FILE = "业务支撑-文件服务"; + + public static final String CONFIG = "业务支撑-系统参数"; + + public static final String DATA_TRACER = "业务支撑-数据变动记录"; + + public static final String DICT = "业务支撑-数据字典"; + + public static final String CODE_GENERATOR = "业务支撑-代码生成"; + + public static final String CHANGE_LOG = "业务支撑-更新日志"; + + public static final String HELP_DOC = "业务支撑-帮助文档"; + + public static final String FEEDBACK = "业务支撑-意见反馈"; + + public static final String TABLE_COLUMN = "业务支撑-列自定义"; + + public static final String PROTECT = "业务支撑-网络安全"; + + public static final String DATA_MASKING = "业务支撑-数据脱敏"; + + public static final String JOB = "业务支撑-定时任务"; + + public static final String MESSAGE = "业务支撑-消息"; + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/handler/GlobalExceptionHandler.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/handler/GlobalExceptionHandler.java new file mode 100644 index 0000000..95e9965 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/handler/GlobalExceptionHandler.java @@ -0,0 +1,130 @@ +package net.lab1024.sa.base.handler; + +import cn.dev33.satoken.exception.NotPermissionException; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.code.SystemErrorCode; +import net.lab1024.sa.base.common.code.UserErrorCode; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.domain.SystemEnvironment; +import net.lab1024.sa.base.common.enumeration.SystemEnvironmentEnum; +import net.lab1024.sa.base.common.exception.BusinessException; +import org.springframework.beans.TypeMismatchException; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.validation.BindException; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * 全局异常拦截 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2020/8/25 21:57 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@ControllerAdvice +public class GlobalExceptionHandler { + + @Resource + private SystemEnvironment systemEnvironment; + + /** + * json 格式错误 缺少请求体 + */ + @ResponseBody + @ExceptionHandler({HttpMessageNotReadableException.class}) + public ResponseDTO jsonFormatExceptionHandler(Exception e) { + if (!systemEnvironment.isProd()) { + log.error("全局JSON格式错误异常,URL:{}", getCurrentRequestUrl(), e); + } + return ResponseDTO.error(UserErrorCode.PARAM_ERROR, "参数JSON格式错误"); + } + + /** + * json 格式错误 缺少请求体 + */ + @ResponseBody + @ExceptionHandler({TypeMismatchException.class, BindException.class}) + public ResponseDTO paramExceptionHandler(Exception e) { + if (e instanceof BindException) { + if (e instanceof MethodArgumentNotValidException) { + List fieldErrors = ((MethodArgumentNotValidException) e).getBindingResult().getFieldErrors(); + List msgList = fieldErrors.stream().map(FieldError::getDefaultMessage).collect(Collectors.toList()); + return ResponseDTO.error(UserErrorCode.PARAM_ERROR, String.join(",", msgList)); + } + + List fieldErrors = ((BindException) e).getFieldErrors(); + List error = fieldErrors.stream().map(field -> field.getField() + ":" + field.getRejectedValue()).collect(Collectors.toList()); + String errorMsg = UserErrorCode.PARAM_ERROR.getMsg() + ":" + error; + return ResponseDTO.error(UserErrorCode.PARAM_ERROR, errorMsg); + } + return ResponseDTO.error(UserErrorCode.PARAM_ERROR); + } + + /** + * sa-token 权限异常处理 + * + * @param e 权限异常 + * @return 错误结果 + */ + @ResponseBody + @ExceptionHandler(NotPermissionException.class) + public ResponseDTO permissionException(NotPermissionException e) { + // 开发环境 方便调试 + if (SystemEnvironmentEnum.PROD != systemEnvironment.getCurrentEnvironment()) { + return ResponseDTO.error(UserErrorCode.NO_PERMISSION, e.getMessage()); + } + return ResponseDTO.error(UserErrorCode.NO_PERMISSION); + } + + + /** + * 业务异常 + */ + @ResponseBody + @ExceptionHandler(BusinessException.class) + public ResponseDTO businessExceptionHandler(BusinessException e) { + if (!systemEnvironment.isProd()) { + log.error("全局业务异常,URL:{}", getCurrentRequestUrl(), e); + } + return ResponseDTO.error(SystemErrorCode.SYSTEM_ERROR, e.getMessage()); + } + + /** + * 其他全部异常 + * + * @param e 全局异常 + * @return 错误结果 + */ + @ResponseBody + @ExceptionHandler(Throwable.class) + public ResponseDTO errorHandler(Throwable e) { + log.error("捕获全局异常,URL:{}", getCurrentRequestUrl(), e); + return ResponseDTO.error(SystemErrorCode.SYSTEM_ERROR, systemEnvironment.isProd() ? null : e.toString()); + } + + /** + * 获取当前请求url + */ + private String getCurrentRequestUrl() { + RequestAttributes request = RequestContextHolder.getRequestAttributes(); + if (null == request) { + return null; + } + ServletRequestAttributes servletRequest = (ServletRequestAttributes) request; + return servletRequest.getRequest().getRequestURI(); + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/handler/MybatisPlusFillHandler.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/handler/MybatisPlusFillHandler.java new file mode 100644 index 0000000..e991ba6 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/handler/MybatisPlusFillHandler.java @@ -0,0 +1,54 @@ +package net.lab1024.sa.base.handler; + +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.reflection.MetaObject; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Date; + +/** + * Mybatis Plus 插入或者更新时指定字段设置值 + * + * @author zhoumingfa + */ +@Component +@Slf4j +public class MybatisPlusFillHandler implements MetaObjectHandler { + public static final String CREATE_TIME = "createTime"; + public static final String UPDATE_TIME = "updateTime"; + + @Override + public void insertFill(MetaObject metaObject) { + fillTimeField(metaObject, CREATE_TIME); + fillTimeField(metaObject, UPDATE_TIME); + } + + @Override + public void updateFill(MetaObject metaObject) { + fillTimeField(metaObject, UPDATE_TIME); + } + + /** + * 动态处理时间字段类型(兼容 LocalDateTime 和 Date) + */ + private void fillTimeField(MetaObject metaObject, String fieldName) { + if (metaObject.hasSetter(fieldName)) { + Class fieldType = metaObject.getGetterType(fieldName); + Object value = null; + + if (fieldType == LocalDateTime.class) { + value = LocalDateTime.now(); + } else if (fieldType == Date.class) { + // 将 LocalDateTime 转换为 Date + value = Date.from(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant()); + } + + if (value != null) { + this.fillStrategy(metaObject, fieldName, value); + } + } + } +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/listener/Ip2RegionListener.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/listener/Ip2RegionListener.java new file mode 100644 index 0000000..0055d15 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/listener/Ip2RegionListener.java @@ -0,0 +1,76 @@ +package net.lab1024.sa.base.listener; + +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.util.SmartIpUtil; +import org.apache.commons.io.FileUtils; +import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; +import org.springframework.boot.context.logging.LoggingApplicationListener; +import org.springframework.context.ApplicationListener; +import org.springframework.core.annotation.Order; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.io.ClassPathResource; + +import java.io.File; +import java.io.IOException; + +/** + * 初初始化ip工具类 + * + * @Author 1024创新实验室: zhuoda + * @Date 2023-09-03 23:45:26 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Order(value = LoggingApplicationListener.DEFAULT_ORDER) +@Slf4j +public class Ip2RegionListener implements ApplicationListener { + + private static final String IP_FILE_NAME = "ip2region.xdb"; + + private static final String LOG_DIRECTORY = "project.log-directory"; + + @Override + public void onApplicationEvent(ApplicationEnvironmentPreparedEvent applicationEvent) { + + ConfigurableEnvironment environment = applicationEvent.getEnvironment(); + String logDirectoryPath = environment.getProperty(LOG_DIRECTORY); + if (logDirectoryPath == null) { + throw new ExceptionInInitializerError("环境变量为空:" + LOG_DIRECTORY); + } + System.setProperty(LOG_DIRECTORY, logDirectoryPath); + + // 1、从jar中的ip2region.xdb文件复制到服务器目录中 + File logDirectoryFile = new File(logDirectoryPath); + if (!logDirectoryFile.exists()) { + logDirectoryFile.mkdirs(); + } + + String tempFilePath = null; + if (logDirectoryPath.endsWith("/")) { + tempFilePath = logDirectoryPath + IP_FILE_NAME; + } else { + tempFilePath = logDirectoryPath + "/" + IP_FILE_NAME; + } + + File tempFile = new File(tempFilePath); + try { + FileUtils.copyInputStreamToFile(new ClassPathResource(IP_FILE_NAME).getInputStream(), tempFile); + + // 2、初始化 + SmartIpUtil.init(tempFilePath); + + + } catch (IOException e) { + log.error("无法复制ip数据文件 ip2region.xdb", e); + throw new ExceptionInInitializerError("无法复制ip数据文件"); + } finally { + if (tempFile.exists()) { + tempFile.delete(); + } + } + + } + + +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/listener/LogVariableListener.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/listener/LogVariableListener.java new file mode 100644 index 0000000..407445c --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/listener/LogVariableListener.java @@ -0,0 +1,32 @@ +package net.lab1024.sa.base.listener; + +import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; +import org.springframework.boot.context.logging.LoggingApplicationListener; +import org.springframework.context.ApplicationListener; +import org.springframework.core.annotation.Order; +import org.springframework.core.env.ConfigurableEnvironment; + +/** + * 将application.yam l中的日志路径变量:project.log-path注入到 log4j2.xml + * + * @Author 1024创新实验室: zhuoda + * @Date 2023-09-03 23:45:26 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Order(value = LoggingApplicationListener.DEFAULT_ORDER - 1) +public class LogVariableListener implements ApplicationListener { + + private static final String LOG_DIRECTORY = "project.log-directory"; + + @Override + public void onApplicationEvent(ApplicationEnvironmentPreparedEvent applicationEvent) { + + ConfigurableEnvironment environment = applicationEvent.getEnvironment(); + String filePath = environment.getProperty(LOG_DIRECTORY); + if (filePath != null) { + System.setProperty(LOG_DIRECTORY, filePath); + } + } +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/listener/WebServerListener.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/listener/WebServerListener.java new file mode 100644 index 0000000..c248c7d --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/listener/WebServerListener.java @@ -0,0 +1,95 @@ +package net.lab1024.sa.base.listener; + +import cn.hutool.core.net.NetUtil; +import cn.hutool.core.util.URLUtil; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.code.ErrorCodeRegister; +import net.lab1024.sa.base.common.enumeration.SystemEnvironmentEnum; +import net.lab1024.sa.base.common.util.SmartEnumUtil; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.context.WebServerApplicationContext; +import org.springframework.boot.web.context.WebServerInitializedEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.core.annotation.Order; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +/** + * 启动监听器 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2021-12-23 23:45:26 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@Component +@Order(value = 1024) +public class WebServerListener implements ApplicationListener { + + @Value("${reload.interval-seconds}") + private Integer intervalSeconds; + + @Override + public void onApplicationEvent(WebServerInitializedEvent webServerInitializedEvent) { + WebServerApplicationContext context = webServerInitializedEvent.getApplicationContext(); + // 初始化reload + initReload(context); + // 项目信息 + showProjectMessage(webServerInitializedEvent); + } + + /** + * 显示项目信息 + */ + private void showProjectMessage(WebServerInitializedEvent webServerInitializedEvent) { + WebServerApplicationContext context = webServerInitializedEvent.getApplicationContext(); + Environment env = context.getEnvironment(); + + //获取服务信息 + String ip = NetUtil.getLocalhost().getHostAddress(); + Integer port = webServerInitializedEvent.getWebServer().getPort(); + String contextPath = env.getProperty("server.servlet.context-path"); + if (contextPath == null) { + contextPath = ""; + } + String profile = env.getProperty("spring.profiles.active"); + SystemEnvironmentEnum environmentEnum = SmartEnumUtil.getEnumByValue(profile, SystemEnvironmentEnum.class); + String projectName = env.getProperty("project.name"); + //拼接服务地址 + String title = String.format("-------------【%s】 服务已成功启动 (%s started successfully)-------------", projectName, projectName); + + // 初始化状态码 + int codeCount = ErrorCodeRegister.initialize(); + String localhostUrl = URLUtil.normalize(String.format("http://localhost:%d%s", port, contextPath), false, true); + String externalUrl = URLUtil.normalize(String.format("http://%s:%d%s", ip, port, contextPath), false, true); + String swaggerUrl = URLUtil.normalize(String.format("http://localhost:%d%s/swagger-ui/index.html", port, contextPath), false, true); + String knife4jUrl = URLUtil.normalize(String.format("http://localhost:%d%s/doc.html", port, contextPath), false, true); + log.warn("\n{}\n" + + "\t当前启动环境:\t{} , {}" + + "\n\t返回码初始化:\t完成{}个返回码初始化" + + "\n\t服务本机地址:\t{}" + + "\n\t服务外网地址:\t{}" + + "\n\tSwagger地址:\t{}" + + "\n\tknife4j地址:\t{}" + + "\n-------------------------------------------------------------------------------------\n", + title, profile, environmentEnum.getDesc(), codeCount, localhostUrl, externalUrl, swaggerUrl, knife4jUrl); + } + + /** + * 初始化reload + */ + private void initReload(WebServerApplicationContext applicationContext) { +// 将applicationContext转换为ConfigurableApplicationContext +// ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext; +// +// +// //获取BeanFactory +// DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getAutowireCapableBeanFactory(); +// +// //动态注册bean +// SmartReloadManager reloadManager = new SmartReloadManager(applicationContext.getBean(ReloadCommand.class), intervalSeconds); +// defaultListableBeanFactory.registerSingleton("smartReloadManager", reloadManager); + } +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/constant/RightsFlowStatusEnum.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/constant/RightsFlowStatusEnum.java new file mode 100644 index 0000000..4abcc4a --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/constant/RightsFlowStatusEnum.java @@ -0,0 +1,63 @@ +package net.lab1024.sa.base.module.business.rights.constant; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 维权流程状态枚举 + * + * @author SmartFlow + * @date 2026-03-18 + */ +@AllArgsConstructor +@Getter +public enum RightsFlowStatusEnum implements BaseEnum { + + /** + * 0 待提交 + */ + PENDING_SUBMIT(0, "待提交"), + + /** + * 1 待受理 + */ + PENDING_ACCEPT(1, "待受理"), + + /** + * 2 受理中 + */ + ACCEPTING(2, "受理中"), + + /** + * 3 已受理 + */ + ACCEPTED(3, "已受理"), + + /** + * 4 办理中 + */ + PROCESSING(4, "办理中"), + + /** + * 5 已办结 + */ + COMPLETED(5, "已办结"), + + + /** + * 7 已评价 + */ + EVALUATED(6, "已评价"), + + /** + * 9 不受理 + */ + REJECTED(9, "不受理"), + + ; + + private final Integer value; + + private final String desc; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/dto/PoliceEventQueryDTO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/dto/PoliceEventQueryDTO.java new file mode 100644 index 0000000..1217577 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/dto/PoliceEventQueryDTO.java @@ -0,0 +1,60 @@ +package net.lab1024.sa.base.module.business.rights.domain.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +/** + * 警情查询 DTO + * + * @author SmartFlow + * @date 2026-03-27 + */ +@Data +@Schema(description = "警情查询参数") +public class PoliceEventQueryDTO { + + @Schema(description = "接警单编号") + private String receiveNo; + + @Schema(description = "案件名称") + private String caseName; + + @Schema(description = "案件类型(administrative:行政,criminal:刑事,other:其他)") + private String caseType; + + @Schema(description = "报案开始时间") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime reportStartTime; + + @Schema(description = "报案结束时间") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime reportEndTime; + + @Schema(description = "受理开始时间") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime acceptStartTime; + + @Schema(description = "受理结束时间") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime acceptEndTime; + + @Schema(description = "报警开始时间") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime alarmStartTime; + + @Schema(description = "报警结束时间") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime alarmEndTime; + + @Schema(description = "接警单位") + private String receiveUnit; + + @Schema(description = "页码") + private Integer pageNum = 1; + + @Schema(description = "每页条数") + private Integer pageSize = 10; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/dto/RightsCaseQueryDTO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/dto/RightsCaseQueryDTO.java new file mode 100644 index 0000000..29e3ea4 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/dto/RightsCaseQueryDTO.java @@ -0,0 +1,52 @@ +package net.lab1024.sa.base.module.business.rights.domain.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +/** + * 案件查询 DTO + * + * @author SmartFlow + * @date 2026-03-27 + */ +@Data +@Schema(description = "案件查询参数") +public class RightsCaseQueryDTO { + + @Schema(description = "案件编号") + private String caseNo; + + @Schema(description = "案件名称") + private String caseName; + + @Schema(description = "案件类型(administrative:行政,criminal:刑事,other:其他)") + private String caseType; + + @Schema(description = "案件状态(pending:待受理,accepted:已受理,investigating:调查中,closed:已结案,archived:已归档)") + private String caseStatus; + + @Schema(description = "报案开始时间") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime reportStartTime; + + @Schema(description = "报案结束时间") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime reportEndTime; + + @Schema(description = "受理开始时间") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime acceptStartTime; + + @Schema(description = "受理结束时间") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime acceptEndTime; + + @Schema(description = "页码") + private Integer pageNum = 1; + + @Schema(description = "每页条数") + private Integer pageSize = 10; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/PoliceEventEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/PoliceEventEntity.java new file mode 100644 index 0000000..95e22f3 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/PoliceEventEntity.java @@ -0,0 +1,159 @@ +package net.lab1024.sa.base.module.business.rights.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import java.time.LocalDateTime; + +/** + * 警情事件实体类 + * + * @author SmartFlow + * @date 2026-03-27 + */ +@Data +@TableName("t_police_event") +public class PoliceEventEntity { + + /** + * 警情事件 ID + */ + @TableId(type = IdType.AUTO) + private Long eventId; + + /** + * 序号 + */ + private String serialNumber; + + /** + * 接警单编号 + */ + private String receiveNo; + + /** + * 报警方式(phone:电话报警,visit:来访报警,letter:来信报警,email:邮件报警,online:网络报警,other:其他) + */ + private String alarmMethod; + + /** + * 报警时间 + */ + private LocalDateTime alarmTime; + + /** + * 报警电话 + */ + private String alarmPhone; + + /** + * 案发时间 + */ + private LocalDateTime incidentTime; + + /** + * 案发地点 + */ + private String incidentPlace; + + /** + * 报警内容 + */ + private String alarmContent; + + /** + * 报警类型(emergency:紧急,urgent: urgent,normal:普通) + */ + private String alarmType; + + /** + * 报警人姓名 + */ + private String alarmPersonName; + + /** + * 报警人联系电话 + */ + private String alarmPersonPhone; + + /** + * 接警单位 + */ + private String receiveUnit; + + /** + * 接警人姓名 + */ + private String receivePersonName; + + /** + * 接警人警号 + */ + private String receivePersonPoliceNo; + + /** + * 接警时间 + */ + private LocalDateTime receiveTime; + + /** + * 案件名称 + */ + private String caseName; + + /** + * 案件类型(administrative:行政,criminal:刑事,other:其他) + */ + private String caseType; + + /** + * 报案时间 + */ + private LocalDateTime reportTime; + + /** + * 受理时间 + */ + private LocalDateTime acceptTime; + + /** + * 详情链接 + */ + private String detailUrl; + + /** + * 创建人 ID + */ + private Long createUserId; + + /** + * 创建人姓名 + */ + private String createUserName; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新人 ID + */ + private Long updateUserId; + + /** + * 更新人姓名 + */ + private String updateUserName; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + + /** + * 是否删除(0-未删除 1-已删除) + */ + private Integer deletedFlag; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsAppealEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsAppealEntity.java new file mode 100644 index 0000000..66fb629 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsAppealEntity.java @@ -0,0 +1,134 @@ +package net.lab1024.sa.base.module.business.rights.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import java.time.LocalDateTime; + +/** + * 民警维权申诉表实体类 + * + * @author SmartFlow + * @date 2026-03-04 + */ +@Data +@TableName("t_rights_defense_appeal") +public class RightsAppealEntity { + + /** + * 申诉 ID + */ + @TableId(type = IdType.AUTO) + private Long appealId; + + /** + * 原维权申请 ID + */ + private Long rightsId; + + /** + * 原办件 ID + */ + private Long originalCaseId; + + /** + * 申诉人 ID + */ + private Long appellantId; + + /** + * 申诉人姓名 + */ + private String appellantName; + + /** + * 申诉人警号 + */ + private String appellantPoliceNo; + + /** + * 申诉时间 + */ + private LocalDateTime appealTime; + + /** + * 申诉理由 + */ + private String appealReason; + + /** + * 申诉请求 + */ + private String appealRequest; + + /** + * 新证据材料 + */ + private String newEvidence; + + /** + * 申诉状态(待审核、审核通过、审核不通过、办理中、已办结) + */ + private String appealStatus; + + /** + * 处理意见 + */ + private String handleOpinion; + + /** + * 处理结果 + */ + private String handleResult; + + /** + * 处理人 ID + */ + private Long handleUserId; + + /** + * 处理人姓名 + */ + private String handleUserName; + + /** + * 处理时间 + */ + private LocalDateTime handleTime; + + /** + * 创建人 ID + */ + private Long createUserId; + + /** + * 创建人姓名 + */ + private String createUserName; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新人 ID + */ + private Long updateUserId; + + /** + * 更新人姓名 + */ + private String updateUserName; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + + /** + * 是否删除 + */ + private Boolean deletedFlag; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsApplicationEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsApplicationEntity.java new file mode 100644 index 0000000..d95589a --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsApplicationEntity.java @@ -0,0 +1,253 @@ +package net.lab1024.sa.base.module.business.rights.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import java.time.LocalDateTime; + +/** + * 民警维权申请表实体类 + * + * @author SmartFlow + * @date 2026-03-04 + */ +@Data +@TableName("t_rights_defense_application") +public class RightsApplicationEntity { + + /** + * 维权 ID + */ + @TableId(type = IdType.AUTO) + private Long rightsId; + + /** + * 维权编号 + */ + private String rightsNo; + + /** + * 申请类型(本人申请、非本人申请) + */ + private String applicationType; + + /** + * 申请人 ID + */ + private Long applicantId; + + /** + * 申请人姓名 + */ + private String applicantName; + + /** + * 申请人警号 + */ + private String applicantPoliceNo; + + /** + * 申请机构 + */ + private String applicantOrg; + + /** + * 申请机构 ID + */ + private String applicantOrgId; + + /** + * 被侵权人姓名(冗余) + */ + private String victimName; + + /** + * 事件发生时间 + */ + private LocalDateTime incidentTime; + + /** + * 事件地点 + */ + private String incidentLocation; + + /** + * 执法环节 + */ + private String incidentPhase; + + /** + * 侵害类型 + */ + private String incidentType; + + /** + * 关联案件 + */ + private String relatedCase; + + private String handleDepartments; + + /** + * 关联警情 + */ + private String relatedAlarm; + + /** + * 维权事项 + */ + private String rightsItems; + + /** + * 证据文件 JSON 数组 + */ + private String evidenceFiles; + + /** + * 流程状态(0 待提交、1 待受理、2 已受理、3 办理中、4 已办结、5 待评价、6 已评价、9 不受理) + */ + private String flowStatus; + + /** + * 工作流实例 ID + */ + private Long instanceId; + + /** + * 当前审批节点 + */ + private String currentNode; + + /** + * 提交时间 + */ + private LocalDateTime submitTime; + + /** + * 创建人 ID + */ + private Long createUserId; + + /** + * 创建人姓名 + */ + private String createUserName; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新人 ID + */ + private Long updateUserId; + + /** + * 更新人姓名 + */ + private String updateUserName; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + + /** + * 是否删除 + */ + @TableField("deleted_flag") + private Boolean deletedFlag; + + /** + * 是否报备工单 (0-否 1-是) + */ + @TableField("is_reported") + private Integer isReported; + + /** + * 是否评查 (0-否 1-是) + */ + @TableField("is_reviewed") + private Integer isReviewed; + + @TableField("claims") + private String claims; + + @TableField("application_area") + private String applicationArea; + + /** + * 延期申请状态 (0-无延期,1-延期申请中,2-已延期) + */ + @TableField("delay_apply_status") + private Integer delayApplyStatus; + + /** + * 延期次数 + */ + @TableField("delay_times") + private Integer delayTimes; + + /** + * 总延期天数 + */ + @TableField("total_delay_days") + private Integer totalDelayDays; + + /** + * 预计办结时间 + */ + @TableField("expected_finish_time") + private LocalDateTime expectedFinishTime; + + /** + * 实际办结时间 + */ + @TableField("actual_finish_time") + private LocalDateTime actualFinishTime; + + /** + * 是否超期 + */ + @TableField("is_timeout") + private Boolean isTimeout; + + /** + * 满意度等级 (满意、基本满意、不满意) + */ + @TableField("satisfaction_level") + private String satisfactionLevel; + + /** + * 满意度评分 (1-3 分) + */ + @TableField("satisfaction_score") + private Integer satisfactionScore; + + /** + * 评价内容 + */ + @TableField("evaluation_content") + private String evaluationContent; + + /** + * 评价人 ID + */ + @TableField("evaluator_id") + private Long evaluatorId; + + /** + * 评价人姓名 + */ + @TableField("evaluator_name") + private String evaluatorName; + + /** + * 评价时间 + */ + @TableField("evaluate_time") + private LocalDateTime evaluateTime; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsCaseEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsCaseEntity.java new file mode 100644 index 0000000..34d9aeb --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsCaseEntity.java @@ -0,0 +1,139 @@ +package net.lab1024.sa.base.module.business.rights.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import java.time.LocalDateTime; + +/** + * 民警维权办件表实体类 + * + * @author SmartFlow + * @date 2026-03-04 + */ +@Data +@TableName("t_rights_defense_case") +public class RightsCaseEntity { + + /** + * 办件 ID + */ + @TableId(type = IdType.AUTO) + private String caseId; + + /** + * 维权申请 ID + */ + private Long rightsId; + + /** + * 办件类型(督察受理、局领导审核、保障结果反馈等) + */ + private String caseType; + + /** + * 办理人 ID + */ + private Long handlerId; + + /** + * 办理人姓名 + */ + private String handlerName; + + /** + * 办理单位 + */ + private String handlerUnit; + + /** + * 办理意见 + */ + private String handlerOpinion; + + /** + * 办理结果 + */ + private String handlerResult; + + /** + * 反馈时间 + */ + private LocalDateTime feedbackTime; + + /** + * 是否通过(0 否 1 是) + */ + private Boolean isPass; + + /** + * 驳回原因 + */ + private String rejectReason; + + /** + * 是否需要延期 + */ + private Boolean needDelay; + + /** + * 延期原因 + */ + private String delayReason; + + /** + * 延期天数 + */ + private Integer delayDays; + + /** + * 预计办结时间 + */ + private LocalDateTime expectedFinishTime; + + /** + * 实际办结时间 + */ + private LocalDateTime actualFinishTime; + + /** + * 是否超期 + */ + private Boolean isTimeout; + + /** + * 创建人 ID + */ + private Long createUserId; + + /** + * 创建人姓名 + */ + private String createUserName; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新人 ID + */ + private Long updateUserId; + + /** + * 更新人姓名 + */ + private String updateUserName; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + + /** + * 是否删除 + */ + private Boolean deletedFlag; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsCaseInfoEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsCaseInfoEntity.java new file mode 100644 index 0000000..0351e41 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsCaseInfoEntity.java @@ -0,0 +1,149 @@ +package net.lab1024.sa.base.module.business.rights.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import java.time.LocalDateTime; + +/** + * 案件信息实体类 + * + * @author SmartFlow + * @date 2026-03-27 + */ +@Data +@TableName("t_rights_case_info") +public class RightsCaseInfoEntity { + + /** + * 案件 ID + */ + @TableId(type = IdType.AUTO) + private String caseId; + + /** + * 序号 + */ + private String serialNumber; + + /** + * 案件编号 + */ + private String caseNo; + + /** + * 案件名称 + */ + private String caseName; + + /** + * 案件类型(administrative:行政,criminal:刑事,other:其他) + */ + private String caseType; + + /** + * 主办单位 + */ + private String organizeUnit; + + /** + * 案件状态(pending:待受理,accepted:已受理,investigating:调查中,closed:已结案,archived:已归档) + */ + private String caseStatus; + + /** + * 报案时间 + */ + private LocalDateTime reportTime; + + /** + * 受理时间 + */ + private LocalDateTime acceptTime; + + /** + * 立案时间 + */ + private LocalDateTime registerTime; + + /** + * 结案时间 + */ + private LocalDateTime closeTime; + + /** + * 归档时间 + */ + private LocalDateTime archiveTime; + + /** + * 主办人姓名 + */ + private String organizerName; + + /** + * 主办人警号 + */ + private String organizerPoliceNo; + + /** + * 主办人电话 + */ + private String organizerPhone; + + /** + * 简要案情 + */ + private String briefDescription; + + /** + * 关联警情 ID + */ + private String relatedEventId; + + /** + * 关联接警单编号 + */ + private String relatedReceiveNo; + + /** + * 详情链接 + */ + private String detailUrl; + + /** + * 创建人 ID + */ + private Long createUserId; + + /** + * 创建人姓名 + */ + private String createUserName; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新人 ID + */ + private Long updateUserId; + + /** + * 更新人姓名 + */ + private String updateUserName; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + + /** + * 是否删除(0-未删除 1-已删除) + */ + private Integer deletedFlag; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsClaimConfigEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsClaimConfigEntity.java new file mode 100644 index 0000000..bea202a --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsClaimConfigEntity.java @@ -0,0 +1,99 @@ +package net.lab1024.sa.base.module.business.rights.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import java.time.LocalDateTime; + +/** + * 保障诉求配置表实体类 + * + * @author SmartFlow + * @date 2026-03-04 + */ +@Data +@TableName("t_rights_claim_config") +public class RightsClaimConfigEntity { + + /** + * 配置 ID + */ + @TableId(type = IdType.AUTO) + private Long configId; + + /** + * 保障诉求类型 + */ + private String claimType; + + /** + * 诉求编码 + */ + private String claimCode; + + /** + * 关联事件性质 + */ + private String eventType; + + /** + * 办理时限(天) + */ + private Integer timeLimit; + + /** + * 填报要求 + */ + private String fillRequirement; + + /** + * 排序 + */ + private Integer sortOrder; + + /** + * 是否启用 + */ + private Boolean isEnabled; + + /** + * 备注 + */ + private String remark; + + /** + * 创建人 ID + */ + private Long createUserId; + + /** + * 创建人姓名 + */ + private String createUserName; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新人 ID + */ + private Long updateUserId; + + /** + * 更新人姓名 + */ + private String updateUserName; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + + /** + * 是否删除 + */ + private Boolean deletedFlag; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsClaimEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsClaimEntity.java new file mode 100644 index 0000000..6141491 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsClaimEntity.java @@ -0,0 +1,100 @@ +package net.lab1024.sa.base.module.business.rights.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Data +@TableName("t_rights_defense_claim") +public class RightsClaimEntity { + + @TableId(type = IdType.AUTO) + private Long claimId; + + private Long rightsId; + + private Long configId; + + private String claimType; + + private String reportRequirement; + + private Integer deadline; + + private Long handlerId; + + private String handlerName; + + private LocalDateTime handleTime; + + private String evidenceFiles; + + @TableField(exist = false) + private List evidenceList; + + private String handleDescription; + + /** + * 会商 ID + */ + private Long consultationId; + + /** + * 会商名称 + */ + private String consultationName; + + /** + * 办理类型 (1-直接办理,2-会商办理,3-延期办理) + */ + private Integer handleType; + + /** + * 延期原因 + */ + private String delayReason; + + /** + * 延期天数 + */ + private Integer delayDays; + + /** + * 原截止时间 + */ + private LocalDateTime originalDeadlineDate; + + /** + * 新截止时间 + */ + private LocalDateTime newDeadlineDate; + + /** + * 处理方式类型(多个用逗号分隔) + */ + private String punishmentTypes; + + /** + * 抚恤金额 + */ + private java.math.BigDecimal compensationAmount; + + /** + * 挽回损失金额(元) + */ + private java.math.BigDecimal recoveryLoss; + + /** + * 是否追究领导和责任人责任(0-否 1-是) + */ + private Integer pursueResponsibility; + + private LocalDateTime createTime; + + private Boolean deletedFlag; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsDefenseFileForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsDefenseFileForm.java new file mode 100644 index 0000000..9769a52 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsDefenseFileForm.java @@ -0,0 +1,29 @@ +package net.lab1024.sa.base.module.business.rights.domain.entity; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +public class RightsDefenseFileForm { + + @Schema(description = "文件ID") + private Long fileId; + + @Schema(description = "文件Key") + private String fileKey; + + @Schema(description = "文件名称") + private String fileName; + + @Schema(description = "文件大小") + private Long fileSize; + + @Schema(description = "文件类型") + private String fileType; + + @Schema(description = "附件地址") + private String fileUrl; + + @Schema(description = "附件业务类型(1:佐证材料;2:初查材料;3:办理材料)") + private Integer busType; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsEvaluationEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsEvaluationEntity.java new file mode 100644 index 0000000..23e9350 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsEvaluationEntity.java @@ -0,0 +1,99 @@ +package net.lab1024.sa.base.module.business.rights.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import java.time.LocalDateTime; + +/** + * 民警维权评价表实体类 + * + * @author SmartFlow + * @date 2026-03-04 + */ +@Data +@TableName("t_rights_defense_evaluation") +public class RightsEvaluationEntity { + + /** + * 评价 ID + */ + @TableId(type = IdType.AUTO) + private Long evaluationId; + + /** + * 维权申请 ID + */ + private Long rightsId; + + /** + * 办件 ID + */ + private String caseId; + + /** + * 评价人 ID + */ + private Long evaluatorId; + + /** + * 评价人姓名 + */ + private String evaluatorName; + + /** + * 满意度等级(非常满意、满意、基本满意、不满意) + */ + private String satisfactionLevel; + + /** + * 满意度评分(1-5 分) + */ + private Integer satisfactionScore; + + /** + * 评价内容 + */ + private String evaluationContent; + + /** + * 回复内容 + */ + private String replyContent; + + /** + * 回复人 ID + */ + private Long replyUserId; + + /** + * 回复人姓名 + */ + private String replyUserName; + + /** + * 回复时间 + */ + private LocalDateTime replyTime; + + /** + * 评价时间 + */ + private LocalDateTime evaluateTime; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + + /** + * 是否删除 + */ + private Boolean deletedFlag; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsEventClaimRelEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsEventClaimRelEntity.java new file mode 100644 index 0000000..6784af5 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsEventClaimRelEntity.java @@ -0,0 +1,69 @@ +package net.lab1024.sa.base.module.business.rights.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import java.time.LocalDateTime; + +/** + * 事件性质与保障诉求关联表实体类 + * + * @author SmartFlow + * @date 2026-03-05 + */ +@Data +@TableName("t_rights_event_claim_rel") +public class RightsEventClaimRelEntity { + + /** + * 关联 ID + */ + @TableId(type = IdType.AUTO) + private Long relId; + + /** + * 事件性质 ID + */ + private Long eventTypeId; + + /** + * 保障诉求配置 ID + */ + private Long configId; + + /** + * 创建人 ID + */ + private Long createUserId; + + /** + * 创建人姓名 + */ + private String createUserName; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新人 ID + */ + private Long updateUserId; + + /** + * 更新人姓名 + */ + private String updateUserName; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + + /** + * 是否删除 + */ + private Boolean deletedFlag; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsEventTypeEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsEventTypeEntity.java new file mode 100644 index 0000000..29e6e63 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsEventTypeEntity.java @@ -0,0 +1,64 @@ +package net.lab1024.sa.base.module.business.rights.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import java.time.LocalDateTime; + +/** + * 事件性质表实体类 + * + * @author SmartFlow + * @date 2026-03-05 + */ +@Data +@TableName("t_rights_event_type") +public class RightsEventTypeEntity { + + /** + * 事件性质 ID + */ + @TableId(type = IdType.AUTO) + private Long eventTypeId; + + /** + * 事件性质名称 + */ + private String eventTypeName; + + /** + * 创建人 ID + */ + private Long createUserId; + + /** + * 创建人姓名 + */ + private String createUserName; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新人 ID + */ + private Long updateUserId; + + /** + * 更新人姓名 + */ + private String updateUserName; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + + /** + * 是否删除 + */ + private Boolean deletedFlag; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsOrganizationMemberEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsOrganizationMemberEntity.java new file mode 100644 index 0000000..7edd1c6 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsOrganizationMemberEntity.java @@ -0,0 +1,114 @@ +package net.lab1024.sa.base.module.business.rights.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import java.time.LocalDateTime; + +/** + * 维权组织成员表实体类 + * + * @author SmartFlow + * @date 2026-03-04 + */ +@Data +@TableName("t_rights_organization_member") +public class RightsOrganizationMemberEntity { + + /** + * 成员 ID + */ + @TableId(type = IdType.AUTO) + private Long memberId; + + /** + * 成员类型(维权委成员、维权办成员、会商成员、工作组) + */ + private String memberType; + + /** + * 用户 ID + */ + private Long userId; + + /** + * 用户姓名 + */ + private String userName; + + /** + * 警号 + */ + private String policeNo; + + /** + * 单位名称 + */ + private String unitName; + + /** + * 职务 + */ + private String position; + + /** + * 角色(主任、副主任、委员、联络员等) + */ + private String role; + + /** + * 管辖区域 + */ + private String jurisdictionArea; + + /** + * 是否启用 + */ + private Boolean isEnabled; + + /** + * 排序 + */ + private Integer sortOrder; + + /** + * 备注 + */ + private String remark; + + /** + * 创建人 ID + */ + private Long createUserId; + + /** + * 创建人姓名 + */ + private String createUserName; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新人 ID + */ + private Long updateUserId; + + /** + * 更新人姓名 + */ + private String updateUserName; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + + /** + * 是否删除 + */ + private Boolean deletedFlag; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsRecordEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsRecordEntity.java new file mode 100644 index 0000000..0ef738a --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsRecordEntity.java @@ -0,0 +1,174 @@ +package net.lab1024.sa.base.module.business.rights.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import java.time.LocalDateTime; + +/** + * 民警维权报备表实体类 + * + * @author SmartFlow + * @date 2026-03-04 + */ +@Data +@TableName("t_rights_defense_record") +public class RightsRecordEntity { + + /** + * 报备 ID + */ + @TableId(type = IdType.AUTO) + private Long recordId; + + /** + * 报备类型(代为申请、记录备案) + */ + private String recordType; + + /** + * 被侵权人 ID + */ + private Long victimId; + + /** + * 被侵权人姓名 + */ + private String victimName; + + /** + * 被侵权人警号 + */ + private String victimPoliceNo; + + /** + * 被侵权人单位 + */ + private String victimUnit; + + /** + * 事件发生时间 + */ + private LocalDateTime incidentTime; + + /** + * 事件地点 + */ + private String incidentLocation; + + /** + * 执法环节 + */ + private String incidentPhase; + + /** + * 侵害类型 + */ + private String incidentType; + + /** + * 事件描述 + */ + private String incidentDescription; + + /** + * 是否有保障诉求(0 否 1 是) + */ + private Boolean hasClaim; + + /** + * 保障诉求内容 + */ + private String claimContent; + + /** + * 证据文件 + */ + private String evidenceFiles; + + /** + * 报备人 ID + */ + private Long recordUserId; + + /** + * 报备人姓名 + */ + private String recordUserName; + + /** + * 报备人警号 + */ + private String recordUserPoliceNo; + + /** + * 报备单位 + */ + private String recordUnit; + + /** + * 报备时间 + */ + private LocalDateTime recordTime; + + /** + * 处理状态(待处理、处理中、已处理) + */ + private String handleStatus; + + /** + * 处理意见 + */ + private String handleOpinion; + + /** + * 处理人 ID + */ + private Long handleUserId; + + /** + * 处理人姓名 + */ + private String handleUserName; + + /** + * 处理时间 + */ + private LocalDateTime handleTime; + + /** + * 创建人 ID + */ + private Long createUserId; + + /** + * 创建人姓名 + */ + private String createUserName; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新人 ID + */ + private Long updateUserId; + + /** + * 更新人姓名 + */ + private String updateUserName; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + + /** + * 是否删除 + */ + private Boolean deletedFlag; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsRiskWarningEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsRiskWarningEntity.java new file mode 100644 index 0000000..65c4d79 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsRiskWarningEntity.java @@ -0,0 +1,104 @@ +package net.lab1024.sa.base.module.business.rights.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import java.time.LocalDateTime; + +/** + * 维权风险预警表实体类 + * + * @author SmartFlow + * @date 2026-03-04 + */ +@Data +@TableName("t_rights_risk_warning") +public class RightsRiskWarningEntity { + + /** + * 预警 ID + */ + @TableId(type = IdType.AUTO) + private Long warningId; + + /** + * 预警类型(办件超期预警、疑似被侵权案件预警) + */ + private String warningType; + + /** + * 预警级别(一般、重要、紧急) + */ + private String warningLevel; + + /** + * 关联维权申请 ID + */ + private Long rightsId; + + /** + * 关联办件 ID + */ + private String caseId; + + /** + * 预警内容 + */ + private String warningContent; + + /** + * 预警时间 + */ + private LocalDateTime warningTime; + + /** + * 处理状态(未处理、已处理、已忽略) + */ + private String handleStatus; + + /** + * 处理人 ID + */ + private Long handleUserId; + + /** + * 处理人姓名 + */ + private String handleUserName; + + /** + * 处理意见 + */ + private String handleOpinion; + + /** + * 处理时间 + */ + private LocalDateTime handleTime; + + /** + * 是否发送短信 + */ + private Boolean sendSmsFlag; + + /** + * 发送时间 + */ + private LocalDateTime sendTime; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + + /** + * 是否删除 + */ + private Boolean deletedFlag; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsVictimEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsVictimEntity.java new file mode 100644 index 0000000..ceb07a3 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/entity/RightsVictimEntity.java @@ -0,0 +1,52 @@ +package net.lab1024.sa.base.module.business.rights.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@TableName("t_rights_defense_victim") +public class RightsVictimEntity { + + @TableId(type = IdType.AUTO) + private Long victimId; + + private Long rightsId; + + private Long employeeId; + + private String victimName; + + private String victimType; + + private String victimIdCard; + + private String victimPhone; + + private String victimWorkUnit; + + private String victimPoliceType; + + private String victimPoliceNo; + + private String victimGender; + + private LocalDateTime victimBirthday; + + /** + * 伤害程度 (sacrifice:牺牲, severe:重伤, minor:轻伤, slight:轻微伤, none:无伤害) + */ + private String injuryLevel; + + /** + * 不公正处分处理类型,多个用逗号分隔 (criminal:刑事责任, measure:刑事强制措施, suspension:停职禁闭, party:党纪处分, admin:政纪处分, dismissal:免职降职辞退调离) + */ + private String unjustTreatmentType; + + private LocalDateTime createTime; + + private Boolean deletedFlag; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/vo/PoliceEventVO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/vo/PoliceEventVO.java new file mode 100644 index 0000000..1088522 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/vo/PoliceEventVO.java @@ -0,0 +1,80 @@ +package net.lab1024.sa.base.module.business.rights.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 警情信息 VO + * + * @author SmartFlow + * @date 2026-03-27 + */ +@Data +@Schema(description = "警情信息") +public class PoliceEventVO { + + @Schema(description = "警情事件 ID") + private String eventId; + + @Schema(description = "序号") + private String serialNumber; + + @Schema(description = "接警单编号") + private String receiveNo; + + @Schema(description = "报警方式") + private String alarmMethod; + + @Schema(description = "报警时间") + private LocalDateTime alarmTime; + + @Schema(description = "报警电话") + private String alarmPhone; + + @Schema(description = "案发时间") + private LocalDateTime incidentTime; + + @Schema(description = "案发地点") + private String incidentPlace; + + @Schema(description = "报警内容") + private String alarmContent; + + @Schema(description = "报警类型") + private String alarmType; + + @Schema(description = "报警人姓名") + private String alarmPersonName; + + @Schema(description = "报警人联系电话") + private String alarmPersonPhone; + + @Schema(description = "接警单位") + private String receiveUnit; + + @Schema(description = "接警人姓名") + private String receivePersonName; + + @Schema(description = "接警人警号") + private String receivePersonPoliceNo; + + @Schema(description = "接警时间") + private LocalDateTime receiveTime; + + @Schema(description = "案件名称") + private String caseName; + + @Schema(description = "案件类型") + private String caseType; + + @Schema(description = "报案时间") + private LocalDateTime reportTime; + + @Schema(description = "受理时间") + private LocalDateTime acceptTime; + + @Schema(description = "详情链接") + private String detailUrl; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/vo/RightsCaseVO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/vo/RightsCaseVO.java new file mode 100644 index 0000000..e590beb --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/business/rights/domain/vo/RightsCaseVO.java @@ -0,0 +1,59 @@ +package net.lab1024.sa.base.module.business.rights.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 案件信息 VO + * + * @author SmartFlow + * @date 2026-03-27 + */ +@Data +@Schema(description = "案件信息") +public class RightsCaseVO { + + @Schema(description = "案件 ID") + private String caseId; + + @Schema(description = "序号") + private String serialNumber; + + @Schema(description = "案件编号") + private String caseNo; + + @Schema(description = "案件名称") + private String caseName; + + @Schema(description = "案件类型") + private String caseType; + + @Schema(description = "主办单位") + private String organizeUnit; + + @Schema(description = "案件状态") + private String caseStatus; + + @Schema(description = "报案时间") + private LocalDateTime reportTime; + + @Schema(description = "受理时间") + private LocalDateTime acceptTime; + + @Schema(description = "立案时间") + private LocalDateTime registerTime; + + @Schema(description = "主办人姓名") + private String organizerName; + + @Schema(description = "主办人警号") + private String organizerPoliceNo; + + @Schema(description = "主办人电话") + private String organizerPhone; + + @Schema(description = "详情链接") + private String detailUrl; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/advice/DecryptRequestAdvice.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/advice/DecryptRequestAdvice.java new file mode 100644 index 0000000..72a86e2 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/advice/DecryptRequestAdvice.java @@ -0,0 +1,95 @@ +package net.lab1024.sa.base.module.support.apiencrypt.advice; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.module.support.apiencrypt.annotation.ApiDecrypt; +import net.lab1024.sa.base.module.support.apiencrypt.domain.ApiEncryptForm; +import net.lab1024.sa.base.module.support.apiencrypt.service.ApiEncryptService; +import org.apache.commons.io.IOUtils; +import org.springframework.core.MethodParameter; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpInputMessage; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter; + +import java.io.InputStream; +import java.lang.reflect.Type; + +/** + * 解密 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/10/21 11:41:46 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室,Since 2012 + */ + +@Slf4j +@ControllerAdvice +public class DecryptRequestAdvice extends RequestBodyAdviceAdapter { + + private static final String ENCODING = "UTF-8"; + + @Resource + private ApiEncryptService apiEncryptService; + + @Resource + private ObjectMapper objectMapper; + + @Override + public boolean supports(MethodParameter methodParameter, Type targetType, Class> converterType) { + return methodParameter.hasMethodAnnotation(ApiDecrypt.class) || methodParameter.hasParameterAnnotation(ApiDecrypt.class) || methodParameter.getContainingClass().isAnnotationPresent(ApiDecrypt.class); + } + + @Override + public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class> converterType) { + try { + String bodyStr = IOUtils.toString(inputMessage.getBody(), ENCODING); + ApiEncryptForm apiEncryptForm = objectMapper.readValue(bodyStr, ApiEncryptForm.class); + if (SmartStringUtil.isEmpty(apiEncryptForm.getEncryptData())) { + return inputMessage; + } + String decrypt = apiEncryptService.decrypt(apiEncryptForm.getEncryptData()); + return new DecryptHttpInputMessage(inputMessage.getHeaders(), IOUtils.toInputStream(decrypt, ENCODING)); + } catch (Exception e) { + log.error("", e); + return inputMessage; + } + } + + @Override + public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class> converterType) { + return body; + } + + @Override + public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class> converterType) { + return body; + } + + static class DecryptHttpInputMessage implements HttpInputMessage { + private final HttpHeaders headers; + + private final InputStream body; + + public DecryptHttpInputMessage(HttpHeaders headers, InputStream body) { + this.headers = headers; + this.body = body; + } + + @Override + public InputStream getBody() { + return body; + } + + @Override + public HttpHeaders getHeaders() { + return headers; + } + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/advice/EncryptResponseAdvice.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/advice/EncryptResponseAdvice.java new file mode 100644 index 0000000..10cbc14 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/advice/EncryptResponseAdvice.java @@ -0,0 +1,63 @@ +package net.lab1024.sa.base.module.support.apiencrypt.advice; + +import com.alibaba.fastjson.JSON; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.enumeration.DataTypeEnum; +import net.lab1024.sa.base.module.support.apiencrypt.annotation.ApiEncrypt; +import net.lab1024.sa.base.module.support.apiencrypt.service.ApiEncryptService; +import org.springframework.core.MethodParameter; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; + +/** + * 加密 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/10/24 09:52:58 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室,Since 2012 + */ + + +@Slf4j +@ControllerAdvice +public class EncryptResponseAdvice implements ResponseBodyAdvice> { + + @Resource + private ApiEncryptService apiEncryptService; + + @Resource + private ObjectMapper objectMapper; + + @Override + public boolean supports(MethodParameter returnType, Class> converterType) { + return returnType.hasMethodAnnotation(ApiEncrypt.class) || returnType.getContainingClass().isAnnotationPresent(ApiEncrypt.class); + } + + @Override + public ResponseDTO beforeBodyWrite(ResponseDTO body, MethodParameter returnType, MediaType selectedContentType, Class> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { + if (body == null || body.getData() == null) { + return body; + } + + try { + String encrypt = apiEncryptService.encrypt(objectMapper.writeValueAsString(body.getData())); + body.setData(encrypt); + body.setDataType(DataTypeEnum.ENCRYPT.getValue()); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + return body; + } +} + + diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/annotation/ApiDecrypt.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/annotation/ApiDecrypt.java new file mode 100644 index 0000000..bedfc90 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/annotation/ApiDecrypt.java @@ -0,0 +1,20 @@ +package net.lab1024.sa.base.module.support.apiencrypt.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 解密注解 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/10/21 11:41:46 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface ApiDecrypt { +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/annotation/ApiEncrypt.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/annotation/ApiEncrypt.java new file mode 100644 index 0000000..4b6950d --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/annotation/ApiEncrypt.java @@ -0,0 +1,20 @@ +package net.lab1024.sa.base.module.support.apiencrypt.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 加密注解 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/10/21 11:41:46 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface ApiEncrypt { +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/domain/ApiEncryptForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/domain/ApiEncryptForm.java new file mode 100644 index 0000000..31e3aee --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/domain/ApiEncryptForm.java @@ -0,0 +1,20 @@ +package net.lab1024.sa.base.module.support.apiencrypt.domain; + +import lombok.Data; + +/** + * 加密数据的表单 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/10/21 11:41:46 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + + +@Data +public class ApiEncryptForm { + + private String encryptData; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/service/ApiEncryptService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/service/ApiEncryptService.java new file mode 100644 index 0000000..6c65708 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/service/ApiEncryptService.java @@ -0,0 +1,30 @@ +package net.lab1024.sa.base.module.support.apiencrypt.service; + +/** + * 接口加密、解密 Service + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/10/21 11:41:46 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +public interface ApiEncryptService { + + /** + * 解密 + * @param data + * @return + */ + String decrypt(String data); + + /** + * 加密 + * + * @param data + * @return + */ + String encrypt(String data); + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/service/ApiEncryptServiceAesImpl.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/service/ApiEncryptServiceAesImpl.java new file mode 100644 index 0000000..3e7ae91 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/service/ApiEncryptServiceAesImpl.java @@ -0,0 +1,114 @@ +package net.lab1024.sa.base.module.support.apiencrypt.service; + +import cn.hutool.crypto.symmetric.AES; +import cn.hutool.crypto.symmetric.SM4; +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.constant.StringConst; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.springframework.stereotype.Service; + +import java.io.UnsupportedEncodingException; +import java.security.Security; +import java.util.Base64; + +/** + * AES 加密和解密 + * 1、AES加密算法支持三种密钥长度:128位、192位和256位,这里选择128位 + * 2、AES 要求秘钥为 128bit,转化字节为 16个字节; + * 3、js前端使用 UCS-2 或者 UTF-16 编码,字母、数字、特殊符号等 占用1个字节; + * 4、所以:秘钥Key 组成为:字母、数字、特殊符号 一共16个即可 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/10/21 11:41:46 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +@Slf4j +public class ApiEncryptServiceAesImpl implements ApiEncryptService { + + private static final String CHARSET = "UTF-8"; + + private static final String AES_KEY = "1024lab__1024lab"; + + static { + Security.addProvider(new BouncyCastleProvider()); + } + + @Override + public String encrypt(String data) { + try { + // AES 加密 并转为 base64 + AES aes = new AES(hexToBytes(stringToHex(AES_KEY))); + return aes.encryptBase64(data); + + + } catch (Exception e) { + log.error(e.getMessage(), e); + return StringConst.EMPTY; + } + } + + @Override + public String decrypt(String data) { + try { + // 第一步: Base64 解码 + byte[] base64Decode = Base64.getDecoder().decode(data); + + // 第二步: AES 解密 + AES aes = new AES(hexToBytes(stringToHex(AES_KEY))); + byte[] decryptedBytes = aes.decrypt(base64Decode); + return new String(decryptedBytes, CHARSET); + + } catch (Exception e) { + log.error(e.getMessage(), e); + return StringConst.EMPTY; + } + } + + /** + * 16 进制串转字节数组 + * + * @param hex 16进制字符串 + * @return byte数组 + */ + public static byte[] hexToBytes(String hex) { + int length = hex.length(); + byte[] result; + if (length % 2 == 1) { + length++; + result = new byte[(length / 2)]; + hex = "0" + hex; + } else { + result = new byte[(length / 2)]; + } + int j = 0; + for (int i = 0; i < length; i += 2) { + result[j] = hexToByte(hex.substring(i, i + 2)); + j++; + } + return result; + } + + public static String stringToHex(String input) { + char[] chars = input.toCharArray(); + StringBuilder hex = new StringBuilder(); + for (char c : chars) { + hex.append(Integer.toHexString((int) c)); + } + return hex.toString(); + } + + /** + * 16 进制字符转字节 + * + * @param hex 16进制字符 0x00到0xFF + * @return byte + */ + private static byte hexToByte(String hex) { + return (byte) Integer.parseInt(hex, 16); + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/service/ApiEncryptServiceSmImpl.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/service/ApiEncryptServiceSmImpl.java new file mode 100644 index 0000000..d8d63fa --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/service/ApiEncryptServiceSmImpl.java @@ -0,0 +1,118 @@ +package net.lab1024.sa.base.module.support.apiencrypt.service; + +import cn.hutool.crypto.symmetric.SM4; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.constant.StringConst; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.springframework.stereotype.Service; + +import java.security.Security; +import java.util.Base64; + +/** + * 国产 SM4 加密 和 解密 + * 1、国密SM4 要求秘钥为 128bit,转化字节为 16个字节; + * 2、js前端使用 UCS-2 或者 UTF-16 编码,字母、数字、特殊符号等 占用1个字节; + * 3、java中 每个 字母数字 也是占用1个字节; + * 4、所以:前端和后端的 秘钥Key 组成为:字母、数字、特殊符号 一共16个即可 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/10/21 11:41:46 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +@Slf4j +@Service +public class ApiEncryptServiceSmImpl implements ApiEncryptService { + + private static final String CHARSET = "UTF-8"; + private static final String SM4_KEY = "1024lab__1024lab"; + + static { + Security.addProvider(new BouncyCastleProvider()); + } + + + @Override + public String encrypt(String data) { + try { + + // 第一步: SM4 加密 + SM4 sm4 = new SM4(hexToBytes(stringToHex(SM4_KEY))); + String encryptHex = sm4.encryptHex(data); + + // 第二步: Base64 编码 + return new String(Base64.getEncoder().encode(encryptHex.getBytes(CHARSET)), CHARSET); + + } catch (Exception e) { + log.error(e.getMessage(), e); + return StringConst.EMPTY; + } + } + + + @Override + public String decrypt(String data) { + try { + + // 第一步: Base64 解码 + byte[] base64Decode = Base64.getDecoder().decode(data); + + // 第二步: SM4 解密 + SM4 sm4 = new SM4(hexToBytes(stringToHex(SM4_KEY))); + return sm4.decryptStr(new String(base64Decode)); + + } catch (Exception e) { + log.error(e.getMessage(), e); + return StringConst.EMPTY; + } + } + + + public static String stringToHex(String input) { + char[] chars = input.toCharArray(); + StringBuilder hex = new StringBuilder(); + for (char c : chars) { + hex.append(Integer.toHexString((int) c)); + } + return hex.toString(); + } + + + /** + * 16 进制串转字节数组 + * + * @param hex 16进制字符串 + * @return byte数组 + */ + public static byte[] hexToBytes(String hex) { + int length = hex.length(); + byte[] result; + if (length % 2 == 1) { + length++; + result = new byte[(length / 2)]; + hex = "0" + hex; + } else { + result = new byte[(length / 2)]; + } + int j = 0; + for (int i = 0; i < length; i += 2) { + result[j] = hexToByte(hex.substring(i, i + 2)); + j++; + } + return result; + } + + /** + * 16 进制字符转字节 + * + * @param hex 16进制字符 0x00到0xFF + * @return byte + */ + private static byte hexToByte(String hex) { + return (byte) Integer.parseInt(hex, 16); + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/cache/CacheService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/cache/CacheService.java new file mode 100644 index 0000000..9b5d07b --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/cache/CacheService.java @@ -0,0 +1,43 @@ +package net.lab1024.sa.base.module.support.cache; + +import com.google.common.collect.Lists; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.constant.ReloadConst; +import net.lab1024.sa.base.module.support.reload.core.annoation.SmartReload; +import org.springframework.cache.caffeine.CaffeineCache; +import org.springframework.cache.caffeine.CaffeineCacheManager; +import org.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 缓存服务 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2021/10/11 20:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public interface CacheService { + + /** + * 获取所有缓存名称 + */ + List cacheNames(); + + /** + * 某个缓存下的所有 key + */ + List cacheKey(String cacheName); + + /** + * 移除某个 key + */ + void removeCache(String cacheName); + +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/cache/CaffeineCacheServiceImpl.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/cache/CaffeineCacheServiceImpl.java new file mode 100644 index 0000000..0abf454 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/cache/CaffeineCacheServiceImpl.java @@ -0,0 +1,71 @@ +package net.lab1024.sa.base.module.support.cache; + +import com.google.common.collect.Lists; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.constant.ReloadConst; +import net.lab1024.sa.base.module.support.reload.core.annoation.SmartReload; +import org.springframework.cache.caffeine.CaffeineCache; +import org.springframework.cache.caffeine.CaffeineCacheManager; + +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * caffeine 缓存实现 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2021/10/11 20:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class CaffeineCacheServiceImpl implements CacheService { + + @Resource + private CaffeineCacheManager caffeineCacheManager; + + /** + * 获取所有缓存名称 + */ + @Override + public List cacheNames() { + return Lists.newArrayList(caffeineCacheManager.getCacheNames()); + } + + /** + * 某个缓存下的所有 key + */ + @Override + public List cacheKey(String cacheName) { + CaffeineCache cache = (CaffeineCache) caffeineCacheManager.getCache(cacheName); + if (cache == null) { + return Lists.newArrayList(); + } + Set cacheKey = cache.getNativeCache().asMap().keySet(); + return cacheKey.stream().map(e -> e.toString()).collect(Collectors.toList()); + } + + /** + * 移除某个 key + */ + @Override + public void removeCache(String cacheName) { + CaffeineCache cache = (CaffeineCache) caffeineCacheManager.getCache(cacheName); + if (cache != null) { + cache.clear(); + } + } + + @SmartReload(ReloadConst.CACHE_SERVICE) + public void clearAllCache() { + Collection cacheNames = caffeineCacheManager.getCacheNames(); + for (String name : cacheNames) { + CaffeineCache cache = (CaffeineCache) caffeineCacheManager.getCache(name); + if (cache != null) { + cache.clear(); + } + } + } +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/cache/RedisCacheServiceImpl.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/cache/RedisCacheServiceImpl.java new file mode 100644 index 0000000..5e04fcc --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/cache/RedisCacheServiceImpl.java @@ -0,0 +1,86 @@ +package net.lab1024.sa.base.module.support.cache; + +import cn.hutool.core.util.StrUtil; +import com.google.common.collect.Lists; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.constant.ReloadConst; +import net.lab1024.sa.base.module.support.reload.core.annoation.SmartReload; +import org.springframework.data.redis.cache.RedisCache; +import org.springframework.data.redis.cache.RedisCacheManager; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisConnectionFactory; + +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * redis 缓存实现 + * + * @author zhoumingfa + * @date 2025/3/28 + */ +public class RedisCacheServiceImpl implements CacheService { + + @Resource + private RedisCacheManager redisCacheManager; + + @Resource + private RedisConnectionFactory redisConnectionFactory; + + /** + * 获取所有缓存名称 + */ + @Override + public List cacheNames() { + return Lists.newArrayList(redisCacheManager.getCacheNames()); + } + + /** + * 某个缓存下的所有 key + */ + @Override + public List cacheKey(String cacheName) { + RedisCache cache = (RedisCache) redisCacheManager.getCache(cacheName); + if (cache == null) { + return Lists.newArrayList(); + } + // 获取 Redis 连接 + RedisConnection connection = redisConnectionFactory.getConnection(); + // 根据指定的 key 模式获取所有匹配的键 + Set keys = connection.keyCommands().keys((cacheName + ":*").getBytes()); + + if (keys != null) { + return keys.stream().map(key -> { + String redisKey = StrUtil.str(key, "utf-8"); + // 从 Redis 键中提取出最后一个冒号后面的字符串作为真正的键 + return redisKey.substring(redisKey.lastIndexOf(":") + 1); + }).collect(Collectors.toList()); + } + connection.close(); + return Lists.newArrayList(cacheName); + } + + /** + * 移除某个 key + */ + @Override + public void removeCache(String cacheName) { + RedisCache cache = (RedisCache) redisCacheManager.getCache(cacheName); + if (cache != null) { + cache.clear(); + } + } + + @SmartReload(ReloadConst.CACHE_SERVICE) + public void clearAllCache() { + Collection cacheNames = redisCacheManager.getCacheNames(); + for (String name : cacheNames) { + RedisCache cache = (RedisCache) redisCacheManager.getCache(name); + if (cache != null) { + cache.clear(); + } + } + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/captcha/CaptchaController.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/captcha/CaptchaController.java new file mode 100644 index 0000000..40b2206 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/captcha/CaptchaController.java @@ -0,0 +1,36 @@ +package net.lab1024.sa.base.module.support.captcha; + + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.controller.SupportBaseController; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import net.lab1024.sa.base.module.support.captcha.domain.CaptchaVO; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 图形验证码业务 + * + * @Author 1024创新实验室: 胡克 + * @Date 2021-09-02 20:21:10 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Tag(name = SwaggerTagConst.Support.CAPTCHA) +@RestController +public class CaptchaController extends SupportBaseController { + + @Resource + private CaptchaService captchaService; + + @Operation(summary = "获取图形验证码 @author 胡克") + @GetMapping("/captcha") + public ResponseDTO generateCaptcha() { + return ResponseDTO.ok(captchaService.generateCaptcha()); + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/captcha/CaptchaService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/captcha/CaptchaService.java new file mode 100644 index 0000000..2058b34 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/captcha/CaptchaService.java @@ -0,0 +1,111 @@ +package net.lab1024.sa.base.module.support.captcha; + +import cn.hutool.captcha.CaptchaUtil; +import cn.hutool.captcha.LineCaptcha; +import cn.hutool.core.img.ImgUtil; +import cn.hutool.core.util.RandomUtil; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.constant.StringConst; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.domain.SystemEnvironment; +import net.lab1024.sa.base.constant.RedisKeyConst; +import net.lab1024.sa.base.module.support.captcha.domain.CaptchaForm; +import net.lab1024.sa.base.module.support.captcha.domain.CaptchaVO; +import net.lab1024.sa.base.module.support.redis.RedisService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import java.awt.*; +import java.util.Objects; +import java.util.UUID; + +/** + * 图形验证码 服务 + * + * @Author 1024创新实验室: 胡克 + * @Date 2021/8/31 20:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@Service +public class CaptchaService { + + /** + * 过期时间:65秒 + */ + private static final long EXPIRE_SECOND = 65L; + + @Resource + private SystemEnvironment systemEnvironment; + + @Resource + private RedisService redisService; + + /** + * 生成图形验证码 + * 默认 1 分钟有效期 + */ + public CaptchaVO generateCaptcha() { + + //生成四位验证码 + String captchaText = RandomUtil.randomNumbers(4); + + //定义图形验证码的长、宽、验证码位数、干扰线数量 + LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(125, 43, 4, 80); + + //设置背景颜色 + lineCaptcha.setBackground(new Color(230, 244, 255)); + + //生成图片 + Image image = lineCaptcha.createImage(captchaText); + + //转为base64 + String base64Code = ImgUtil.toBase64(image, "jpg"); + + /* + * 返回验证码对象 + * 图片 base64格式 + */ + // uuid 唯一标识 + String uuid = UUID.randomUUID().toString().replace("-", StringConst.EMPTY); + + CaptchaVO captchaVO = new CaptchaVO(); + captchaVO.setCaptchaUuid(uuid); + captchaVO.setCaptchaBase64Image("data:image/png;base64," + base64Code); + captchaVO.setExpireSeconds(EXPIRE_SECOND); + if (!systemEnvironment.isProd()) { + captchaVO.setCaptchaText(captchaText); + } + String redisCaptchaKey = redisService.generateRedisKey(RedisKeyConst.Support.CAPTCHA, uuid); + redisService.set(redisCaptchaKey, captchaText, EXPIRE_SECOND); + return captchaVO; + } + + /** + * 校验图形验证码 + */ + public ResponseDTO checkCaptcha(CaptchaForm captchaForm) { + if (StringUtils.isBlank(captchaForm.getCaptchaUuid()) || StringUtils.isBlank(captchaForm.getCaptchaCode())) { + return ResponseDTO.userErrorParam("请输入正确验证码"); + } + /* + * 1、校验redis里的验证码 + * 2、校验成功后,删除redis + */ + String redisCaptchaKey = redisService.generateRedisKey(RedisKeyConst.Support.CAPTCHA, captchaForm.getCaptchaUuid()); + String redisCaptchaCode = redisService.get(redisCaptchaKey); + if (StringUtils.isBlank(redisCaptchaCode)) { + return ResponseDTO.userErrorParam("验证码已过期,请刷新重试"); + } + if (!Objects.equals(redisCaptchaCode, captchaForm.getCaptchaCode())) { + return ResponseDTO.userErrorParam("验证码错误,请输入正确的验证码"); + } + // 删除已使用的验证码 + redisService.delete(redisCaptchaKey); + return ResponseDTO.ok(); + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/captcha/domain/CaptchaForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/captcha/domain/CaptchaForm.java new file mode 100644 index 0000000..e9b9fc9 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/captcha/domain/CaptchaForm.java @@ -0,0 +1,27 @@ +package net.lab1024.sa.base.module.support.captcha.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +/** + * 图形验证码 表单 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021-09-02 20:21:10 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +@Data +public class CaptchaForm { + + @Schema(description = "验证码") +// @NotBlank(message = "验证码不能为空") + private String captchaCode; + + @Schema(description = "验证码uuid标识") +// @NotBlank(message = "验证码uuid标识不能为空") + private String captchaUuid; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/captcha/domain/CaptchaVO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/captcha/domain/CaptchaVO.java new file mode 100644 index 0000000..a198a90 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/captcha/domain/CaptchaVO.java @@ -0,0 +1,29 @@ +package net.lab1024.sa.base.module.support.captcha.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 图形验证码 VO + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021/8/31 20:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class CaptchaVO { + + @Schema(description = "验证码唯一标识") + private String captchaUuid; + + @Schema(description = "验证码图片内容-生产环境无效") + private String captchaText; + + @Schema(description = "验证码Base64图片") + private String captchaBase64Image; + + @Schema(description = "过期时间(秒)") + private Long expireSeconds; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/constant/ChangeLogTypeEnum.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/constant/ChangeLogTypeEnum.java new file mode 100644 index 0000000..be53584 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/constant/ChangeLogTypeEnum.java @@ -0,0 +1,39 @@ +package net.lab1024.sa.base.module.support.changelog.constant; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 更新类型:[1:特大版本功能更新;2:功能更新;3:bug修复] + * + * @Author 卓大 + * @Date 2022-09-26T14:53:50 + * @Copyright 1024创新实验室 + */ + +@AllArgsConstructor +@Getter +public enum ChangeLogTypeEnum implements BaseEnum { + + /** + * 重大更新 + */ + MAJOR_UPDATE(1, "重大更新"), + + /** + * 功能更新 + */ + FUNCTION_UPDATE(2, "功能更新"), + + /** + * Bug修复 + */ + BUG_FIX(3, "Bug修复"), + + ; + + private final Integer value; + + private final String desc; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/controller/ChangeLogController.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/controller/ChangeLogController.java new file mode 100644 index 0000000..1b22a3c --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/controller/ChangeLogController.java @@ -0,0 +1,43 @@ +package net.lab1024.sa.base.module.support.changelog.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import net.lab1024.sa.base.common.controller.SupportBaseController; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import net.lab1024.sa.base.module.support.changelog.domain.form.ChangeLogQueryForm; +import net.lab1024.sa.base.module.support.changelog.domain.vo.ChangeLogVO; +import net.lab1024.sa.base.module.support.changelog.service.ChangeLogService; +import org.springframework.web.bind.annotation.*; + +/** + * 系统更新日志 Controller + * + * @Author 卓大 + * @Date 2022-09-26 14:53:50 + * @Copyright 1024创新实验室 + */ + +@RestController +@Tag(name = SwaggerTagConst.Support.CHANGE_LOG) +public class ChangeLogController extends SupportBaseController { + + @Resource + private ChangeLogService changeLogService; + + @Operation(summary = "分页查询 @author 卓大") + @PostMapping("/changeLog/queryPage") + public ResponseDTO> queryPage(@RequestBody @Valid ChangeLogQueryForm queryForm) { + return ResponseDTO.ok(changeLogService.queryPage(queryForm)); + } + + + @Operation(summary = "变更内容详情 @author 卓大") + @GetMapping("/changeLog/getDetail/{changeLogId}") + public ResponseDTO getDetail(@PathVariable Long changeLogId) { + return ResponseDTO.ok(changeLogService.getById(changeLogId)); + } +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/dao/ChangeLogDao.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/dao/ChangeLogDao.java new file mode 100644 index 0000000..09f72cc --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/dao/ChangeLogDao.java @@ -0,0 +1,38 @@ +package net.lab1024.sa.base.module.support.changelog.dao; + +import java.util.List; + +import net.lab1024.sa.base.module.support.changelog.domain.form.ChangeLogQueryForm; +import net.lab1024.sa.base.module.support.changelog.domain.vo.ChangeLogVO; +import net.lab1024.sa.base.module.support.changelog.domain.entity.ChangeLogEntity; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +/** + * 系统更新日志 Dao + * + * @Author 卓大 + * @Date 2022-09-26 14:53:50 + * @Copyright 1024创新实验室 + */ + +@Mapper +public interface ChangeLogDao extends BaseMapper { + + /** + * 分页 查询 + * + */ + List queryPage(Page page, @Param("queryForm") ChangeLogQueryForm queryForm); + + /** + * 根据版本查询 ChangeLog + * + */ + ChangeLogEntity selectByVersion(@Param("version") String version); + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/domain/entity/ChangeLogEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/domain/entity/ChangeLogEntity.java new file mode 100644 index 0000000..d0bf74b --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/domain/entity/ChangeLogEntity.java @@ -0,0 +1,68 @@ +package net.lab1024.sa.base.module.support.changelog.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.time.LocalDate; +import java.time.LocalDateTime; +import lombok.Data; + +/** + * 系统更新日志 + * + * @Author 卓大 + * @Date 2022-09-26 14:53:50 + * @Copyright 1024创新实验室 + */ + +@Data +@TableName("t_change_log") +public class ChangeLogEntity { + + /** + * 更新日志id + */ + @TableId(type = IdType.AUTO) + private Long changeLogId; + + /** + * 版本 + */ + private String version; + + /** + * 更新类型:[1:特大版本功能更新;2:功能更新;3:bug修复] + */ + private Integer type; + + /** + * 发布人 + */ + private String publishAuthor; + + /** + * 发布日期 + */ + private LocalDate publicDate; + + /** + * 更新内容 + */ + private String content; + + /** + * 跳转链接 + */ + private String link; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/domain/form/ChangeLogAddForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/domain/form/ChangeLogAddForm.java new file mode 100644 index 0000000..22979b2 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/domain/form/ChangeLogAddForm.java @@ -0,0 +1,47 @@ +package net.lab1024.sa.base.module.support.changelog.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; +import net.lab1024.sa.base.module.support.changelog.constant.ChangeLogTypeEnum; + +import java.time.LocalDate; + +/** + * 系统更新日志 新建表单 + * + * @Author 卓大 + * @Date 2022-09-26 14:53:50 + * @Copyright 1024创新实验室 + */ + +@Data +public class ChangeLogAddForm { + + @Schema(description = "版本", required = true) + @NotBlank(message = "版本 不能为空") + private String version; + + @SchemaEnum(value = ChangeLogTypeEnum.class, desc = "更新类型:[1:特大版本功能更新;2:功能更新;3:bug修复]") + @CheckEnum(value = ChangeLogTypeEnum.class, message = "更新类型:[1:特大版本功能更新;2:功能更新;3:bug修复] 错误", required = true) + private Integer type; + + @Schema(description = "发布人", required = true) + @NotBlank(message = "发布人 不能为空") + private String publishAuthor; + + @Schema(description = "发布日期", required = true) + @NotNull(message = "发布日期 不能为空") + private LocalDate publicDate; + + @Schema(description = "更新内容", required = true) + @NotBlank(message = "更新内容 不能为空") + private String content; + + @Schema(description = "跳转链接") + private String link; + +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/domain/form/ChangeLogQueryForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/domain/form/ChangeLogQueryForm.java new file mode 100644 index 0000000..699ff5d --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/domain/form/ChangeLogQueryForm.java @@ -0,0 +1,42 @@ +package net.lab1024.sa.base.module.support.changelog.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; +import net.lab1024.sa.base.module.support.changelog.constant.ChangeLogTypeEnum; + +import java.time.LocalDate; + +/** + * 系统更新日志 查询 + * + * @Author 卓大 + * @Date 2022-09-26 14:53:50 + * @Copyright 1024创新实验室 + */ + +@Data +public class ChangeLogQueryForm extends PageParam{ + + @SchemaEnum(value = ChangeLogTypeEnum.class, desc = "更新类型:[1:特大版本功能更新;2:功能更新;3:bug修复]") + @CheckEnum(value = ChangeLogTypeEnum.class, message = "更新类型:[1:特大版本功能更新;2:功能更新;3:bug修复] 错误") + private Integer type; + + @Schema(description = "关键字") + private String keyword; + + @Schema(description = "发布日期") + private LocalDate publicDateBegin; + + @Schema(description = "发布日期") + private LocalDate publicDateEnd; + + @Schema(description = "创建时间") + private LocalDate createTime; + + @Schema(description = "跳转链接") + private String link; + +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/domain/form/ChangeLogUpdateForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/domain/form/ChangeLogUpdateForm.java new file mode 100644 index 0000000..a907a23 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/domain/form/ChangeLogUpdateForm.java @@ -0,0 +1,51 @@ +package net.lab1024.sa.base.module.support.changelog.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; +import net.lab1024.sa.base.module.support.changelog.constant.ChangeLogTypeEnum; + +import java.time.LocalDate; + +/** + * 系统更新日志 更新表单 + * + * @Author 卓大 + * @Date 2022-09-26 14:53:50 + * @Copyright 1024创新实验室 + */ + +@Data +public class ChangeLogUpdateForm { + + @Schema(description = "更新日志id", required = true) + @NotNull(message = "更新日志id 不能为空") + private Long changeLogId; + + @Schema(description = "版本", required = true) + @NotBlank(message = "版本 不能为空") + private String version; + + @SchemaEnum(value = ChangeLogTypeEnum.class, desc = "更新类型:[1:特大版本功能更新;2:功能更新;3:bug修复]") + @CheckEnum(value = ChangeLogTypeEnum.class, message = "更新类型:[1:特大版本功能更新;2:功能更新;3:bug修复] 错误", required = true) + private Integer type; + + @Schema(description = "发布人", required = true) + @NotBlank(message = "发布人 不能为空") + private String publishAuthor; + + @Schema(description = "发布日期", required = true) + @NotNull(message = "发布日期 不能为空") + private LocalDate publicDate; + + @Schema(description = "更新内容", required = true) + @NotBlank(message = "更新内容 不能为空") + private String content; + + @Schema(description = "跳转链接") + private String link; + +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/domain/vo/ChangeLogVO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/domain/vo/ChangeLogVO.java new file mode 100644 index 0000000..e8930d0 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/domain/vo/ChangeLogVO.java @@ -0,0 +1,49 @@ +package net.lab1024.sa.base.module.support.changelog.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +import lombok.Data; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.module.support.changelog.constant.ChangeLogTypeEnum; + +/** + * 系统更新日志 列表VO + * + * @Author 卓大 + * @Date 2022-09-26 14:53:50 + * @Copyright 1024创新实验室 + */ + +@Data +public class ChangeLogVO { + + private Long changeLogId; + + @Schema(description = "版本") + private String version; + + @SchemaEnum(value = ChangeLogTypeEnum.class, desc = "更新类型:[1:特大版本功能更新;2:功能更新;3:bug修复]") + private Integer type; + + @Schema(description = "发布人") + private String publishAuthor; + + @Schema(description = "发布日期") + private LocalDate publicDate; + + @Schema(description = "更新内容") + private String content; + + @Schema(description = "跳转链接") + private String link; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "更新时间") + private LocalDateTime updateTime; + +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/service/ChangeLogService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/service/ChangeLogService.java new file mode 100644 index 0000000..b8d2047 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/service/ChangeLogService.java @@ -0,0 +1,97 @@ +package net.lab1024.sa.base.module.support.changelog.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.module.support.changelog.dao.ChangeLogDao; +import net.lab1024.sa.base.module.support.changelog.domain.entity.ChangeLogEntity; +import net.lab1024.sa.base.module.support.changelog.domain.form.ChangeLogAddForm; +import net.lab1024.sa.base.module.support.changelog.domain.form.ChangeLogQueryForm; +import net.lab1024.sa.base.module.support.changelog.domain.form.ChangeLogUpdateForm; +import net.lab1024.sa.base.module.support.changelog.domain.vo.ChangeLogVO; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 系统更新日志 Service + * + * @Author 卓大 + * @Date 2022-09-26 14:53:50 + * @Copyright 1024创新实验室 + */ + +@Service +public class ChangeLogService { + + @Resource + private ChangeLogDao changeLogDao; + + /** + * 分页查询 + */ + public PageResult queryPage(ChangeLogQueryForm queryForm) { + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List list = changeLogDao.queryPage(page, queryForm); + return SmartPageUtil.convert2PageResult(page, list); + } + + /** + * 添加 + */ + public synchronized ResponseDTO add(ChangeLogAddForm addForm) { + ChangeLogEntity existVersion = changeLogDao.selectByVersion(addForm.getVersion()); + if (existVersion != null) { + return ResponseDTO.userErrorParam("此版本已经存在"); + } + + ChangeLogEntity changeLogEntity = SmartBeanUtil.copy(addForm, ChangeLogEntity.class); + changeLogDao.insert(changeLogEntity); + return ResponseDTO.ok(); + } + + /** + * 更新 + */ + public synchronized ResponseDTO update(ChangeLogUpdateForm updateForm) { + ChangeLogEntity existVersion = changeLogDao.selectByVersion(updateForm.getVersion()); + if (existVersion != null && !updateForm.getChangeLogId().equals(existVersion.getChangeLogId())) { + return ResponseDTO.userErrorParam("此版本已经存在"); + } + ChangeLogEntity changeLogEntity = SmartBeanUtil.copy(updateForm, ChangeLogEntity.class); + changeLogDao.updateById(changeLogEntity); + return ResponseDTO.ok(); + } + + /** + * 批量删除 + */ + public synchronized ResponseDTO batchDelete(List idList) { + if (CollectionUtils.isEmpty(idList)) { + return ResponseDTO.ok(); + } + + changeLogDao.deleteBatchIds(idList); + return ResponseDTO.ok(); + } + + /** + * 单个删除 + */ + public synchronized ResponseDTO delete(Long changeLogId) { + if (null == changeLogId) { + return ResponseDTO.ok(); + } + + changeLogDao.deleteById(changeLogId); + return ResponseDTO.ok(); + } + + public ChangeLogVO getById(Long changeLogId) { + return SmartBeanUtil.copy(changeLogDao.selectById(changeLogId), ChangeLogVO.class); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/constant/CodeDeleteEnum.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/constant/CodeDeleteEnum.java new file mode 100644 index 0000000..ac0e007 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/constant/CodeDeleteEnum.java @@ -0,0 +1,38 @@ +package net.lab1024.sa.base.module.support.codegenerator.constant; + +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 删除类型 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-06-30 22:15:38 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public enum CodeDeleteEnum implements BaseEnum { + + SINGLE("Single", "单个删除"), + BATCH("Batch", "批量删除"), + SINGLE_AND_BATCH("SingleAndBatch", "单个和批量删除"); + + private String value; + + private String desc; + + CodeDeleteEnum(String value, String desc) { + this.value = value; + this.desc = desc; + } + + @Override + public Object getValue() { + return value; + } + + @Override + public String getDesc() { + return desc; + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/constant/CodeFrontComponentEnum.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/constant/CodeFrontComponentEnum.java new file mode 100644 index 0000000..10c8779 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/constant/CodeFrontComponentEnum.java @@ -0,0 +1,52 @@ +package net.lab1024.sa.base.module.support.codegenerator.constant; + +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 前端组件类型 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-06-30 20:15:38 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public enum CodeFrontComponentEnum implements BaseEnum { + + INPUT("Input", "输入框"), + + INPUT_NUMBER("InputNumber", "数字输入框"), + + TEXTAREA("Textarea", " 文本"), + + BOOLEAN_SELECT("BooleanSelect", "布尔下拉框"), + + ENUM_SELECT("SmartEnumSelect", "枚举下拉"), + + DICT_SELECT("DictSelect", "字典下拉"), + + DATE("Date", "日期选择"), + + DATE_TIME("DateTime", "时间选择"), + + FILE_UPLOAD("FileUpload", "文件上传"); + + private String value; + + private String desc; + + CodeFrontComponentEnum(String value, String desc) { + this.value = value; + this.desc = desc; + } + + @Override + public String getValue() { + return value; + } + + @Override + public String getDesc() { + return desc; + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/constant/CodeGeneratorConstant.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/constant/CodeGeneratorConstant.java new file mode 100644 index 0000000..1b2fe58 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/constant/CodeGeneratorConstant.java @@ -0,0 +1,30 @@ +package net.lab1024.sa.base.module.support.codegenerator.constant; + +/** + * 常量 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022-06-30 22:15:38 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class CodeGeneratorConstant { + + /** + * 主键 + */ + public final static String PRIMARY_KEY = "PRI"; + + /** + * 自增 + */ + public final static String AUTO_INCREMENT = "auto_increment"; + + /** + * 默认逻辑删除字段名称 + */ + public static String DELETED_FLAG = "deleted_flag"; + + +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/constant/CodeGeneratorPageTypeEnum.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/constant/CodeGeneratorPageTypeEnum.java new file mode 100644 index 0000000..536f3b8 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/constant/CodeGeneratorPageTypeEnum.java @@ -0,0 +1,38 @@ +package net.lab1024.sa.base.module.support.codegenerator.constant; + +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 页面类型 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-06-29 19:11:22 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public enum CodeGeneratorPageTypeEnum implements BaseEnum { + + MODAL("modal", "弹窗"), + DRAWER("drawer", "抽屉"), + PAGE("page", "新页面"); + + private String value; + + private String desc; + + CodeGeneratorPageTypeEnum(String value, String desc) { + this.value = value; + this.desc = desc; + } + + @Override + public Object getValue() { + return value; + } + + @Override + public String getDesc() { + return desc; + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/constant/CodeQueryFieldQueryTypeEnum.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/constant/CodeQueryFieldQueryTypeEnum.java new file mode 100644 index 0000000..2e6cbd7 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/constant/CodeQueryFieldQueryTypeEnum.java @@ -0,0 +1,43 @@ +package net.lab1024.sa.base.module.support.codegenerator.constant; + +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 查询条件类型 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-06-29 20:23:46 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public enum CodeQueryFieldQueryTypeEnum implements BaseEnum { + + LIKE("Like", "模糊查询"), + EQUAL("Equal", "等于"), + DATE_RANGE("DateRange", "日期范围"), + DATE("Date", "指定日期"), + ENUM("Enum", "枚举"), + + DICT("Dict", "字典"), + ; + + private String value; + + private String desc; + + CodeQueryFieldQueryTypeEnum(String value, String desc) { + this.value = value; + this.desc = desc; + } + + @Override + public Object getValue() { + return value; + } + + @Override + public String getDesc() { + return desc; + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/controller/CodeGeneratorController.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/controller/CodeGeneratorController.java new file mode 100644 index 0000000..80a1948 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/controller/CodeGeneratorController.java @@ -0,0 +1,97 @@ +package net.lab1024.sa.base.module.support.codegenerator.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import net.lab1024.sa.base.common.controller.SupportBaseController; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartResponseUtil; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorPreviewForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.TableQueryForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.vo.TableColumnVO; +import net.lab1024.sa.base.module.support.codegenerator.domain.vo.TableConfigVO; +import net.lab1024.sa.base.module.support.codegenerator.domain.vo.TableVO; +import net.lab1024.sa.base.module.support.codegenerator.service.CodeGeneratorService; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.List; + +/** + * 代码生成 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-06-29 20:23:46 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Tag(name = SwaggerTagConst.Support.CODE_GENERATOR) +@Controller +public class CodeGeneratorController extends SupportBaseController { + + @Resource + private CodeGeneratorService codeGeneratorService; + + // ------------------- 查询 ------------------- + + @Operation(summary = "获取表的列 @author 卓大") + @GetMapping("/codeGenerator/table/getTableColumns/{table}") + @ResponseBody + public ResponseDTO> getTableColumns(@PathVariable String table) { + return ResponseDTO.ok(codeGeneratorService.getTableColumns(table)); + } + + @Operation(summary = "查询数据库的表 @author 卓大") + @PostMapping("/codeGenerator/table/queryTableList") + @ResponseBody + public ResponseDTO> queryTableList(@RequestBody @Valid TableQueryForm tableQueryForm) { + return ResponseDTO.ok(codeGeneratorService.queryTableList(tableQueryForm)); + } + + // ------------------- 配置 ------------------- + + @Operation(summary = "获取表的配置信息 @author 卓大") + @GetMapping("/codeGenerator/table/getConfig/{table}") + @ResponseBody + public ResponseDTO getTableConfig(@PathVariable String table) { + return ResponseDTO.ok(codeGeneratorService.getTableConfig(table)); + } + + @Operation(summary = "更新配置信息 @author 卓大") + @PostMapping("/codeGenerator/table/updateConfig") + @ResponseBody + public ResponseDTO updateConfig(@RequestBody @Valid CodeGeneratorConfigForm form) { + return codeGeneratorService.updateConfig(form); + } + + // ------------------- 生成 ------------------- + + @Operation(summary = "代码预览 @author 卓大") + @PostMapping("/codeGenerator/code/preview") + @ResponseBody + public ResponseDTO preview(@RequestBody @Valid CodeGeneratorPreviewForm form) { + return codeGeneratorService.preview(form); + } + + @Operation(summary = "代码下载 @author 卓大") + @GetMapping(value = "/codeGenerator/code/download/{tableName}", produces = "application/octet-stream") + public void download(@PathVariable String tableName, HttpServletResponse response) throws IOException { + + ResponseDTO download = codeGeneratorService.download(tableName); + + if (download.getOk()) { + SmartResponseUtil.setDownloadFileHeader(response, tableName + "_code.zip", (long) download.getData().length); + response.getOutputStream().write(download.getData()); + } else { + SmartResponseUtil.write(response, download); + } + } + +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/dao/CodeGeneratorConfigDao.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/dao/CodeGeneratorConfigDao.java new file mode 100644 index 0000000..b9763c6 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/dao/CodeGeneratorConfigDao.java @@ -0,0 +1,19 @@ +package net.lab1024.sa.base.module.support.codegenerator.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import net.lab1024.sa.base.module.support.codegenerator.domain.entity.CodeGeneratorConfigEntity; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.stereotype.Component; + +/** + * 表的 代码生成配置 Dao + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-09-23 20:15:38 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface CodeGeneratorConfigDao extends BaseMapper { + +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/dao/CodeGeneratorDao.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/dao/CodeGeneratorDao.java new file mode 100644 index 0000000..ab609ad --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/dao/CodeGeneratorDao.java @@ -0,0 +1,44 @@ +package net.lab1024.sa.base.module.support.codegenerator.dao; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.TableQueryForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.vo.TableColumnVO; +import net.lab1024.sa.base.module.support.codegenerator.domain.vo.TableVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * @Author 1024创新实验室: 罗伊 + * @Date 2022-06-30 22:15:38 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface CodeGeneratorDao { + + /** + * 分页查询表 + */ + List queryTableList(Page page, @Param("queryForm") TableQueryForm queryForm); + + /** + * 查询表是否存在 + * + * @param tableName + * @return + */ + long countByTableName(@Param("tableName") String tableName); + + + /** + * 查询表列信息 + * + * @param tableName + * @return + */ + List selectTableColumn(@Param("tableName") String tableName); +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/entity/CodeGeneratorConfigEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/entity/CodeGeneratorConfigEntity.java new file mode 100644 index 0000000..b686d54 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/entity/CodeGeneratorConfigEntity.java @@ -0,0 +1,73 @@ +package net.lab1024.sa.base.module.support.codegenerator.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 代码生成-配置 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022/6/23 21:59:22 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName("t_code_generator_config") +public class CodeGeneratorConfigEntity { + + /** + * 表名 + */ + @TableId(type = IdType.NONE) + private String tableName; + + /** + * 基础命名信息 + */ + private String basic; + + /** + * 字段列表 + */ + private String fields; + + /** + * 增加、修改 信息 + */ + private String insertAndUpdate; + + /** + * 删除 信息 + */ + private String deleteInfo; + + /** + * 查询字段 + */ + private String queryFields; + + /** + * 列表字段 + */ + private String tableFields; + + /** + * 详情 + */ + private String detail; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/form/CodeGeneratorConfigForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/form/CodeGeneratorConfigForm.java new file mode 100644 index 0000000..7d608c4 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/form/CodeGeneratorConfigForm.java @@ -0,0 +1,63 @@ +package net.lab1024.sa.base.module.support.codegenerator.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.*; + +import java.util.List; + +/** + * 代码生成 配置信息表单 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-06-29 20:23:46 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class CodeGeneratorConfigForm { + + @NotBlank(message = "表名 不能为空") + @Schema(description = "表名") + private String tableName; + + @Valid + @NotNull(message = "基础信息不能为空") + @Schema(description = "基础信息") + private CodeBasic basic; + + @Valid + @NotNull(message = "字段信息不能为空") + @Schema(description = "字段信息") + private List fields; + + @Valid + @NotNull(message = "增加、修改 信息 不能为空") + @Schema(description = "增加、修改 信息") + private CodeInsertAndUpdate insertAndUpdate; + + @Valid + @NotNull(message = "删除 信息 不能为空") + @Schema(description = "删除 信息") + private CodeDelete deleteInfo; + + @Valid + @Schema(description = "查询字段") + private List queryFields; + + @Valid + @Schema(description = "列表字段") + private List tableFields; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/form/CodeGeneratorPreviewForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/form/CodeGeneratorPreviewForm.java new file mode 100644 index 0000000..2b5bf23 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/form/CodeGeneratorPreviewForm.java @@ -0,0 +1,27 @@ +package net.lab1024.sa.base.module.support.codegenerator.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +/** + * 代码生成 预览 表单 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022/6/23 23:20:46 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class CodeGeneratorPreviewForm { + + @NotBlank(message = "模板文件 不能为空") + @Schema(description = "模板文件") + private String templateFile; + + @NotBlank(message = "表名 不能为空") + @Schema(description = "表名") + private String tableName; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/form/TableQueryForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/form/TableQueryForm.java new file mode 100644 index 0000000..bde89a8 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/form/TableQueryForm.java @@ -0,0 +1,23 @@ +package net.lab1024.sa.base.module.support.codegenerator.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; + + +/** + * 查询表数据 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-06-30 22:15:38 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class TableQueryForm extends PageParam { + + @Schema(description = "表名关键字") + private String tableNameKeywords; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeBasic.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeBasic.java new file mode 100644 index 0000000..878df4a --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeBasic.java @@ -0,0 +1,55 @@ +package net.lab1024.sa.base.module.support.codegenerator.domain.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 代码生成 基础数据 模型 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-06-30 22:15:38 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +@Data +public class CodeBasic { + + @Schema(description = "业务名称") + @NotBlank(message = "1.基础命名 基础命名 不能为空") + private String moduleName; + + @Schema(description = "java包名") + @NotBlank(message = "1.基础命名 java包名 不能为空") + private String javaPackageName; + + @Schema(description = "注释") + @NotBlank(message = "1.基础命名 注释 不能为空") + private String description; + + @Schema(description = "前端作者") + @NotBlank(message = "1.基础命名 前端作者 不能为空") + private String frontAuthor; + + @Schema(description = "前端时间") + @NotNull(message = "1.基础命名 前端时间 不能为空") + private LocalDateTime frontDate; + + @Schema(description = "后端作者") + @NotBlank(message = "1.基础命名 后端作者 不能为空") + private String backendAuthor; + + @Schema(description = "后端时间") + @NotNull(message = "1.基础命名 后端时间 不能为空") + private LocalDateTime backendDate; + + @Schema(description = "版权信息") + @NotNull(message = "1.基础命名 版权信息 不能为空") + private String copyright; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeDelete.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeDelete.java new file mode 100644 index 0000000..3813287 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeDelete.java @@ -0,0 +1,39 @@ +package net.lab1024.sa.base.module.support.codegenerator.domain.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; +import net.lab1024.sa.base.module.support.codegenerator.constant.CodeDeleteEnum; + +/** + * 代码生成 删除 模型 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-06-30 22:15:38 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +@Data +public class CodeDelete { + + @Schema(description = "是否支持删除 ") + @NotNull(message = "4.删除 是否支持删除 不能为空") + private Boolean isSupportDelete; + + @Schema(description = "是否为物理删除") + @NotNull(message = "4.删除 是否为物理删除 不能为空") + private Boolean isPhysicallyDeleted; + + @Schema(description = "删除类型") + @NotBlank(message = "4.删除 删除类型 不能为空") + @SchemaEnum(CodeDeleteEnum.class) + @CheckEnum(value = CodeDeleteEnum.class, message = "删除 删除类型 枚举值错误") + private String deleteEnum; + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeField.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeField.java new file mode 100644 index 0000000..2af5ac5 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeField.java @@ -0,0 +1,58 @@ +package net.lab1024.sa.base.module.support.codegenerator.domain.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 代码生成 基础字段 模型 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-06-30 22:15:38 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +@Data +public class CodeField { + + @Schema(description = "列") + @NotBlank(message = " 2.字段列表 列名 不能为空") + private String columnName; + + @Schema(description = "列备注") + private String columnComment; + + @Schema(description = "字段名词") + @NotBlank(message = "2.字段列表 字段名词 不能为空") + private String label; + + @Schema(description = "字段命名") + @NotBlank(message = "2.字段列表 字段命名 不能为空") + private String fieldName; + + @Schema(description = "java类型") + @NotBlank(message = "2.字段列表 java类型 不能为空") + private String javaType; + + @Schema(description = "js类型") + @NotBlank(message = "2.字段列表 js类型 不能为空") + private String jsType; + + @Schema(description = "字典key") + private String dict; + + @Schema(description = "枚举名称") + private String enumName; + + @Schema(description = "主键") + @NotNull(message = "2.字段列表 主键 不能为空") + private Boolean primaryKeyFlag; + + @Schema(description = "自增") + @NotNull(message = "2.字段列表 自增 不能为空") + private Boolean autoIncreaseFlag; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeInsertAndUpdate.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeInsertAndUpdate.java new file mode 100644 index 0000000..3ff8dfe --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeInsertAndUpdate.java @@ -0,0 +1,42 @@ +package net.lab1024.sa.base.module.support.codegenerator.domain.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; +import net.lab1024.sa.base.module.support.codegenerator.constant.CodeGeneratorPageTypeEnum; + +import java.util.List; + +/** + * 代码生成 增加、修改 模型 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-06-30 22:15:38 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +@Data +public class CodeInsertAndUpdate { + + @NotNull(message = "3.增加、修改 是否支持增加、修改 不能为空") + private Boolean isSupportInsertAndUpdate; + + @SchemaEnum(CodeGeneratorPageTypeEnum.class) + @CheckEnum(value = CodeGeneratorPageTypeEnum.class, message = "3.增加、修改 增加、修改 页面类型 枚举值错误") + private String pageType; + + @Schema(description = "宽度") + private String width; + + @NotNull(message = "3.增加、修改 每行字段数量 不能为空") + @Schema(description = "每行字段数量") + private Integer countPerLine; + + @Schema(description = "字段列表") + private List fieldList; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeInsertAndUpdateField.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeInsertAndUpdateField.java new file mode 100644 index 0000000..1d3a8b1 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeInsertAndUpdateField.java @@ -0,0 +1,45 @@ +package net.lab1024.sa.base.module.support.codegenerator.domain.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; +import net.lab1024.sa.base.module.support.codegenerator.constant.CodeFrontComponentEnum; +import net.lab1024.sa.base.module.support.codegenerator.constant.CodeGeneratorPageTypeEnum; + +/** + * 代码生成 增加、修改的字段 模型 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-06-30 22:15:38 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +@Data +public class CodeInsertAndUpdateField { + + @NotBlank(message = "3.增加、修改 列名 不能为空") + @Schema(description = "列名") + private String columnName; + + @NotNull(message = "3.增加、修改 必须 不能为空") + @Schema(description = "必须") + private Boolean requiredFlag; + + @NotNull(message = "3.增加、修改 插入标识 不能为空") + @Schema(description = "插入标识") + private Boolean insertFlag; + + @NotNull(message = "3.增加、修改 更新标识 不能为空") + @Schema(description = "更新标识") + private Boolean updateFlag; + + @SchemaEnum(value = CodeFrontComponentEnum.class) + @CheckEnum(value = CodeFrontComponentEnum.class, message = "3.增加、修改 组件类型 枚举值错误", required = true) + private String frontComponent; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeQueryField.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeQueryField.java new file mode 100644 index 0000000..10af085 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeQueryField.java @@ -0,0 +1,47 @@ +package net.lab1024.sa.base.module.support.codegenerator.domain.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; +import net.lab1024.sa.base.module.support.codegenerator.constant.CodeQueryFieldQueryTypeEnum; + +import java.util.List; + +/** + * 代码生成 查询条件 模型 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-06-30 22:15:38 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +@Data +public class CodeQueryField { + + @NotBlank(message = "5、查询条件 条件名称 不能为空") + @Schema(description = "条件名称") + private String label; + + @NotBlank(message = "5、查询条件 字段名 不能为空") + @Schema(description = "字段名") + private String fieldName; + + @SchemaEnum(CodeQueryFieldQueryTypeEnum.class) + @CheckEnum(value = CodeQueryFieldQueryTypeEnum.class, message = "5、查询条件 查询条件 查询类型 枚举值错误") + private String queryTypeEnum; + + @NotEmpty(message = "5、查询条件 列 不能为空") + @Schema(description = "列") + private List columnNameList; + + @NotBlank(message = "5、查询条件 宽度 不能为空") + @Schema(description = "宽度") + private String width; + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeTableField.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeTableField.java new file mode 100644 index 0000000..04816d2 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeTableField.java @@ -0,0 +1,45 @@ +package net.lab1024.sa.base.module.support.codegenerator.domain.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 代码生成 列表表格 模型 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-06-30 22:15:38 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +@Data +public class CodeTableField { + + @NotBlank(message = "6、列表 列名 不能为空") + @Schema(description = "列名") + private String columnName; + + @NotBlank(message = "6、列表 字段名词 不能为空") + @Schema(description = "字段名词") + private String label; + + @NotBlank(message = "6、列表 字段命名 不能为空") + @Schema(description = "字段命名") + private String fieldName; + + @NotNull(message = "6、列表 列表显示 不能为空") + @Schema(description = "列表显示") + private Boolean showFlag; + + @Schema(description = "宽度") + private Integer width; + + @NotNull(message = "6、列表 自动省略标识 不能为空") + @Schema(description = "自动省略标识") + private Boolean ellipsisFlag; + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/vo/TableColumnVO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/vo/TableColumnVO.java new file mode 100644 index 0000000..b28c39e --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/vo/TableColumnVO.java @@ -0,0 +1,42 @@ + +package net.lab1024.sa.base.module.support.codegenerator.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 列 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/21 21:07:58 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +@Data +public class TableColumnVO { + + @Schema(description = "列名") + private String columnName; + + @Schema(description = "列描述") + private String columnComment; + + @Schema(description = "columnKey") + private String columnKey; + + @Schema(description = "extra") + private String extra; + + @Schema(description = "是否为空") + private String isNullable; + + @Schema(description = "数据类型varchar") + private String dataType; + + @Schema(description = "列类型varchar(50)") + private String columnType; + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/vo/TableConfigVO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/vo/TableConfigVO.java new file mode 100644 index 0000000..2dfe0e2 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/vo/TableConfigVO.java @@ -0,0 +1,40 @@ + +package net.lab1024.sa.base.module.support.codegenerator.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.*; + +import java.util.List; + +/** + * 表的配置信息 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/21 21:07:58 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +@Data +public class TableConfigVO { + + @Schema(description = "基础命名信息") + private CodeBasic basic; + + @Schema(description = "字段列") + private List fields; + + @Schema(description = "增加、修改 信息") + private CodeInsertAndUpdate insertAndUpdate; + + @Schema(description = "删除 信息") + private CodeDelete deleteInfo; + + @Schema(description = "查询字段") + private List queryFields; + + @Schema(description = "列表字段") + private List tableFields; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/vo/TableVO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/vo/TableVO.java new file mode 100644 index 0000000..8b6d477 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/vo/TableVO.java @@ -0,0 +1,37 @@ + +package net.lab1024.sa.base.module.support.codegenerator.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 表信息 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/21 18:07:58 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +@Data +public class TableVO { + + @Schema(description = "表名") + private String tableName; + + @Schema(description = "表备注") + private String tableComment; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "更新时间") + private LocalDateTime updateTime; + + @Schema(description = "配置时间") + private LocalDateTime configTime; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/CodeGeneratorService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/CodeGeneratorService.java new file mode 100644 index 0000000..6c96e30 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/CodeGeneratorService.java @@ -0,0 +1,228 @@ +package net.lab1024.sa.base.module.support.codegenerator.service; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.module.support.codegenerator.constant.CodeGeneratorConstant; +import net.lab1024.sa.base.module.support.codegenerator.dao.CodeGeneratorConfigDao; +import net.lab1024.sa.base.module.support.codegenerator.dao.CodeGeneratorDao; +import net.lab1024.sa.base.module.support.codegenerator.domain.entity.CodeGeneratorConfigEntity; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorPreviewForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.TableQueryForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.*; +import net.lab1024.sa.base.module.support.codegenerator.domain.vo.TableColumnVO; +import net.lab1024.sa.base.module.support.codegenerator.domain.vo.TableConfigVO; +import net.lab1024.sa.base.module.support.codegenerator.domain.vo.TableVO; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; + +import java.io.ByteArrayOutputStream; +import java.util.List; +import java.util.Optional; + +/** + * 代码生成器 Service + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-06-30 22:15:38 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@Service +public class CodeGeneratorService { + + @Resource + private CodeGeneratorDao codeGeneratorDao; + + @Resource + private CodeGeneratorConfigDao codeGeneratorConfigDao; + + @Resource + private CodeGeneratorTemplateService codeGeneratorTemplateService; + + + /** + * 列信息 + * + * @param tableName + * @return + */ + public List getTableColumns(String tableName) { + return codeGeneratorDao.selectTableColumn(tableName); + } + + + /** + * 查询数据库表数据 + * + * @param tableQueryForm + * @return + */ + public PageResult queryTableList(TableQueryForm tableQueryForm) { + Page page = SmartPageUtil.convert2PageQuery(tableQueryForm); + List tableVOList = codeGeneratorDao.queryTableList(page, tableQueryForm); + return SmartPageUtil.convert2PageResult(page, tableVOList); + } + + /** + * 获取 表的 配置信息 + * + * @param table + * @return + */ + public TableConfigVO getTableConfig(String table) { + + TableConfigVO config = new TableConfigVO(); + + CodeGeneratorConfigEntity codeGeneratorConfigEntity = codeGeneratorConfigDao.selectById(table); + if (codeGeneratorConfigEntity == null) { + return config; + } + + if (SmartStringUtil.isNotEmpty(codeGeneratorConfigEntity.getBasic())) { + CodeBasic basic = JSON.parseObject(codeGeneratorConfigEntity.getBasic(), CodeBasic.class); + config.setBasic(basic); + } + + if (SmartStringUtil.isNotEmpty(codeGeneratorConfigEntity.getFields())) { + List fields = JSONArray.parseArray(codeGeneratorConfigEntity.getFields(), CodeField.class); + config.setFields(fields); + } + + if (SmartStringUtil.isNotEmpty(codeGeneratorConfigEntity.getInsertAndUpdate())) { + CodeInsertAndUpdate insertAndUpdate = JSON.parseObject(codeGeneratorConfigEntity.getInsertAndUpdate(), CodeInsertAndUpdate.class); + config.setInsertAndUpdate(insertAndUpdate); + } + + if (SmartStringUtil.isNotEmpty(codeGeneratorConfigEntity.getDeleteInfo())) { + CodeDelete deleteInfo = JSON.parseObject(codeGeneratorConfigEntity.getDeleteInfo(), CodeDelete.class); + config.setDeleteInfo(deleteInfo); + } + + if (SmartStringUtil.isNotEmpty(codeGeneratorConfigEntity.getQueryFields())) { + List queryFields = JSONArray.parseArray(codeGeneratorConfigEntity.getQueryFields(), CodeQueryField.class); + config.setQueryFields(queryFields); + } + + if (SmartStringUtil.isNotEmpty(codeGeneratorConfigEntity.getTableFields())) { + List tableFields = JSONArray.parseArray(codeGeneratorConfigEntity.getTableFields(), CodeTableField.class); + config.setTableFields(tableFields); + } + + return config; + } + + /** + * 更新配置 + * + * @param form + * @return + */ + public synchronized ResponseDTO updateConfig(CodeGeneratorConfigForm form) { + long existCount = codeGeneratorDao.countByTableName(form.getTableName()); + if (existCount == 0) { + return ResponseDTO.userErrorParam("表不存在,请联系后端查看下数据库"); + } + + CodeGeneratorConfigEntity codeGeneratorConfigEntity = codeGeneratorConfigDao.selectById(form.getTableName()); + boolean updateFlag = true; + if (codeGeneratorConfigEntity == null) { + codeGeneratorConfigEntity = new CodeGeneratorConfigEntity(); + updateFlag = false; + } + + // 校验假删,必须有 deleted_flag 字段 + List tableColumns = getTableColumns(form.getTableName()); + if (null != form.getDeleteInfo() && form.getDeleteInfo().getIsSupportDelete() && !form.getDeleteInfo().getIsPhysicallyDeleted()) { + Optional any = tableColumns.stream().filter(e -> e.getColumnName().equals(CodeGeneratorConstant.DELETED_FLAG)).findAny(); + if (!any.isPresent()) { + return ResponseDTO.userErrorParam("表结构中没有假删字段:" + CodeGeneratorConstant.DELETED_FLAG + ",请仔细排查"); + } + } + + // 校验表必须有主键 + if(!tableColumns.stream().filter( e -> "PRI".equalsIgnoreCase(e.getColumnKey())).findAny().isPresent()){ + return ResponseDTO.userErrorParam("表必须有主键,请联系后端查看下数据库表结构"); + } + + codeGeneratorConfigEntity.setTableName(form.getTableName()); + codeGeneratorConfigEntity.setBasic(JSON.toJSONString(form.getBasic())); + codeGeneratorConfigEntity.setFields(JSONArray.toJSONString(form.getFields())); + codeGeneratorConfigEntity.setInsertAndUpdate(JSON.toJSONString(form.getInsertAndUpdate())); + codeGeneratorConfigEntity.setDeleteInfo(JSON.toJSONString(form.getDeleteInfo())); + codeGeneratorConfigEntity.setQueryFields(JSONArray.toJSONString(form.getQueryFields())); + codeGeneratorConfigEntity.setTableFields(JSONArray.toJSONString(form.getTableFields())); + + if (updateFlag) { + codeGeneratorConfigDao.updateById(codeGeneratorConfigEntity); + } else { + codeGeneratorConfigDao.insert(codeGeneratorConfigEntity); + } + return ResponseDTO.ok(); + } + + /** + * 预览 + * + * @param form + * @return + */ + public ResponseDTO preview(CodeGeneratorPreviewForm form) { + long existCount = codeGeneratorDao.countByTableName(form.getTableName()); + if (existCount == 0) { + return ResponseDTO.userErrorParam("表不存在,请联系后端查看下数据库"); + } + + CodeGeneratorConfigEntity codeGeneratorConfigEntity = codeGeneratorConfigDao.selectById(form.getTableName()); + if (codeGeneratorConfigEntity == null) { + return ResponseDTO.userErrorParam("配置信息不存在,请先进行配置"); + } + + List columns = getTableColumns(form.getTableName()); + if (CollectionUtils.isEmpty(columns)) { + return ResponseDTO.userErrorParam("表没有列信息无法生成"); + } + + String result = codeGeneratorTemplateService.generate(form.getTableName(), form.getTemplateFile(), codeGeneratorConfigEntity); + return ResponseDTO.ok(result); + + } + + /** + * 下载代码 + * @param tableName + * @return + */ + public ResponseDTO download(String tableName) { + if (SmartStringUtil.isBlank(tableName)) { + return ResponseDTO.userErrorParam("表名不能为空"); + } + + long existCount = codeGeneratorDao.countByTableName(tableName); + if (existCount == 0) { + return ResponseDTO.userErrorParam("表不存在,请联系后端查看下数据库"); + } + + CodeGeneratorConfigEntity codeGeneratorConfigEntity = codeGeneratorConfigDao.selectById(tableName); + if (codeGeneratorConfigEntity == null) { + return ResponseDTO.userErrorParam("配置信息不存在,请先进行配置"); + } + + List columns = getTableColumns(tableName); + if (CollectionUtils.isEmpty(columns)) { + return ResponseDTO.userErrorParam("表没有列信息无法生成"); + } + ByteArrayOutputStream out = new ByteArrayOutputStream(); + codeGeneratorTemplateService.zipGeneratedFiles(out, tableName, codeGeneratorConfigEntity); + return ResponseDTO.ok(out.toByteArray()); + } +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/CodeGeneratorTemplateService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/CodeGeneratorTemplateService.java new file mode 100644 index 0000000..930d73b --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/CodeGeneratorTemplateService.java @@ -0,0 +1,239 @@ +package net.lab1024.sa.base.module.support.codegenerator.service; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.util.ZipUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.google.common.base.CaseFormat; +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.module.support.codegenerator.domain.entity.CodeGeneratorConfigEntity; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.*; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.CodeGenerateBaseVariableService; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.backend.*; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.backend.domain.*; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.front.ApiVariableService; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.front.ConstVariableService; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.front.FormVariableService; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.front.ListVariableService; +import net.lab1024.sa.base.module.support.codegenerator.util.CodeGeneratorTool; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.velocity.Template; +import org.apache.velocity.app.Velocity; +import org.apache.velocity.app.VelocityEngine; +import org.apache.velocity.tools.ToolContext; +import org.apache.velocity.tools.ToolManager; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.io.OutputStream; +import java.io.StringWriter; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 代码生成器 模板 Service + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-06-30 22:15:38 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +@Service +@Slf4j +public class CodeGeneratorTemplateService { + + + private Map map = new HashMap<>(); + + @PostConstruct + public void init() { + // 后端 + map.put("java/domain/entity/Entity.java", new EntityVariableService()); + map.put("java/domain/form/AddForm.java", new AddFormVariableService()); + map.put("java/domain/form/UpdateForm.java", new UpdateFormVariableService()); + map.put("java/domain/form/QueryForm.java", new QueryFormVariableService()); + map.put("java/domain/vo/VO.java", new VOVariableService()); + map.put("java/controller/Controller.java", new ControllerVariableService()); + map.put("java/service/Service.java", new ServiceVariableService()); + map.put("java/manager/Manager.java", new ManagerVariableService()); + map.put("java/dao/Dao.java", new DaoVariableService()); + map.put("java/mapper/Mapper.xml", new MapperVariableService()); + // 菜单 SQL + map.put("java/sql/Menu.sql", new MenuVariableService()); + // 前端 + map.put("js/api.js", new ApiVariableService()); + map.put("js/const.js", new ConstVariableService()); + map.put("js/list.vue", new ListVariableService()); + map.put("js/form.vue", new FormVariableService()); + } + + public void zipGeneratedFiles(OutputStream outputStream, String tableName, CodeGeneratorConfigEntity codeGeneratorConfigEntity) { + String uuid = UUID.randomUUID().toString(); + File dir = new File(uuid); + + // 1、生产文件 + CodeBasic basic = JSON.parseObject(codeGeneratorConfigEntity.getBasic(), CodeBasic.class); + String moduleName = basic.getModuleName(); + + for (Map.Entry entry : map.entrySet()) { + try { + String templateFile = entry.getKey(); + String upperCamel = new CodeGeneratorTool().lowerCamel2UpperCamel(moduleName); + String lowerHyphen = new CodeGeneratorTool().lowerCamel2LowerHyphen(moduleName); + String[] templateSplit = templateFile.split("/"); + String fileName = templateFile.startsWith("java") ? upperCamel + templateSplit[templateSplit.length - 1] : lowerHyphen + "-" + templateSplit[templateSplit.length - 1]; + String fullPathFileName = templateFile.replaceAll(templateSplit[templateSplit.length - 1], fileName); + fullPathFileName = fullPathFileName.replaceAll("java/", "java/" + basic.getModuleName().toLowerCase() + "/"); + fullPathFileName = fullPathFileName.replaceAll("js/", "js/" + lowerHyphen + "/"); + + String fileContent = generate(tableName, templateFile, codeGeneratorConfigEntity); + File file = new File(uuid + "/" + fullPathFileName); + file.getParentFile().mkdirs(); + FileUtil.appendUtf8String(fileContent, file); + } catch (IORuntimeException e) { + log.error(e.getMessage(), e); + } + } + + // 2、后端的枚举文件 + List fields = JSONArray.parseArray(codeGeneratorConfigEntity.getFields(), CodeField.class); + if (CollectionUtils.isNotEmpty(fields)) { + List enumFiledList = fields.stream().filter(e -> SmartStringUtil.isNotBlank(e.getEnumName())).collect(Collectors.toList()); + for (CodeField codeField : enumFiledList) { + Map variablesMap = new HashMap<>(); + + String enumName = CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, codeField.getEnumName()); + if (!enumName.endsWith("Enum")) { + enumName = enumName + "Enum"; + } + variablesMap.put("enumName", enumName); + variablesMap.put("enumDesc", codeField.getColumnComment()); + variablesMap.put("enumJavaType", codeField.getJavaType()); + variablesMap.put("basic", basic); + variablesMap.put("packageName", basic.getJavaPackageName() + ".constant"); + + String fileContent = render("code-generator-template/java/constant/enum.java.vm", variablesMap); + File file = new File(uuid + "/java/" + basic.getModuleName().toLowerCase() + "/constant/" + enumName + ".java"); + file.getParentFile().mkdirs(); + FileUtil.appendUtf8String(fileContent, file); + } + } + + + ZipUtil.zip(outputStream, StandardCharsets.UTF_8, false, null, dir); + + FileUtil.del(dir); + + } + + + public String generate(String tableName, String file, CodeGeneratorConfigEntity codeGeneratorConfigEntity) { + + // -------------------- 1 校验不支持的代码生成,比如增加、删除等 -------------------- + + String finalFile = file; + Optional optional = map.keySet().stream().filter(e -> e.contains(finalFile)).findFirst(); + if (!optional.isPresent()) { + return "不存在此模板!"; + } + + file = optional.get(); + CodeGenerateBaseVariableService codeGenerateBaseVariableService = map.get(file); + if (codeGenerateBaseVariableService == null) { + return "代码生成Service不存在,请检查相关代码!"; + } + + CodeBasic basic = JSON.parseObject(codeGeneratorConfigEntity.getBasic(), CodeBasic.class); + List fields = JSONArray.parseArray(codeGeneratorConfigEntity.getFields(), CodeField.class); + CodeInsertAndUpdate insertAndUpdate = JSON.parseObject(codeGeneratorConfigEntity.getInsertAndUpdate(), CodeInsertAndUpdate.class); + CodeDelete deleteInfo = JSON.parseObject(codeGeneratorConfigEntity.getDeleteInfo(), CodeDelete.class); + List queryFields = JSONArray.parseArray(codeGeneratorConfigEntity.getQueryFields(), CodeQueryField.class); + List tableFields = JSONArray.parseArray(codeGeneratorConfigEntity.getTableFields(), CodeTableField.class); + tableFields.forEach(e -> e.setWidth(e.getWidth() == null ? 0 : e.getWidth())); + + CodeGeneratorConfigForm form = CodeGeneratorConfigForm.builder().basic(basic).fields(fields).insertAndUpdate(insertAndUpdate).deleteInfo(deleteInfo).queryFields(queryFields).tableFields(tableFields).deleteInfo(deleteInfo).build(); + form.setTableName(tableName); + if (!codeGenerateBaseVariableService.isSupport(form)) { + return "业务不需要此功能,故没有生成代码;"; + } + + // -------------------- 2 通用模板的变量 -------------------- + Map variablesMap = new HashMap<>(); + + + Map basicMap = BeanUtil.beanToMap(basic); + basicMap.put("frontDate", DateUtil.formatLocalDateTime(basic.getFrontDate())); + basicMap.put("backendDate", DateUtil.formatLocalDateTime(basic.getBackendDate())); + + variablesMap.put("basic", basicMap); + variablesMap.put("fields", fields); + variablesMap.put("insertAndUpdate", insertAndUpdate); + variablesMap.put("deleteInfo", deleteInfo); + variablesMap.put("queryFields", queryFields); + variablesMap.put("tableFields", tableFields); + variablesMap.put("tableName", tableName); + + //名词的大写开头和小写开头 + HashMap names = new HashMap<>(); + names.put("lowerCamel", CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, basic.getModuleName())); + names.put("upperCamel", CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_CAMEL, basic.getModuleName())); + names.put("lowerHyphenCamel", CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_HYPHEN, basic.getModuleName())); + variablesMap.put("name", names); + + //主键字段名称和java类型 + CodeField primaryKeycodeField = fields.stream().filter(e -> e.getPrimaryKeyFlag()).findFirst().get(); + if (primaryKeycodeField != null) { + variablesMap.put("primaryKeyJavaType", primaryKeycodeField.getJavaType()); + variablesMap.put("primaryKeyFieldName", primaryKeycodeField.getFieldName()); + variablesMap.put("primaryKeyColumnName", primaryKeycodeField.getColumnName()); + } + + // -------------------- 3、针对此 模板 的特殊变量 -------------------- + + Map specialVariables = codeGenerateBaseVariableService.getInjectVariablesMap(form); + variablesMap.putAll(specialVariables); + + // -------------------- 4、模板 生成代码 -------------------- + + return render("code-generator-template/" + file + ".vm", variablesMap); + } + + /** + * 渲染 + * + * @param templateFile + * @param variablesMap + * @return + */ + private String render(String templateFile, Map variablesMap) { + VelocityEngine engine = new VelocityEngine(); + engine.setProperty(Velocity.FILE_RESOURCE_LOADER_CACHE, true); + engine.setProperty(Velocity.INPUT_ENCODING, "UTF-8"); + engine.setProperty("resource.loader.file.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); + engine.init(); + Template template = engine.getTemplate(templateFile); + + //加载tools.xml配置文件 + ToolManager toolManager = new ToolManager(); + toolManager.configure("code-generator-template/tools.xml"); + + //注入变量 + ToolContext context = toolManager.createContext(); + context.putAll(variablesMap); + + StringWriter sw = new StringWriter(); + template.merge(context, sw); + return sw.toString(); + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/CodeGenerateBaseVariableService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/CodeGenerateBaseVariableService.java new file mode 100644 index 0000000..b2e026e --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/CodeGenerateBaseVariableService.java @@ -0,0 +1,151 @@ +package net.lab1024.sa.base.module.support.codegenerator.service.variable; + +import com.google.common.base.CaseFormat; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.module.support.codegenerator.constant.CodeFrontComponentEnum; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeField; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeInsertAndUpdate; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeInsertAndUpdateField; +import org.apache.commons.collections4.CollectionUtils; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/29 17:20:41 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public abstract class CodeGenerateBaseVariableService { + + public abstract Map getInjectVariablesMap(CodeGeneratorConfigForm form); + + /** + * 是否支持 : + * 1、增加、修改 + * 2、删除 + * + * @param form + * @return + */ + public abstract boolean isSupport(CodeGeneratorConfigForm form); + + /** + * 获取所有javabean的 import 包名 + * + * @param form + * @return + */ + public List getJavaBeanImportClass(CodeGeneratorConfigForm form) { + String upperCamelName = CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_CAMEL, form.getBasic().getModuleName()); + ArrayList list = new ArrayList<>(); + + list.add("import " + form.getBasic().getJavaPackageName() + ".domain.entity." + upperCamelName + "Entity;"); + + list.add("import " + form.getBasic().getJavaPackageName() + ".domain.form." + upperCamelName + "AddForm;"); + list.add("import " + form.getBasic().getJavaPackageName() + ".domain.form." + upperCamelName + "UpdateForm;"); + list.add("import " + form.getBasic().getJavaPackageName() + ".domain.form." + upperCamelName + "QueryForm;"); + + list.add("import " + form.getBasic().getJavaPackageName() + ".domain.vo." + upperCamelName + "VO;"); + return list; + } + + + /** + * 根据列名查找 CodeField + */ + public CodeField getCodeFieldByColumnName(String columnName, CodeGeneratorConfigForm form) { + List fields = form.getFields(); + if (CollectionUtils.isEmpty(fields)) { + return null; + } + + return fields.stream().filter(e -> SmartStringUtil.equals(columnName, e.getColumnName())).findFirst().orElse(null); + } + + + /** + * 是否为文件上传字段 + */ + protected boolean isFile(String columnName, CodeGeneratorConfigForm form) { + CodeInsertAndUpdate insertAndUpdate = form.getInsertAndUpdate(); + if (insertAndUpdate == null) { + return false; + } + + List fieldList = insertAndUpdate.getFieldList(); + if (CollectionUtils.isEmpty(fieldList)) { + return false; + } + + Optional first = fieldList.stream().filter(e -> columnName.equals(e.getColumnName())).findFirst(); + if (!first.isPresent()) { + return false; + } + + CodeInsertAndUpdateField field = first.get(); + return CodeFrontComponentEnum.FILE_UPLOAD.equalsValue(field.getFrontComponent()); + } + + /** + * 是否为 字典 + */ + protected boolean isDict(String columnName, CodeGeneratorConfigForm form) { + CodeField codeField = getCodeField(columnName, form); + return codeField != null && codeField.getDict() != null; + } + + /** + * 是否为 枚举 + */ + protected boolean isEnum(String columnName, CodeGeneratorConfigForm form) { + CodeField codeField = getCodeField(columnName, form); + return codeField != null && codeField.getEnumName() != null; + } + + private CodeField getCodeField(String columnName, CodeGeneratorConfigForm form) { + List fields = form.getFields(); + if (CollectionUtils.isEmpty(fields)) { + return null; + } + + return fields.stream().filter(e -> columnName.equals(e.getColumnName())).findFirst().orElse(null); + } + + /** + * 获取字段集合 + * + * @param form + * @return + */ + protected Map getFieldMap(CodeGeneratorConfigForm form) { + List fields = form.getFields(); + if (fields == null) { + return new HashMap<>(); + } + + return fields.stream().collect(Collectors.toMap(CodeField::getColumnName, Function.identity())); + } + + /** + * 获取java类型 + * + * @return + */ + protected String getJavaPackageName(String javaType) { + if ("BigDecimal".equals(javaType)) { + return "import java.math.BigDecimal;"; + } else if ("LocalDate".equals(javaType)) { + return "import java.time.LocalDate;"; + } else if ("LocalDateTime".equals(javaType)) { + return "import java.time.LocalDateTime;"; + } else { + return null; + } + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/ControllerVariableService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/ControllerVariableService.java new file mode 100644 index 0000000..167bd16 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/ControllerVariableService.java @@ -0,0 +1,78 @@ +package net.lab1024.sa.base.module.support.codegenerator.service.variable.backend; + +import com.google.common.base.CaseFormat; +import net.lab1024.sa.base.common.util.SmartEnumUtil; +import net.lab1024.sa.base.module.support.codegenerator.constant.CodeDeleteEnum; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeInsertAndUpdateField; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.CodeGenerateBaseVariableService; +import org.apache.commons.collections4.CollectionUtils; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/29 17:20:41 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +public class ControllerVariableService extends CodeGenerateBaseVariableService { + + @Override + public boolean isSupport(CodeGeneratorConfigForm form) { + return true; + } + + @Override + public Map getInjectVariablesMap(CodeGeneratorConfigForm form) { + Map variablesMap = new HashMap<>(); + + List updateFieldList = form.getInsertAndUpdate().getFieldList().stream().filter(e -> Boolean.TRUE.equals(e.getInsertFlag())).collect(Collectors.toList()); + + variablesMap.put("packageName", form.getBasic().getJavaPackageName() + ".controller"); + + List packageList = getPackageList(updateFieldList, form); + variablesMap.put("importPackageList", packageList); + + return variablesMap; + } + + + public List getPackageList(List fields, CodeGeneratorConfigForm form) { + if (CollectionUtils.isEmpty(fields)) { + return new ArrayList<>(); + } + + HashSet packageSet = new HashSet<>(); + + //1、javabean相关的包 + packageSet.addAll(getJavaBeanImportClass(form).stream().filter(e -> !e.contains("Entity;")).collect(Collectors.toList())); + + //2、其他包 + if (form.getDeleteInfo().getIsSupportDelete()) { + + CodeDeleteEnum codeDeleteEnum = SmartEnumUtil.getEnumByValue(form.getDeleteInfo().getDeleteEnum(), CodeDeleteEnum.class); + if (codeDeleteEnum == CodeDeleteEnum.BATCH || codeDeleteEnum == CodeDeleteEnum.SINGLE_AND_BATCH) { + //2、批量删除的话,要导入ValidateList + packageSet.add("import net.lab1024.sa.base.common.domain.ValidateList;"); + } + + if (codeDeleteEnum == CodeDeleteEnum.SINGLE || codeDeleteEnum == CodeDeleteEnum.SINGLE_AND_BATCH) { + //3、单个删除的话,要导入 @PathVariable + packageSet.add("import org.springframework.web.bind.annotation.PathVariable;"); + packageSet.add("import org.springframework.web.bind.annotation.GetMapping;"); + } + } + + packageSet.add("import " + form.getBasic().getJavaPackageName() + ".service." + CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, form.getBasic().getModuleName()) + "Service;"); + + // 排序一下 + ArrayList packageList = new ArrayList<>(packageSet); + Collections.sort(packageList); + return packageList; + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/DaoVariableService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/DaoVariableService.java new file mode 100644 index 0000000..b4d313f --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/DaoVariableService.java @@ -0,0 +1,59 @@ +package net.lab1024.sa.base.module.support.codegenerator.service.variable.backend; + +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeInsertAndUpdateField; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.CodeGenerateBaseVariableService; +import org.apache.commons.collections4.CollectionUtils; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/29 17:20:41 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +public class DaoVariableService extends CodeGenerateBaseVariableService { + + @Override + public boolean isSupport(CodeGeneratorConfigForm form) { + return true; + } + + @Override + public Map getInjectVariablesMap(CodeGeneratorConfigForm form) { + Map variablesMap = new HashMap<>(); + + List updateFieldList = form.getInsertAndUpdate().getFieldList().stream().filter(e -> Boolean.TRUE.equals(e.getInsertFlag())).collect(Collectors.toList()); + List packageList = getPackageList(updateFieldList, form); + + variablesMap.put("packageName", form.getBasic().getJavaPackageName() + ".dao" ); + variablesMap.put("importPackageList", packageList); + + return variablesMap; + } + + + public List getPackageList(List fields, CodeGeneratorConfigForm form) { + if (CollectionUtils.isEmpty(fields)) { + return new ArrayList<>(); + } + + HashSet packageSet = new HashSet<>(); + + //1、javabean相关的包 + packageSet.addAll(getJavaBeanImportClass(form).stream().filter( e-> e.contains("QueryForm;") || e.contains("VO;")|| e.contains("Entity;")).collect(Collectors.toList())); + + //2、util + packageSet.add("import java.util.List;"); + + //3、 排序一下 + ArrayList packageList = new ArrayList<>(packageSet); + Collections.sort(packageList); + return packageList; + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/ManagerVariableService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/ManagerVariableService.java new file mode 100644 index 0000000..c46fdb5 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/ManagerVariableService.java @@ -0,0 +1,55 @@ +package net.lab1024.sa.base.module.support.codegenerator.service.variable.backend; + +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeInsertAndUpdateField; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.CodeGenerateBaseVariableService; +import org.apache.commons.collections4.CollectionUtils; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/29 17:20:41 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +public class ManagerVariableService extends CodeGenerateBaseVariableService { + + @Override + public boolean isSupport(CodeGeneratorConfigForm form) { + return true; + } + + @Override + public Map getInjectVariablesMap(CodeGeneratorConfigForm form) { + Map variablesMap = new HashMap<>(); + + List updateFieldList = form.getInsertAndUpdate().getFieldList().stream().filter(e -> Boolean.TRUE.equals(e.getInsertFlag())).collect(Collectors.toList()); + List packageList = getPackageList(updateFieldList, form); + + variablesMap.put("packageName", form.getBasic().getJavaPackageName() + ".manager" ); + variablesMap.put("importPackageList", packageList); + + return variablesMap; + } + + + public List getPackageList(List fields, CodeGeneratorConfigForm form) { + if (CollectionUtils.isEmpty(fields)) { + return new ArrayList<>(); + } + + HashSet packageList = new HashSet<>(); + + //1、javabean相关的包 + packageList.addAll(getJavaBeanImportClass(form).stream().filter(e -> e.contains("Entity;")).collect(Collectors.toList())); + + //2、dao + packageList.add("import " + form.getBasic().getJavaPackageName() + ".dao."+ form.getBasic().getModuleName() + "Dao;" ); + return new ArrayList<>(packageList); + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/MenuVariableService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/MenuVariableService.java new file mode 100644 index 0000000..17ab19f --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/MenuVariableService.java @@ -0,0 +1,27 @@ +package net.lab1024.sa.base.module.support.codegenerator.service.variable.backend; + +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.CodeGenerateBaseVariableService; + +import java.util.HashMap; +import java.util.Map; + +/** + * 目前暂时没用到 这是一个空实现 + * + * @author zhoumingfa + * @date 2024/8/13 + */ +public class MenuVariableService extends CodeGenerateBaseVariableService { + + @Override + public boolean isSupport(CodeGeneratorConfigForm form) { + return true; + } + + @Override + public Map getInjectVariablesMap(CodeGeneratorConfigForm form) { + return new HashMap<>(2); + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/ServiceVariableService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/ServiceVariableService.java new file mode 100644 index 0000000..fb5750b --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/ServiceVariableService.java @@ -0,0 +1,62 @@ +package net.lab1024.sa.base.module.support.codegenerator.service.variable.backend; + +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeInsertAndUpdateField; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.CodeGenerateBaseVariableService; +import org.apache.commons.collections4.CollectionUtils; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/29 17:20:41 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +public class ServiceVariableService extends CodeGenerateBaseVariableService { + + @Override + public boolean isSupport(CodeGeneratorConfigForm form) { + return true; + } + + @Override + public Map getInjectVariablesMap(CodeGeneratorConfigForm form) { + Map variablesMap = new HashMap<>(); + + List updateFieldList = form.getInsertAndUpdate().getFieldList().stream().filter(e -> Boolean.TRUE.equals(e.getInsertFlag())).collect(Collectors.toList()); + List packageList = getPackageList(updateFieldList, form); + + variablesMap.put("packageName", form.getBasic().getJavaPackageName() + ".service" ); + variablesMap.put("importPackageList", packageList); + + return variablesMap; + } + + + public List getPackageList(List fields, CodeGeneratorConfigForm form) { + if (CollectionUtils.isEmpty(fields)) { + return new ArrayList<>(); + } + + HashSet packageSet = new HashSet<>(); + + //1、javabean相关的包 + packageSet.addAll(getJavaBeanImportClass(form)); + + //2、dao + packageSet.add("import " + form.getBasic().getJavaPackageName() + ".dao."+ form.getBasic().getModuleName() + "Dao;" ); + + //3、util list + packageSet.add("import java.util.List;"); + + //3、 排序一下 + ArrayList packageList = new ArrayList<>(packageSet); + Collections.sort(packageList); + return packageList; + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/domain/AddFormVariableService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/domain/AddFormVariableService.java new file mode 100644 index 0000000..0df8d33 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/domain/AddFormVariableService.java @@ -0,0 +1,131 @@ +package net.lab1024.sa.base.module.support.codegenerator.service.variable.backend.domain; + +import cn.hutool.core.bean.BeanUtil; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.module.support.codegenerator.constant.CodeFrontComponentEnum; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeField; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeInsertAndUpdate; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeInsertAndUpdateField; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.CodeGenerateBaseVariableService; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/29 17:20:41 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +public class AddFormVariableService extends CodeGenerateBaseVariableService { + + + @Override + public boolean isSupport(CodeGeneratorConfigForm form) { + CodeInsertAndUpdate insertAndUpdate = form.getInsertAndUpdate(); + return insertAndUpdate != null && insertAndUpdate.getIsSupportInsertAndUpdate() != null && insertAndUpdate.getIsSupportInsertAndUpdate(); + } + + @Override + public Map getInjectVariablesMap(CodeGeneratorConfigForm form) { + Map variablesMap = new HashMap<>(); + + List updateFieldList = form.getInsertAndUpdate().getFieldList().stream().filter(e -> Boolean.TRUE.equals(e.getInsertFlag())).collect(Collectors.toList()); + ImmutablePair, List>> packageListAndFields = getPackageListAndFields(updateFieldList, form); + + variablesMap.put("packageName", form.getBasic().getJavaPackageName() + ".domain.form"); + variablesMap.put("importPackageList", packageListAndFields.getLeft()); + variablesMap.put("fields", packageListAndFields.getRight()); + + return variablesMap; + } + + + public ImmutablePair, List>> getPackageListAndFields(List fields, CodeGeneratorConfigForm form) { + if (CollectionUtils.isEmpty(fields)) { + return ImmutablePair.of(new ArrayList<>(), new ArrayList<>()); + } + + Map fieldMap = getFieldMap(form); + HashSet packageList = new HashSet<>(); + + + /** + * 1、LocalDate、LocalDateTime、BigDecimal 类型的包名 + * 2、排序 + */ + + List> finalFieldList = new ArrayList<>(); + + for (CodeInsertAndUpdateField field : fields) { + CodeField codeField = fieldMap.get(field.getColumnName()); + if (codeField == null) { + continue; + } + + // CodeField 和 InsertAndUpdateField 合并 + Map finalFieldMap = BeanUtil.beanToMap(field); + finalFieldMap.putAll(BeanUtil.beanToMap(codeField)); + + // 枚举 + if (SmartStringUtil.isNotEmpty(codeField.getEnumName())) { + packageList.add("import net.lab1024.sa.base.common.swagger.SchemaEnum;"); + packageList.add("import net.lab1024.sa.base.common.validator.enumeration.CheckEnum;"); + packageList.add("import " + form.getBasic().getJavaPackageName() + ".constant." + codeField.getEnumName() + ";"); + + //enum check + String checkEnumPrefix = "@CheckEnum(value = " + codeField.getEnumName() + ".class, message = \"" + codeField.getLabel() + " 错误\""; + String checkEnum = checkEnumPrefix + (field.getRequiredFlag() ? ", required = true)" : ")"); + + finalFieldMap.put("apiModelProperty", "@SchemaEnum(value = " + codeField.getEnumName() + ".class, desc = \"" + codeField.getLabel() + "\")"); + finalFieldMap.put("checkEnum", checkEnum); + finalFieldMap.put("isEnum", true); + + } else { + String prefix = "@Schema(description = \"" + codeField.getLabel() + "\""; + String apiModelProperty = prefix + (field.getRequiredFlag() ? ", requiredMode = Schema.RequiredMode.REQUIRED)" : ")"); + finalFieldMap.put("apiModelProperty", apiModelProperty); + + packageList.add("import io.swagger.v3.oas.annotations.media.Schema;"); + + if (Boolean.TRUE.equals(field.getRequiredFlag())) { + String notEmptyPrefix = "String".equals(codeField.getJavaType()) ? "@NotBlank" : "@NotNull"; + finalFieldMap.put("notEmpty", "\n " + notEmptyPrefix + "(message = \"" + codeField.getLabel() + " 不能为空\")"); + packageList.add("String".equals(codeField.getJavaType()) ? "import jakarta.validation.constraints.NotBlank;" + : "import jakarta.validation.constraints.NotNull;"); + } + } + + //字典 + if (SmartStringUtil.isNotEmpty(codeField.getDict())) { + finalFieldMap.put("dict", "\n @JsonDeserialize(using = DictValueVoDeserializer.class)"); + packageList.add("import com.fasterxml.jackson.databind.annotation.JsonDeserialize;"); + packageList.add("import net.lab1024.sa.base.common.json.deserializer.DictValueVoDeserializer;"); + } + + //文件上传 + if (CodeFrontComponentEnum.FILE_UPLOAD.equalsValue(field.getFrontComponent())) { + finalFieldMap.put("file", "\n @JsonDeserialize(using = FileKeyVoDeserializer.class)"); + packageList.add("import com.fasterxml.jackson.databind.annotation.JsonDeserialize;"); + packageList.add("import net.lab1024.sa.base.common.json.deserializer.FileKeyVoDeserializer;"); + } + + packageList.add(getJavaPackageName(codeField.getJavaType())); + finalFieldList.add(finalFieldMap); + } + + + // lombok + packageList.add("import lombok.Data;"); + + List packageNameList = packageList.stream().filter(Objects::nonNull).collect(Collectors.toList()); + Collections.sort(packageNameList); + return ImmutablePair.of(packageNameList, finalFieldList); + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/domain/EntityVariableService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/domain/EntityVariableService.java new file mode 100644 index 0000000..391bffa --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/domain/EntityVariableService.java @@ -0,0 +1,80 @@ +package net.lab1024.sa.base.module.support.codegenerator.service.variable.backend.domain; + +import com.google.common.collect.Lists; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeField; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.CodeGenerateBaseVariableService; +import org.apache.commons.collections4.CollectionUtils; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/29 17:20:41 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +public class EntityVariableService extends CodeGenerateBaseVariableService { + + @Override + public boolean isSupport(CodeGeneratorConfigForm form) { + return true; + } + + @Override + public Map getInjectVariablesMap(CodeGeneratorConfigForm form) { + Map variablesMap = new HashMap<>(); + + + variablesMap.put("packageName", form.getBasic().getJavaPackageName() + ".domain.entity"); + variablesMap.put("importPackageList", getImportPackageList(form.getFields())); + + + return variablesMap; + } + + + public List getImportPackageList(List fields) { + if (CollectionUtils.isEmpty(fields)) { + return Lists.newArrayList(); + } + + /** + * 1、LocalDate、LocalDateTime、BigDecimal 类型的包名 + * 2、排序 + */ + List result = fields.stream().map(e -> getJavaPackageName(e.getJavaType())).filter(Objects::nonNull).distinct().collect(Collectors.toList()); + + // lombok + result.add("import lombok.Data;"); + + // mybatis plus + result.add("import com.baomidou.mybatisplus.annotation.TableName;"); + + // 自动填充注解 + boolean existCreateAndUpdate = fields.stream().anyMatch(e -> "create_time".equals(e.getColumnName()) || "update_time".equals(e.getColumnName())); + if (existCreateAndUpdate) { + result.add("import com.baomidou.mybatisplus.annotation.FieldFill;"); + result.add("import com.baomidou.mybatisplus.annotation.TableField;"); + } + + //主键 + boolean isExistPrimaryKey = fields.stream().anyMatch(e -> e.getPrimaryKeyFlag() != null && e.getPrimaryKeyFlag()); + if (isExistPrimaryKey) { + result.add("import com.baomidou.mybatisplus.annotation.TableId;"); + } + + //自增 + boolean isExistAutoIncrease = fields.stream().anyMatch(e -> e.getAutoIncreaseFlag() != null && e.getAutoIncreaseFlag()); + if (isExistAutoIncrease) { + result.add("import com.baomidou.mybatisplus.annotation.IdType;"); + } + + Collections.sort(result); + return result; + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/domain/MapperVariableService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/domain/MapperVariableService.java new file mode 100644 index 0000000..efd88d9 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/domain/MapperVariableService.java @@ -0,0 +1,88 @@ +package net.lab1024.sa.base.module.support.codegenerator.service.variable.backend.domain; + +import cn.hutool.core.bean.BeanUtil; +import net.lab1024.sa.base.module.support.codegenerator.constant.CodeQueryFieldQueryTypeEnum; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeInsertAndUpdateField; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeQueryField; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.CodeGenerateBaseVariableService; +import org.apache.commons.collections4.CollectionUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/29 17:20:41 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +public class MapperVariableService extends CodeGenerateBaseVariableService { + + @Override + public boolean isSupport(CodeGeneratorConfigForm form) { + return true; + } + + @Override + public Map getInjectVariablesMap(CodeGeneratorConfigForm form) { + Map variablesMap = new HashMap<>(); + List> finalQueryFiledList = new ArrayList<>(); + for (CodeQueryField queryField : form.getQueryFields()) { + Map fieldMap = BeanUtil.beanToMap(queryField); + finalQueryFiledList.add(fieldMap); + + //模糊查询 + if (CodeQueryFieldQueryTypeEnum.LIKE.getValue().equals(queryField.getQueryTypeEnum())) { + StringBuilder stringBuilder = new StringBuilder(); + List columnNameList = queryField.getColumnNameList(); + if (columnNameList.size() == 1) { + // AND INSTR(t_notice.title,#{query.keywords}) + stringBuilder.append("AND INSTR(") + .append(form.getTableName()).append(".").append(queryField.getColumnNameList().get(0)) + .append(",#{queryForm.") + .append(queryField.getFieldName()) + .append("})"); + } else { + for (int i = 0; i < columnNameList.size(); i++) { + if (i == 0) { + stringBuilder.append("AND (\n INSTR(") + .append(form.getTableName()).append(".").append(queryField.getColumnNameList().get(i)) + .append(",#{queryForm.") + .append(queryField.getFieldName()) + .append("})"); + } else { + // OR INSTR(t_notice.author,#{query.keywords}) + stringBuilder.append("\n OR INSTR(") + .append(form.getTableName()).append(".").append(queryField.getColumnNameList().get(i)) + .append(",#{queryForm.") + .append(queryField.getFieldName()) + .append("})"); + } + } + stringBuilder.append("\n )"); + } + fieldMap.put("likeStr", stringBuilder.toString()); + } else if (CodeQueryFieldQueryTypeEnum.DICT.equalsValue(queryField.getQueryTypeEnum())) { + String stringBuilder = "AND INSTR(" + + form.getTableName() + "." + queryField.getColumnNameList().get(0) + + ",#{queryForm." + + queryField.getFieldName() + + "})"; + fieldMap.put("likeStr", stringBuilder); + } + else { + fieldMap.put("columnName", queryField.getColumnNameList().get(0)); + } + } + + variablesMap.put("queryFields", finalQueryFiledList); + variablesMap.put("daoClassName", form.getBasic().getJavaPackageName() + ".dao." + form.getBasic().getModuleName() + "Dao"); + return variablesMap; + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/domain/QueryFormVariableService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/domain/QueryFormVariableService.java new file mode 100644 index 0000000..4101d5e --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/domain/QueryFormVariableService.java @@ -0,0 +1,129 @@ +package net.lab1024.sa.base.module.support.codegenerator.service.variable.backend.domain; + +import cn.hutool.core.bean.BeanUtil; +import net.lab1024.sa.base.common.util.SmartEnumUtil; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.module.support.codegenerator.constant.CodeQueryFieldQueryTypeEnum; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeField; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeQueryField; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.CodeGenerateBaseVariableService; +import org.apache.commons.lang3.tuple.ImmutablePair; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/29 17:20:41 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +public class QueryFormVariableService extends CodeGenerateBaseVariableService { + + + @Override + public boolean isSupport(CodeGeneratorConfigForm form) { + return true; + } + + @Override + public Map getInjectVariablesMap(CodeGeneratorConfigForm form) { + Map variablesMap = new HashMap<>(); + ImmutablePair, List>> packageListAndFields = getPackageListAndFields(form); + variablesMap.put("packageName", form.getBasic().getJavaPackageName() + ".domain.form"); + variablesMap.put("importPackageList", packageListAndFields.getLeft()); + variablesMap.put("fields", packageListAndFields.getRight()); + return variablesMap; + } + + + public ImmutablePair, List>> getPackageListAndFields(CodeGeneratorConfigForm form) { + + List fields = form.getQueryFields(); + + HashSet packageList = new HashSet<>(); + + /** + * 1、LocalDate、LocalDateTime、BigDecimal 类型的包名 + * 2、排序 + */ + + List> finalFieldList = new ArrayList<>(); + + for (CodeQueryField field : fields) { + + // CodeField 和 InsertAndUpdateField 合并 + Map finalFieldMap = BeanUtil.beanToMap(field); + finalFieldMap.putAll(BeanUtil.beanToMap(field)); + + String queryTypeEnumStr = field.getQueryTypeEnum(); + CodeQueryFieldQueryTypeEnum queryTypeEnum = SmartEnumUtil.getEnumByValue(queryTypeEnumStr, CodeQueryFieldQueryTypeEnum.class); + if (queryTypeEnum == null) { + continue; + } + + String apiModelProperty = "@Schema(description = \"" + field.getLabel() + "\")"; + finalFieldMap.put("apiModelProperty", apiModelProperty); + packageList.add("import io.swagger.v3.oas.annotations.media.Schema;"); + + CodeField codeField = null; + + switch (queryTypeEnum) { + case EQUAL: + codeField = getCodeFieldByColumnName(field.getColumnNameList().get(0), form); + if (codeField == null) { + finalFieldMap.put("javaType", "String"); + } else { + finalFieldMap.put("javaType", codeField.getJavaType()); + } + break; + case DATE_RANGE: + case DATE: + packageList.add("import java.time.LocalDate;"); + finalFieldMap.put("javaType", "LocalDate"); + break; + case ENUM: + codeField = getCodeFieldByColumnName(field.getColumnNameList().get(0), form); + if (codeField == null) { + continue; + } + + packageList.add("import net.lab1024.sa.base.common.swagger.SchemaEnum;"); + packageList.add("import net.lab1024.sa.base.common.validator.enumeration.CheckEnum;"); + packageList.add("import " + form.getBasic().getJavaPackageName() + ".constant." + codeField.getEnumName() + ";"); + + //enum check + String checkEnum = "@CheckEnum(value = " + codeField.getEnumName() + ".class, message = \"" + codeField.getLabel() + " 错误\")"; + finalFieldMap.put("apiModelProperty", "@SchemaEnum(value = " + codeField.getEnumName() + ".class, desc = \"" + codeField.getLabel() + "\")"); + finalFieldMap.put("checkEnum", checkEnum); + finalFieldMap.put("isEnum", true); + + finalFieldMap.put("javaType", codeField.getJavaType()); + break; + case DICT: + codeField = getCodeFieldByColumnName(field.getColumnNameList().get(0), form); + if (SmartStringUtil.isNotEmpty(codeField.getDict())) { + finalFieldMap.put("dict", "\n @JsonDeserialize(using = DictValueVoDeserializer.class)"); + packageList.add("import com.fasterxml.jackson.databind.annotation.JsonDeserialize;"); + packageList.add("import net.lab1024.sa.base.common.json.deserializer.DictValueVoDeserializer;"); + } + finalFieldMap.put("javaType", "String"); + default: + finalFieldMap.put("javaType", "String"); + } + + finalFieldList.add(finalFieldMap); + } + + // lombok + packageList.add("import lombok.Data;"); + packageList.add("import lombok.EqualsAndHashCode;"); + + List packageNameList = packageList.stream().filter(Objects::nonNull).sorted().collect(Collectors.toList()); + return ImmutablePair.of(packageNameList, finalFieldList); + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/domain/UpdateFormVariableService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/domain/UpdateFormVariableService.java new file mode 100644 index 0000000..a3cfb43 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/domain/UpdateFormVariableService.java @@ -0,0 +1,146 @@ +package net.lab1024.sa.base.module.support.codegenerator.service.variable.backend.domain; + +import cn.hutool.core.bean.BeanUtil; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.module.support.codegenerator.constant.CodeFrontComponentEnum; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeField; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeInsertAndUpdate; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeInsertAndUpdateField; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.CodeGenerateBaseVariableService; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/29 17:20:41 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +public class UpdateFormVariableService extends CodeGenerateBaseVariableService { + + + @Override + public boolean isSupport(CodeGeneratorConfigForm form) { + CodeInsertAndUpdate insertAndUpdate = form.getInsertAndUpdate(); + return insertAndUpdate != null && insertAndUpdate.getIsSupportInsertAndUpdate() != null && insertAndUpdate.getIsSupportInsertAndUpdate(); + } + + @Override + public Map getInjectVariablesMap(CodeGeneratorConfigForm form) { + Map variablesMap = new HashMap<>(); + + Map fieldMap = getFieldMap(form); + List updateFieldList = form.getInsertAndUpdate().getFieldList().stream().filter(e -> { + boolean isUpdate = Boolean.TRUE.equals(e.getUpdateFlag()); + CodeField codeField = fieldMap.get(e.getColumnName()); + if (codeField == null) { + return false; + } + + if (Boolean.TRUE.equals(codeField.getPrimaryKeyFlag())) { + e.setRequiredFlag(true); + } + + return isUpdate || Boolean.TRUE.equals(codeField.getPrimaryKeyFlag()); + } + + ).collect(Collectors.toList()); + + ImmutablePair, List>> packageListAndFields = getPackageListAndFields(updateFieldList, form); + + variablesMap.put("packageName", form.getBasic().getJavaPackageName() + ".domain.form"); + variablesMap.put("importPackageList", packageListAndFields.getLeft()); + variablesMap.put("fields", packageListAndFields.getRight()); + + return variablesMap; + } + + public ImmutablePair, List>> getPackageListAndFields(List fields, CodeGeneratorConfigForm form) { + if (CollectionUtils.isEmpty(fields)) { + return ImmutablePair.of(new ArrayList<>(), new ArrayList<>()); + } + + Map fieldMap = getFieldMap(form); + HashSet packageList = new HashSet<>(); + + + /** + * 1、LocalDate、LocalDateTime、BigDecimal 类型的包名 + * 2、排序 + */ + + List> finalFieldList = new ArrayList<>(); + + for (CodeInsertAndUpdateField field : fields) { + CodeField codeField = fieldMap.get(field.getColumnName()); + if (codeField == null) { + continue; + } + + // CodeField 和 InsertAndUpdateField 合并 + Map finalFieldMap = BeanUtil.beanToMap(field); + finalFieldMap.putAll(BeanUtil.beanToMap(codeField)); + + // 枚举 + if (SmartStringUtil.isNotEmpty(codeField.getEnumName())) { + packageList.add("import net.lab1024.sa.base.common.swagger.SchemaEnum;"); + packageList.add("import net.lab1024.sa.base.common.validator.enumeration.CheckEnum;"); + packageList.add("import " + form.getBasic().getJavaPackageName() + ".constant." + codeField.getEnumName() + ";"); + + //enum check + String checkEnumPrefix = "@CheckEnum(value = " + codeField.getEnumName() + ".class, message = \"" + codeField.getLabel() + " 错误\""; + String checkEnum = checkEnumPrefix + (field.getRequiredFlag() ? ", required = true)" : ")"); + + finalFieldMap.put("apiModelProperty", "@SchemaEnum(value = " + codeField.getEnumName() + ".class, desc = \"" + codeField.getLabel() + "\")"); + finalFieldMap.put("checkEnum", checkEnum); + finalFieldMap.put("isEnum", true); + + } else { + String prefix = "@Schema(description = \"" + codeField.getLabel() + "\""; + String apiModelProperty = prefix + (field.getRequiredFlag() ? ", requiredMode = Schema.RequiredMode.REQUIRED)" : ")"); + finalFieldMap.put("apiModelProperty", apiModelProperty); + + packageList.add("import io.swagger.v3.oas.annotations.media.Schema;"); + + if (Boolean.TRUE.equals(field.getRequiredFlag())) { + String notEmptyPrefix = "String".equals(codeField.getJavaType()) ? "@NotBlank" : "@NotNull"; + finalFieldMap.put("notEmpty", "\n " + notEmptyPrefix + "(message = \"" + codeField.getLabel() + " 不能为空\")"); + packageList.add("String".equals(codeField.getJavaType()) ? "import jakarta.validation.constraints.NotBlank;" : "import jakarta.validation.constraints.NotNull;"); + } + } + + + //字典 + if (SmartStringUtil.isNotEmpty(codeField.getDict())) { + finalFieldMap.put("dict", "\n @JsonDeserialize(using = DictValueVoDeserializer.class)"); + packageList.add("import com.fasterxml.jackson.databind.annotation.JsonDeserialize;"); + packageList.add("import net.lab1024.sa.base.common.json.deserializer.DictValueVoDeserializer;"); + } + + //文件上传 + if (CodeFrontComponentEnum.FILE_UPLOAD.equalsValue(field.getFrontComponent())) { + finalFieldMap.put("file", "\n @JsonDeserialize(using = FileKeyVoDeserializer.class)"); + packageList.add("import com.fasterxml.jackson.databind.annotation.JsonDeserialize;"); + packageList.add("import net.lab1024.sa.base.common.json.deserializer.FileKeyVoDeserializer;"); + } + + packageList.add(getJavaPackageName(codeField.getJavaType())); + finalFieldList.add(finalFieldMap); + } + + + // lombok + packageList.add("import lombok.Data;"); + + List packageNameList = packageList.stream().filter(Objects::nonNull).collect(Collectors.toList()); + Collections.sort(packageNameList); + return ImmutablePair.of(packageNameList, finalFieldList); + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/domain/VOVariableService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/domain/VOVariableService.java new file mode 100644 index 0000000..45844fe --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/domain/VOVariableService.java @@ -0,0 +1,107 @@ +package net.lab1024.sa.base.module.support.codegenerator.service.variable.backend.domain; + +import cn.hutool.core.bean.BeanUtil; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeField; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeTableField; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.CodeGenerateBaseVariableService; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/29 17:20:41 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +public class VOVariableService extends CodeGenerateBaseVariableService { + + @Override + public boolean isSupport(CodeGeneratorConfigForm form) { + return true; + } + + @Override + public Map getInjectVariablesMap(CodeGeneratorConfigForm form) { + Map variablesMap = new HashMap<>(); + + Map fieldMap = getFieldMap(form); + List updateFieldList = form.getTableFields().stream().filter(e -> Boolean.TRUE.equals(e.getShowFlag())).collect(Collectors.toList()); + + ImmutablePair, List>> packageListAndFields = getPackageListAndFields(updateFieldList, form); + + variablesMap.put("packageName", form.getBasic().getJavaPackageName() + ".domain.vo"); + variablesMap.put("importPackageList", packageListAndFields.getLeft()); + variablesMap.put("fields", packageListAndFields.getRight()); + + return variablesMap; + } + + public ImmutablePair, List>> getPackageListAndFields(List fields, CodeGeneratorConfigForm form) { + if (CollectionUtils.isEmpty(fields)) { + return ImmutablePair.of(new ArrayList<>(), new ArrayList<>()); + } + + Map fieldMap = getFieldMap(form); + HashSet packageList = new HashSet<>(); + + + /** + * 1、LocalDate、LocalDateTime、BigDecimal 类型的包名 + * 2、排序 + */ + + List> finalFieldList = new ArrayList<>(); + + for (CodeTableField field : fields) { + CodeField codeField = fieldMap.get(field.getColumnName()); + if (codeField == null) { + continue; + } + + // CodeField 和 CodeTableField 合并 + Map finalFieldMap = BeanUtil.beanToMap(field); + finalFieldMap.putAll(BeanUtil.beanToMap(codeField)); + + // 枚举 + if (SmartStringUtil.isNotEmpty(codeField.getEnumName())) { + packageList.add("import net.lab1024.sa.base.common.swagger.SchemaEnum;"); + packageList.add("import " + form.getBasic().getJavaPackageName() + ".constant." + codeField.getEnumName() + ";"); + + finalFieldMap.put("apiModelProperty", "@SchemaEnum(value = " + codeField.getEnumName() + ".class, desc = \"" + codeField.getLabel() + "\")"); + finalFieldMap.put("isEnum", true); + + } else { + String apiModelProperty = "@Schema(description = \"" + codeField.getLabel() + "\")"; + finalFieldMap.put("apiModelProperty", apiModelProperty); + + packageList.add("import io.swagger.v3.oas.annotations.media.Schema;"); + } + + //文件上传 + if (isFile(field.getColumnName(), form)) { + finalFieldMap.put("file", "\n @JsonSerialize(using = FileKeyVoSerializer.class)"); + packageList.add("import com.fasterxml.jackson.databind.annotation.JsonSerialize;"); + packageList.add("import net.lab1024.sa.base.common.json.serializer.FileKeyVoSerializer;"); + } + + packageList.add(getJavaPackageName(codeField.getJavaType())); + finalFieldList.add(finalFieldMap); + } + + + // lombok + packageList.add("import lombok.Data;"); + + List packageNameList = packageList.stream().filter(Objects::nonNull).collect(Collectors.toList()); + Collections.sort(packageNameList); + return ImmutablePair.of(packageNameList, finalFieldList); + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/front/ApiVariableService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/front/ApiVariableService.java new file mode 100644 index 0000000..0a98179 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/front/ApiVariableService.java @@ -0,0 +1,29 @@ +package net.lab1024.sa.base.module.support.codegenerator.service.variable.front; + +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.CodeGenerateBaseVariableService; + +import java.util.*; + +/** + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/29 17:20:41 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +public class ApiVariableService extends CodeGenerateBaseVariableService { + + @Override + public boolean isSupport(CodeGeneratorConfigForm form) { + return true; + } + + @Override + public Map getInjectVariablesMap(CodeGeneratorConfigForm form) { + Map variablesMap = new HashMap<>(); + + return variablesMap; + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/front/ConstVariableService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/front/ConstVariableService.java new file mode 100644 index 0000000..0f49467 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/front/ConstVariableService.java @@ -0,0 +1,45 @@ +package net.lab1024.sa.base.module.support.codegenerator.service.variable.front; + +import cn.hutool.core.bean.BeanUtil; +import com.google.common.base.CaseFormat; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeField; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.CodeGenerateBaseVariableService; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/29 17:20:41 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +public class ConstVariableService extends CodeGenerateBaseVariableService { + + @Override + public boolean isSupport(CodeGeneratorConfigForm form) { + return true; + } + + @Override + public Map getInjectVariablesMap(CodeGeneratorConfigForm form) { + Map variablesMap = new HashMap<>(); + List> enumList = new ArrayList<>(); + List enumFiledList = form.getFields().stream().filter(e -> SmartStringUtil.isNotBlank(e.getEnumName())).collect(Collectors.toList()); + for (CodeField codeField : enumFiledList) { + Map beanToMap = BeanUtil.beanToMap(codeField); + String upperUnderscoreEnum = CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, codeField.getEnumName()); + beanToMap.put("upperUnderscoreEnum", upperUnderscoreEnum); + enumList.add(beanToMap); + } + variablesMap.put("enumList", enumList); + return variablesMap; + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/front/FormVariableService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/front/FormVariableService.java new file mode 100644 index 0000000..85b2354 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/front/FormVariableService.java @@ -0,0 +1,84 @@ +package net.lab1024.sa.base.module.support.codegenerator.service.variable.front; + +import cn.hutool.core.bean.BeanUtil; +import com.google.common.base.CaseFormat; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.module.support.codegenerator.constant.CodeFrontComponentEnum; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeField; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeInsertAndUpdateField; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.CodeGenerateBaseVariableService; + +import java.util.*; + +/** + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/29 17:20:41 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +public class FormVariableService extends CodeGenerateBaseVariableService { + + @Override + public boolean isSupport(CodeGeneratorConfigForm form) { + return true; + } + + @Override + public Map getInjectVariablesMap(CodeGeneratorConfigForm form) { + Map variablesMap = new HashMap<>(); + List> fieldsVariableList = new ArrayList<>(); + List fieldList = form.getInsertAndUpdate().getFieldList(); + + HashSet frontImportSet = new HashSet<>(); + + for (CodeInsertAndUpdateField field : fieldList) { + // 不存在 添加 和 更新 + if (!(field.getInsertFlag() || field.getUpdateFlag())) { + continue; + } + + Map objectMap = BeanUtil.beanToMap(field); + + CodeField codeField = getCodeFieldByColumnName(field.getColumnName(), form); + if (codeField == null) { + continue; + } + objectMap.put("label", codeField.getLabel()); + objectMap.put("fieldName", codeField.getFieldName()); + objectMap.put("dict", codeField.getDict()); + + if (SmartStringUtil.isNotBlank(codeField.getEnumName())) { + String upperUnderscoreEnum = CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, codeField.getEnumName()); + objectMap.put("upperUnderscoreEnum", upperUnderscoreEnum); + } + + fieldsVariableList.add(objectMap); + + if (CodeFrontComponentEnum.ENUM_SELECT.equalsValue(field.getFrontComponent())) { + frontImportSet.add("import SmartEnumSelect from '/@/components/framework/smart-enum-select/index.vue';"); + } + + if (CodeFrontComponentEnum.BOOLEAN_SELECT.equalsValue(field.getFrontComponent())) { + frontImportSet.add("import BooleanSelect from '/@/components/framework/boolean-select/index.vue';"); + } + + if (CodeFrontComponentEnum.DICT_SELECT.equalsValue(field.getFrontComponent())) { + frontImportSet.add("import DictSelect from '/@/components/support/dict-select/index.vue';"); + frontImportSet.add("import { DICT_CODE_ENUM } from '/@/constants/support/dict-const.js';"); + } + + if (CodeFrontComponentEnum.FILE_UPLOAD.equalsValue(field.getFrontComponent())) { + frontImportSet.add("import { FILE_FOLDER_TYPE_ENUM } from '/@/constants/support/file-const';"); + frontImportSet.add("import FileUpload from '/@/components/support/file-upload/index.vue';"); + } + } + + variablesMap.put("formFields", fieldsVariableList); + variablesMap.put("frontImportList", new ArrayList<>(frontImportSet)); + + return variablesMap; + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/front/ListVariableService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/front/ListVariableService.java new file mode 100644 index 0000000..81619de --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/front/ListVariableService.java @@ -0,0 +1,106 @@ +package net.lab1024.sa.base.module.support.codegenerator.service.variable.front; + +import cn.hutool.core.bean.BeanUtil; +import com.google.common.base.CaseFormat; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.module.support.codegenerator.constant.CodeFrontComponentEnum; +import net.lab1024.sa.base.module.support.codegenerator.constant.CodeQueryFieldQueryTypeEnum; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeField; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeInsertAndUpdateField; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeQueryField; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeTableField; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.CodeGenerateBaseVariableService; + +import java.util.*; + +/** + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/29 17:20:41 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +public class ListVariableService extends CodeGenerateBaseVariableService { + + @Override + public boolean isSupport(CodeGeneratorConfigForm form) { + return true; + } + + @Override + public Map getInjectVariablesMap(CodeGeneratorConfigForm form) { + Map variablesMap = new HashMap<>(); + + + HashSet frontImportSet = new HashSet<>(); + frontImportSet.add("import " + CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, form.getBasic().getModuleName()) + "Form from './" + CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_HYPHEN, form.getBasic().getModuleName()) + "-form.vue';"); + + // 查询参数 + List> queryVariable = new ArrayList<>(); + List queryFields = form.getQueryFields(); + + for (CodeQueryField queryField : queryFields) { + Map objectMap = BeanUtil.beanToMap(queryField); + + CodeField codeField = getCodeFieldByColumnName(queryField.getColumnNameList().get(0), form); + + if (CodeQueryFieldQueryTypeEnum.ENUM.equalsValue(queryField.getQueryTypeEnum())) { + objectMap.put("frontEnumName", codeField.getEnumName()); + frontImportSet.add("import SmartEnumSelect from '/@/components/framework/smart-enum-select/index.vue';"); + } + + if (CodeQueryFieldQueryTypeEnum.DICT.equalsValue(queryField.getQueryTypeEnum())) { + objectMap.put("dict", codeField.getDict()); + frontImportSet.add("import DictSelect from '/@/components/support/dict-select/index.vue';"); + } + + if (CodeQueryFieldQueryTypeEnum.DATE_RANGE.equalsValue(queryField.getQueryTypeEnum())) { + frontImportSet.add("import { defaultTimeRanges } from '/@/lib/default-time-ranges';"); + } + queryVariable.add(objectMap); + } + + // 表格列表 + List> listVariable = new ArrayList<>(); + // 过滤掉不显示的字段 + List tableFields = form.getTableFields().stream().filter(CodeTableField::getShowFlag).toList(); + + for (CodeTableField tableField : tableFields) { + Map objectMap = BeanUtil.beanToMap(tableField); + objectMap.put("fieldName", tableField.getFieldName()); + + CodeField codeField = getCodeFieldByColumnName(tableField.getColumnName(), form); + if (codeField == null) { + continue; + } + + // 是否存在字典 + if (SmartStringUtil.isNotBlank(codeField.getDict())) { + objectMap.put("dict", codeField.getDict()); + frontImportSet.add("import { DICT_CODE_ENUM } from '/@/constants/support/dict-const.js';"); + frontImportSet.add("import DictLabel from '/@/components/support/dict-label/index.vue';"); + } + + CodeInsertAndUpdateField codeInsertAndUpdateField = form.getInsertAndUpdate().getFieldList().stream().filter(e -> SmartStringUtil.equals(tableField.getColumnName(), e.getColumnName())).findFirst().orElse(null); + if (codeInsertAndUpdateField == null) { + continue; + } + + // 是否存在上传组件 + if (CodeFrontComponentEnum.FILE_UPLOAD.equalsValue(codeInsertAndUpdateField.getFrontComponent())) { + objectMap.put("frontComponent", codeInsertAndUpdateField.getFrontComponent()); + frontImportSet.add("import FilePreview from '/@/components/support/file-preview/index.vue';"); + } + + listVariable.add(objectMap); + } + + variablesMap.put("queryFields", queryVariable); + variablesMap.put("listFields", listVariable); + variablesMap.put("frontImportList", new ArrayList<>(frontImportSet)); + + return variablesMap; + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/util/CodeGeneratorTool.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/util/CodeGeneratorTool.java new file mode 100644 index 0000000..b3c0be3 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/util/CodeGeneratorTool.java @@ -0,0 +1,59 @@ +package net.lab1024.sa.base.module.support.codegenerator.util; + +import com.google.common.base.CaseFormat; +import net.lab1024.sa.base.common.constant.StringConst; + +/** + * 代码生成 velocity 工具类 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/30 19:02:17 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +public class CodeGeneratorTool { + + /** + * 小写驼峰,转为大写驼峰 + */ + public String lowerCamel2UpperCamel(String str) { + if (str == null) { + return StringConst.EMPTY; + } + return CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, str); + } + + /** + * 小写驼峰,转为小写中划线 + */ + public String lowerCamel2LowerHyphen(String str) { + if (str == null) { + return StringConst.EMPTY; + } + return CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, str); + } + + + /** + * 去掉 注释 枚举类型 + */ + public String removeEnumDesc(String str) { + if (str == null) { + return StringConst.EMPTY; + } + + int index = str.indexOf("["); + if (index == -1) { + index = str.indexOf("【"); + } + + if (index == -1) { + return str; + } + + return str.substring(0, index - 1); + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/ConfigController.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/ConfigController.java new file mode 100644 index 0000000..c88c7a4 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/ConfigController.java @@ -0,0 +1,36 @@ +package net.lab1024.sa.base.module.support.config; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.controller.SupportBaseController; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import net.lab1024.sa.base.module.support.config.domain.ConfigVO; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * 配置 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-14 20:46:27 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Tag(name = SwaggerTagConst.Support.CONFIG) +@RestController +public class ConfigController extends SupportBaseController { + + @Resource + private ConfigService configService; + + @Operation(summary = "查询配置详情 @author 卓大") + @GetMapping("/config/queryByKey") + public ResponseDTO queryByKey(@RequestParam String configKey) { + return ResponseDTO.ok(configService.getConfig(configKey)); + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/ConfigDao.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/ConfigDao.java new file mode 100644 index 0000000..bb525b8 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/ConfigDao.java @@ -0,0 +1,36 @@ +package net.lab1024.sa.base.module.support.config; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.base.module.support.config.domain.ConfigEntity; +import net.lab1024.sa.base.module.support.config.domain.ConfigQueryForm; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 系统参数配置 t_config Dao层 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-14 20:46:27 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface ConfigDao extends BaseMapper { + + /** + * 分页查询系统配置 + * + */ + List queryByPage(Page page, @Param("query") ConfigQueryForm queryForm); + + /** + * 根据key查询获取数据 + * + */ + ConfigEntity selectByKey(String key); +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/ConfigKeyEnum.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/ConfigKeyEnum.java new file mode 100644 index 0000000..da4514d --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/ConfigKeyEnum.java @@ -0,0 +1,31 @@ +package net.lab1024.sa.base.module.support.config; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 系统配置常量类 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-14 20:46:27 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Getter +@AllArgsConstructor +public enum ConfigKeyEnum implements BaseEnum { + + /** + * 万能密码 + */ + SUPER_PASSWORD("super_password", "万能密码"), + + LEVEL3_PROTECT_CONFIG("level3_protect_config", "三级等保配置"), + ; + + private final String value; + + private final String desc; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/ConfigService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/ConfigService.java new file mode 100644 index 0000000..961e2ca --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/ConfigService.java @@ -0,0 +1,187 @@ +package net.lab1024.sa.base.module.support.config; + +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.code.UserErrorCode; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.constant.ReloadConst; +import net.lab1024.sa.base.module.support.config.domain.*; +import net.lab1024.sa.base.module.support.reload.core.annoation.SmartReload; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 系统配置业务类 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-14 20:46:27 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@Service +public class ConfigService { + + /** + * 一个简单的系统配置缓存 + */ + private final ConcurrentHashMap CONFIG_CACHE = new ConcurrentHashMap<>(); + + @Resource + private ConfigDao configDao; + + @SmartReload(ReloadConst.CONFIG_RELOAD) + public void configReload(String param) { + this.loadConfigCache(); + } + + /** + * 初始化系统设置缓存 + */ + @PostConstruct + private void loadConfigCache() { + CONFIG_CACHE.clear(); + List entityList = configDao.selectList(null); + if (CollectionUtils.isEmpty(entityList)) { + return; + } + entityList.forEach(entity -> this.CONFIG_CACHE.put(entity.getConfigKey().toLowerCase(), entity)); + log.info("################# 系统配置缓存初始化完毕:{} ###################", CONFIG_CACHE.size()); + } + + /** + * 刷新系统设置缓存 + */ + private void refreshConfigCache(Long configId) { + // 重新查询 加入缓存 + ConfigEntity configEntity = configDao.selectById(configId); + if (null == configEntity) { + return; + } + this.CONFIG_CACHE.put(configEntity.getConfigKey().toLowerCase(), configEntity); + } + + /** + * 分页查询系统配置 + * + */ + public ResponseDTO> queryConfigPage(ConfigQueryForm queryForm) { + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List entityList = configDao.queryByPage(page, queryForm); + PageResult pageResult = SmartPageUtil.convert2PageResult(page, entityList, ConfigVO.class); + return ResponseDTO.ok(pageResult); + } + + /** + * 查询配置缓存 + * + */ + public ConfigVO getConfig(ConfigKeyEnum configKey) { + return this.getConfig(configKey.getValue()); + } + + /** + * 查询配置缓存 + * + */ + public ConfigVO getConfig(String configKey) { + if (StrUtil.isBlank(configKey)) { + return null; + } + ConfigEntity entity = this.CONFIG_CACHE.get(configKey.toLowerCase()); + return SmartBeanUtil.copy(entity, ConfigVO.class); + } + + /** + * 查询配置缓存参数 + * + */ + public String getConfigValue(ConfigKeyEnum configKey) { + ConfigVO config = this.getConfig(configKey); + return config == null ? null : config.getConfigValue(); + } + + /** + * 根据参数key查询 并转换为对象 + * + */ + public T getConfigValue2Obj(ConfigKeyEnum configKey, Class clazz) { + String configValue = this.getConfigValue(configKey); + return JSON.parseObject(configValue, clazz); + } + + /** + * 添加系统配置 + * + */ + public ResponseDTO add(ConfigAddForm configAddForm) { + ConfigEntity entity = configDao.selectByKey(configAddForm.getConfigKey()); + if (null != entity) { + return ResponseDTO.error(UserErrorCode.ALREADY_EXIST); + } + entity = SmartBeanUtil.copy(configAddForm, ConfigEntity.class); + configDao.insert(entity); + + // 刷新缓存 + this.refreshConfigCache(entity.getConfigId()); + return ResponseDTO.ok(); + } + + /** + * 更新系统配置 + * + */ + public ResponseDTO updateConfig(ConfigUpdateForm updateDTO) { + Long configId = updateDTO.getConfigId(); + ConfigEntity entity = configDao.selectById(configId); + if (null == entity) { + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST); + } + ConfigEntity alreadyEntity = configDao.selectByKey(updateDTO.getConfigKey()); + if (null != alreadyEntity && !Objects.equals(configId, alreadyEntity.getConfigId())) { + return ResponseDTO.error(UserErrorCode.ALREADY_EXIST, "config key 已存在"); + } + + // 更新数据 + entity = SmartBeanUtil.copy(updateDTO, ConfigEntity.class); + configDao.updateById(entity); + + // 刷新缓存 + this.refreshConfigCache(configId); + return ResponseDTO.ok(); + } + + /** + * 更新系统配置 + * + */ + public ResponseDTO updateValueByKey(ConfigKeyEnum key, String value) { + ConfigVO config = this.getConfig(key); + if (null == config) { + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST); + } + + // 更新数据 + Long configId = config.getConfigId(); + ConfigEntity entity = new ConfigEntity(); + entity.setConfigId(configId); + entity.setConfigValue(value); + configDao.updateById(entity); + + // 刷新缓存 + this.refreshConfigCache(configId); + return ResponseDTO.ok(); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/domain/ConfigAddForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/domain/ConfigAddForm.java new file mode 100644 index 0000000..480c186 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/domain/ConfigAddForm.java @@ -0,0 +1,38 @@ +package net.lab1024.sa.base.module.support.config.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +/** + * 添加配置表单 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-14 20:46:27 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class ConfigAddForm { + + @Schema(description = "参数key") + @NotBlank(message = "参数key不能为空") + @Length(max = 255, message = "参数key最多255个字符") + private String configKey; + + @Schema(description = "参数的值") + @NotBlank(message = "参数的值不能为空") + @Length(max = 60000, message = "参数的值最多60000个字符") + private String configValue; + + @Schema(description = "参数名称") + @NotBlank(message = "参数名称不能为空") + @Length(max = 255, message = "参数名称最多255个字符") + private String configName; + + @Schema(description = "备注") + @Length(max = 255, message = "备注最多255个字符") + private String remark; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/domain/ConfigEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/domain/ConfigEntity.java new file mode 100644 index 0000000..1f8f696 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/domain/ConfigEntity.java @@ -0,0 +1,49 @@ +package net.lab1024.sa.base.module.support.config.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 系统配置参数 实体类 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-14 20:46:27 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName("t_config") +public class ConfigEntity { + + @TableId(type = IdType.AUTO) + private Long configId; + + /** + * 参数key + */ + private String configKey; + + /** + * 参数的值 + */ + private String configValue; + + /** + * 参数名称 + */ + private String configName; + + /** + * 备注 + */ + private String remark; + + private LocalDateTime updateTime; + + private LocalDateTime createTime; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/domain/ConfigQueryForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/domain/ConfigQueryForm.java new file mode 100644 index 0000000..db5b8a1 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/domain/ConfigQueryForm.java @@ -0,0 +1,23 @@ +package net.lab1024.sa.base.module.support.config.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; +import org.hibernate.validator.constraints.Length; + +/** + * 分页查询 系统配置 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-14 20:46:27 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class ConfigQueryForm extends PageParam { + + @Schema(description = "参数KEY") + @Length(max = 50, message = "参数Key最多50字符") + private String configKey; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/domain/ConfigUpdateForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/domain/ConfigUpdateForm.java new file mode 100644 index 0000000..fb84a72 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/domain/ConfigUpdateForm.java @@ -0,0 +1,22 @@ +package net.lab1024.sa.base.module.support.config.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 配置更新表单 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-14 20:46:27 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class ConfigUpdateForm extends ConfigAddForm { + + @Schema(description = "configId") + @NotNull(message = "configId不能为空") + private Long configId; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/domain/ConfigVO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/domain/ConfigVO.java new file mode 100644 index 0000000..f80e560 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/domain/ConfigVO.java @@ -0,0 +1,39 @@ +package net.lab1024.sa.base.module.support.config.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 配置信息 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-14 20:46:27 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class ConfigVO { + @Schema(description = "主键") + private Long configId; + + @Schema(description = "参数key") + private String configKey; + + @Schema(description = "参数的值") + private String configValue; + + @Schema(description = "参数名称") + private String configName; + + @Schema(description = "备注") + private String remark; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "上次修改时间") + private LocalDateTime updateTime; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datamasking/DataMasking.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datamasking/DataMasking.java new file mode 100644 index 0000000..c3847d7 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datamasking/DataMasking.java @@ -0,0 +1,27 @@ +package net.lab1024.sa.base.module.support.datamasking; + +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import net.lab1024.sa.base.common.json.serializer.DataMaskingSerializer; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 脱敏注解 + * + * @author 罗伊 + * @description: + * @date 2024/7/21 4:39 下午 + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@JacksonAnnotationsInside +@JsonSerialize(using = DataMaskingSerializer.class, nullsUsing = DataMaskingSerializer.class) +public @interface DataMasking { + + DataMaskingTypeEnum value() default DataMaskingTypeEnum.COMMON; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datamasking/DataMaskingTypeEnum.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datamasking/DataMaskingTypeEnum.java new file mode 100644 index 0000000..7df897a --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datamasking/DataMaskingTypeEnum.java @@ -0,0 +1,40 @@ +package net.lab1024.sa.base.module.support.datamasking; + +import cn.hutool.core.util.DesensitizedUtil; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 脱敏数据类型 + * + * @Author 1024创新实验室-创始人兼主任:卓大 + * @Date 2024/8/1 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 ,Since 2012 + */ + +@AllArgsConstructor +@Getter +public enum DataMaskingTypeEnum { + + COMMON(null, "通用"), + PHONE(DesensitizedUtil.DesensitizedType.MOBILE_PHONE, "手机号"), + CHINESE_NAME(DesensitizedUtil.DesensitizedType.CHINESE_NAME, "中文名"), + ID_CARD(DesensitizedUtil.DesensitizedType.ID_CARD, "身份证号"), + FIXED_PHONE(DesensitizedUtil.DesensitizedType.FIXED_PHONE, "座机号"), + ADDRESS(DesensitizedUtil.DesensitizedType.ADDRESS, "地址"), + EMAIL(DesensitizedUtil.DesensitizedType.EMAIL, "电子邮件"), + PASSWORD(DesensitizedUtil.DesensitizedType.PASSWORD, "密码"), + CAR_LICENSE(DesensitizedUtil.DesensitizedType.CAR_LICENSE, "中国大陆车牌"), + BANK_CARD(DesensitizedUtil.DesensitizedType.BANK_CARD, "银行卡"), + USER_ID(DesensitizedUtil.DesensitizedType.USER_ID, "用户id"); + + + + private DesensitizedUtil.DesensitizedType type; + + private String desc; + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datamasking/SmartDataMaskingUtil.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datamasking/SmartDataMaskingUtil.java new file mode 100644 index 0000000..4443da4 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datamasking/SmartDataMaskingUtil.java @@ -0,0 +1,216 @@ +package net.lab1024.sa.base.module.support.datamasking; + +import cn.hutool.core.util.DesensitizedUtil; +import cn.hutool.core.util.StrUtil; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; + +import java.beans.IntrospectionException; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 脱敏工具类 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2024-07-23 21:38:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class SmartDataMaskingUtil { + + /** + * 类 加注解字段缓存 + */ + private static final ConcurrentHashMap, List> fieldMap = new ConcurrentHashMap<>(); + + public static String dataMasking(String value) { + if (StringUtils.isBlank(value)) { + return value; + } + + if (value.length() < 4) { + return StrUtil.hide(value, 0, value.length()); + } + + int valueLength = value.length(); + int startHideIndex = getHideStartIndex(valueLength); + int endHideIndex = getHideEndIndex(valueLength); + return StrUtil.hide(value, startHideIndex, endHideIndex); + } + + public static Object dataMasking(Object value, DataMaskingTypeEnum dataType) { + + if (value == null) { + return null; + } + + if (dataType == null) { + return dataMasking(String.valueOf(value)); + } + + switch (dataType) { + case PHONE: + return DesensitizedUtil.mobilePhone(String.valueOf(value)); + case CHINESE_NAME: + return DesensitizedUtil.chineseName(String.valueOf(value)); + case ID_CARD: + return DesensitizedUtil.idCardNum(String.valueOf(value), 6, 2); + case FIXED_PHONE: + return DesensitizedUtil.fixedPhone(String.valueOf(value)); + case ADDRESS: + return StrUtil.hide(String.valueOf(value), 6, String.valueOf(value).length() - 1); + case EMAIL: + return DesensitizedUtil.email(String.valueOf(value)); + case PASSWORD: + return DesensitizedUtil.password(String.valueOf(value)); + case CAR_LICENSE: + return DesensitizedUtil.carLicense(String.valueOf(value)); + case BANK_CARD: + return DesensitizedUtil.bankCard(String.valueOf(value)); + case USER_ID: + return DesensitizedUtil.userId(); + default: + return dataMasking(String.valueOf(value)); + } + } + + /** + * 批量脱敏 + */ + public static void dataMasking(Collection objectList) throws IntrospectionException, InvocationTargetException, IllegalAccessException { + if (CollectionUtils.isEmpty(objectList)) { + return; + } + + for (T object : objectList) { + dataMasking(object); + } + } + + + /** + * 单个脱敏 + */ + public static void dataMasking(T object) throws IntrospectionException, InvocationTargetException, IllegalAccessException { + Class tClass = object.getClass(); + List fieldList = getField(object); + for (Field field : fieldList) { + field.setAccessible(true); + String fieldValue = ""; + try { + PropertyDescriptor pd = new PropertyDescriptor(field.getName(), tClass); + Method getMethod = pd.getReadMethod(); + Object value = getMethod.invoke(object); + if (value != null) { + fieldValue = value.toString(); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + if (StringUtils.isBlank(fieldValue)) { + continue; + } + int valueLength = fieldValue.length(); + int startHideIndex = getHideStartIndex(valueLength); + int endHideIndex = getHideEndIndex(valueLength); + try { + field.set(object, StrUtil.hide(fieldValue, startHideIndex, endHideIndex)); + } catch (Exception e1) { + throw new RuntimeException(e1); + } + } + } + + private static int getHideStartIndex(int totalLength) { + if (totalLength <= 4) { + return 1; + } else if (totalLength <= 6) { + return 1; + } else if (totalLength <= 10) { + return 2; + } else if (totalLength <= 18) { + return 3; + } else if (totalLength <= 27) { + return 5; + } else if (totalLength <= 34) { + return 7; + } else if (totalLength <= 41) { + return 9; + } else { + return 15; + } + } + + private static int getHideEndIndex(int totalLength) { + if (totalLength <= 4) { + return totalLength - 1; + } else if (totalLength <= 6) { + return totalLength - 2; + } else if (totalLength <= 10) { + return totalLength - 2; + } else if (totalLength <= 18) { + return totalLength - 4; + } else if (totalLength <= 27) { + return totalLength - 6; + } else if (totalLength <= 34) { + return totalLength - 8; + } else if (totalLength <= 41) { + return totalLength - 10; + } else { + return totalLength - 16; + } + } + + + public static List getField(Object object) throws IntrospectionException { + // 从缓存中查询 + Class tClass = object.getClass(); + List fieldList = fieldMap.get(tClass); + if (null != fieldList) { + return fieldList; + } + + // 这一段递归代码 是为了 从父类中获取属性 + Class tempClass = tClass; + fieldList = new ArrayList<>(); + while (tempClass != null) { + Field[] declaredFields = tempClass.getDeclaredFields(); + for (Field field : declaredFields) { + boolean stringField = false; + try { + PropertyDescriptor pd = new PropertyDescriptor(field.getName(), tClass); + Method getMethod = pd.getReadMethod(); + Type returnType = getMethod.getGenericReturnType(); + stringField = "java.lang.String".equals(returnType.getTypeName()); + } catch (Exception e) { + throw new RuntimeException(e); + } + if (field.isAnnotationPresent(DataMasking.class) && stringField) { + field.setAccessible(true); + fieldList.add(field); + } + } + tempClass = tempClass.getSuperclass(); + } + fieldMap.put(tClass, fieldList); + return fieldList; + } + + public static void main(String[] args) { + System.out.println(dataMasking("a", null)); + System.out.println(dataMasking("ab", null)); + System.out.println(dataMasking("abc", null)); + System.out.println(dataMasking("abcd", null)); + System.out.println(dataMasking("abcde", null)); + } + +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/annoation/DataTracerFieldBigDecimal.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/annoation/DataTracerFieldBigDecimal.java new file mode 100644 index 0000000..c68dbdd --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/annoation/DataTracerFieldBigDecimal.java @@ -0,0 +1,21 @@ +package net.lab1024.sa.base.module.support.datatracer.annoation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 数据变动字段注解 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-07-23 19:38:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface DataTracerFieldBigDecimal { + int scale() default 2; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/annoation/DataTracerFieldDict.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/annoation/DataTracerFieldDict.java new file mode 100644 index 0000000..6fefae2 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/annoation/DataTracerFieldDict.java @@ -0,0 +1,22 @@ +package net.lab1024.sa.base.module.support.datatracer.annoation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 字典的字段 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-07-23 19:38:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface DataTracerFieldDict { + + String dictCode(); +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/annoation/DataTracerFieldEnum.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/annoation/DataTracerFieldEnum.java new file mode 100644 index 0000000..b46c574 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/annoation/DataTracerFieldEnum.java @@ -0,0 +1,25 @@ +package net.lab1024.sa.base.module.support.datatracer.annoation; + +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 字段枚举 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-07-23 19:38:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface DataTracerFieldEnum { + + Class enumClass() default BaseEnum.class; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/annoation/DataTracerFieldLabel.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/annoation/DataTracerFieldLabel.java new file mode 100644 index 0000000..4209e17 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/annoation/DataTracerFieldLabel.java @@ -0,0 +1,26 @@ +package net.lab1024.sa.base.module.support.datatracer.annoation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 字段标签 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-07-23 19:38:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface DataTracerFieldLabel { + /** + * 本属性的注释信息 + * @return + */ + String value() default ""; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/annoation/DataTracerFieldSql.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/annoation/DataTracerFieldSql.java new file mode 100644 index 0000000..d373ebd --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/annoation/DataTracerFieldSql.java @@ -0,0 +1,40 @@ +package net.lab1024.sa.base.module.support.datatracer.annoation; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 支持查询sql + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-07-23 19:38:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface DataTracerFieldSql { + + /** + * 关联字段名称 + * @return + */ + String relateColumn() default "id"; + + /** + * 关联显示的字段 + * @return + */ + String relateDisplayColumn() default ""; + /** + * 是否关联字段查询Mapper + * @return + */ + Class relateMapper() default BaseMapper.class; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/constant/DataTracerConst.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/constant/DataTracerConst.java new file mode 100644 index 0000000..b80fcd2 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/constant/DataTracerConst.java @@ -0,0 +1,27 @@ +package net.lab1024.sa.base.module.support.datatracer.constant; + +/** + * 常量 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-07-23 19:38:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class DataTracerConst { + + public static final String TAB = " "; + + public static final String SPLIT_LINE = "-----------------------------"; + + public static final String BLANK = " "; + public static final String SPLIT = ": "; + public static final String HTML_BR = "
"; + + public static final String INSERT = "新增"; + + public static final String DELETE = "删除"; + + public static final String UPDATE = "修改"; +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/constant/DataTracerTypeEnum.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/constant/DataTracerTypeEnum.java new file mode 100644 index 0000000..cf572fe --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/constant/DataTracerTypeEnum.java @@ -0,0 +1,46 @@ +package net.lab1024.sa.base.module.support.datatracer.constant; + + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 数据业务类型 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-07-23 19:38:52- + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@AllArgsConstructor +@Getter +public enum DataTracerTypeEnum implements BaseEnum { + + /** + * 商品 + */ + GOODS(1, "商品"), + + /** + *通知公告 + */ + OA_NOTICE(2, "OA-通知公告"), + + /** + * 企业信息 + */ + OA_ENTERPRISE(3, "OA-企业信息"), + + /** + *通知公告 + */ + OA_LEAVE(4, "OA-请假申请"), + + ; + + private final Integer value; + + private final String desc; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/controller/DataTracerController.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/controller/DataTracerController.java new file mode 100644 index 0000000..8582bce --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/controller/DataTracerController.java @@ -0,0 +1,39 @@ +package net.lab1024.sa.base.module.support.datatracer.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import net.lab1024.sa.base.common.controller.SupportBaseController; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import net.lab1024.sa.base.module.support.datatracer.domain.form.DataTracerQueryForm; +import net.lab1024.sa.base.module.support.datatracer.domain.vo.DataTracerVO; +import net.lab1024.sa.base.module.support.datatracer.service.DataTracerService; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * 数据变动记录 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-07-23 19:38:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Tag(name = SwaggerTagConst.Support.DATA_TRACER) +@RestController +public class DataTracerController extends SupportBaseController { + + @Resource + private DataTracerService dataTracerService; + + @Operation(summary = "分页查询业务操作日志 - @author 卓大") + @PostMapping("/dataTracer/query") + public ResponseDTO> query(@Valid @RequestBody DataTracerQueryForm queryForm) { + return dataTracerService.query(queryForm); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/dao/DataTracerDao.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/dao/DataTracerDao.java new file mode 100644 index 0000000..9f467a7 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/dao/DataTracerDao.java @@ -0,0 +1,37 @@ +package net.lab1024.sa.base.module.support.datatracer.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.base.module.support.datatracer.domain.entity.DataTracerEntity; +import net.lab1024.sa.base.module.support.datatracer.domain.form.DataTracerQueryForm; +import net.lab1024.sa.base.module.support.datatracer.domain.vo.DataTracerVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * dao: t_data_tracker + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-07-23 19:38:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface DataTracerDao extends BaseMapper { + + /** + * 操作记录查询 + * + */ + List selectRecord(@Param("dataId") Long dataId, @Param("dataType") Integer dataType); + + /** + * 分页查询 + * + */ + List query(Page page, @Param("query") DataTracerQueryForm queryForm); +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/domain/bo/DataTracerContentBO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/domain/bo/DataTracerContentBO.java new file mode 100644 index 0000000..a129e96 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/domain/bo/DataTracerContentBO.java @@ -0,0 +1,39 @@ +package net.lab1024.sa.base.module.support.datatracer.domain.bo; + +import lombok.Data; + +import java.lang.reflect.Field; + +/** + * 变动内容 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-07-23 19:38:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class DataTracerContentBO { + + /** + * 变动字段 + */ + private Field field; + + /** + * 变动字段的值 + */ + private Object fieldValue; + + /** + * 变动字段描述 + */ + private String fieldDesc; + + /** + * 变动内容 + */ + private String fieldContent; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/domain/entity/DataTracerEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/domain/entity/DataTracerEntity.java new file mode 100644 index 0000000..3df5f7b --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/domain/entity/DataTracerEntity.java @@ -0,0 +1,95 @@ +package net.lab1024.sa.base.module.support.datatracer.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import net.lab1024.sa.base.module.support.datatracer.constant.DataTracerTypeEnum; + +import java.time.LocalDateTime; + +/** + * 数据记录 实体 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-07-23 19:38:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName("t_data_tracer") +public class DataTracerEntity { + + @TableId(type = IdType.AUTO) + private Long dataTracerId; + /** + * 数据id + */ + private Long dataId; + /** + * 业务类型 + * {@link DataTracerTypeEnum} + */ + private Integer type; + + /** + * 内容 + */ + private String content; + + /** + * diff 差异:旧的数据 + */ + private String diffOld; + + /** + * 差异:新的数据 + */ + private String diffNew; + + /** + * 扩展字段 + */ + private String extraData; + + /** + * 用户 + */ + private Long userId; + + /** + * 用户类型 + */ + private Integer userType; + + /** + * 用户名 + */ + private String userName; + + /** + * 请求ip + */ + private String ip; + + /** + * 请求ip地区 + */ + private String ipRegion; + + /** + * 请求头 + */ + private String userAgent; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/domain/form/DataTracerForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/domain/form/DataTracerForm.java new file mode 100644 index 0000000..2052ce0 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/domain/form/DataTracerForm.java @@ -0,0 +1,54 @@ +package net.lab1024.sa.base.module.support.datatracer.domain.form; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import net.lab1024.sa.base.module.support.datatracer.constant.DataTracerTypeEnum; + +/** + * 数据变动表单 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-07-23 19:38:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class DataTracerForm { + + /** + * 业务id + */ + private Long dataId; + + /** + * 业务类型 + */ + private DataTracerTypeEnum type; + + /** + * 操作内容 + */ + private String content; + + /** + * diff 差异:旧的数据 + */ + private String diffOld; + + /** + * 差异:新的数据 + */ + private String diffNew; + + /** + * 扩展字段 + */ + private String extraData; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/domain/form/DataTracerQueryForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/domain/form/DataTracerQueryForm.java new file mode 100644 index 0000000..a1f6c9c --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/domain/form/DataTracerQueryForm.java @@ -0,0 +1,31 @@ +package net.lab1024.sa.base.module.support.datatracer.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.module.support.datatracer.constant.DataTracerTypeEnum; + +/** + * 查询表单 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-07-23 19:38:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class DataTracerQueryForm extends PageParam { + + @SchemaEnum(DataTracerTypeEnum.class) + private Integer type; + + @Schema(description = "业务id") + @NotNull(message = "业务id不能为空") + private Long dataId; + + @Schema(description = "关键字") + private String keywords; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/domain/vo/DataTracerVO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/domain/vo/DataTracerVO.java new file mode 100644 index 0000000..4e36cfc --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/domain/vo/DataTracerVO.java @@ -0,0 +1,65 @@ +package net.lab1024.sa.base.module.support.datatracer.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.enumeration.UserTypeEnum; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.module.support.datatracer.constant.DataTracerTypeEnum; + +import java.time.LocalDateTime; + +/** + * 变动记录 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-07-23 19:38:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class DataTracerVO { + + @Schema(description = "日志id") + private Long dataTracerId; + + @Schema(description = "单据id") + private Long dataId; + + @SchemaEnum(value = DataTracerTypeEnum.class, desc = "业务类型") + private Integer type; + + @Schema(description = "操作内容") + private String content; + + @Schema(description = "diff 差异:旧的数据") + private String diffOld; + + @Schema(description = "差异:新的数据") + private String diffNew; + + @Schema(description = "扩展字段") + private String extraData; + + @Schema(description = "操作人") + private Long userId; + + @SchemaEnum(value = UserTypeEnum.class, desc = "用户类型") + private Integer userType; + + @Schema(description = "操作人名称") + private String userName; + + @Schema(description = "userAgent") + private String userAgent; + + @Schema(description = "ip") + private String ip; + + @Schema(description = "ip地区") + private String ipRegion; + + @Schema(description = "操作时间") + private LocalDateTime createTime; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/manager/DataTracerManger.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/manager/DataTracerManger.java new file mode 100644 index 0000000..6764151 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/manager/DataTracerManger.java @@ -0,0 +1,19 @@ +package net.lab1024.sa.base.module.support.datatracer.manager; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import net.lab1024.sa.base.module.support.datatracer.dao.DataTracerDao; +import net.lab1024.sa.base.module.support.datatracer.domain.entity.DataTracerEntity; +import org.springframework.stereotype.Service; + +/** + * manager层 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-07-23 19:38:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class DataTracerManger extends ServiceImpl { +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/service/DataTracerChangeContentService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/service/DataTracerChangeContentService.java new file mode 100644 index 0000000..e872c7b --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/service/DataTracerChangeContentService.java @@ -0,0 +1,457 @@ +package net.lab1024.sa.base.module.support.datatracer.service; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.json.JSONUtil; +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.google.common.base.CaseFormat; +import com.google.common.collect.Lists; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.util.SmartBigDecimalUtil; +import net.lab1024.sa.base.common.util.SmartEnumUtil; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.module.support.datatracer.annoation.*; +import net.lab1024.sa.base.module.support.datatracer.constant.DataTracerConst; +import net.lab1024.sa.base.module.support.datatracer.domain.bo.DataTracerContentBO; +import net.lab1024.sa.base.module.support.dict.domain.vo.DictDataVO; +import net.lab1024.sa.base.module.support.dict.service.DictService; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Service; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 数据变更内容 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-07-23 19:38:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@Service +public class DataTracerChangeContentService { + + @Resource + private ApplicationContext applicationContext; + @Resource + private DictService dictService; + /** + * 字段描述缓存 + */ + private final ConcurrentHashMap fieldDescCacheMap = new ConcurrentHashMap<>(); + + /** + * 类 加注解字段缓存 + */ + private final ConcurrentHashMap, List> fieldMap = new ConcurrentHashMap<>(); + + /** + * 数据批量对比 + * + * @param oldObjectList 原始对象列表 + * @param newObjectList 新的对象列表 + * @param Class类型 + * @return 变更内容 + */ + public String getChangeContent(List oldObjectList, List newObjectList) { + boolean valid = this.valid(oldObjectList, newObjectList); + if (!valid) { + return ""; + } + String operateType = this.getOperateType(oldObjectList, newObjectList); + String operateContent = ""; + if (DataTracerConst.INSERT.equals(operateType) || DataTracerConst.DELETE.equals(operateType)) { + operateContent = this.getObjectListContent(newObjectList); + if (StringUtils.isEmpty(operateContent)) { + return ""; + } + return operateType + ":" + operateContent; + } + if (DataTracerConst.UPDATE.equals(operateType)) { + return this.getUpdateContentList(oldObjectList, newObjectList); + } + return operateContent; + } + + + /** + * 解析多个对象的变更,删除,新增 + * oldObject 为空 ,newObject 不为空 为新增 + * oldObject 不为空 ,newObject 不空 为删除 + * 都不为空为编辑 + * + * @param oldObject 原始对象 + * @param newObject 新对象 + * @return 变更内容 + */ + public String getChangeContent(Object oldObject, Object newObject) { + boolean valid = this.valid(oldObject, newObject); + if (!valid) { + return ""; + } + String operateType = this.getOperateType(oldObject, newObject); + String operateContent = ""; + if (DataTracerConst.INSERT.equals(operateType) || DataTracerConst.DELETE.equals(operateType)) { + operateContent = this.getAddDeleteContent(newObject); + } + if (DataTracerConst.UPDATE.equals(operateType)) { + operateContent = this.getUpdateContent(oldObject, newObject); + } + if (StringUtils.isEmpty(operateContent)) { + return ""; + } + return operateContent; + } + + /** + * 解析单个bean的内容 + * + * @param object 普通对象 + * @return 单个内容 + */ + public String getChangeContent(Object object) { + return this.getAddDeleteContent(object); + } + + // ---------------------------- 以下 是 私有private 方法 ---------------------------- + + /** + * 获取新增或删除操作内容 + * + * @param object 新增或删除的对象 + */ + private String getAddDeleteContent(Object object) { + List fields = this.getField(object); + Map beanParseMap = this.fieldParse(object, fields); + return this.getAddDeleteContent(beanParseMap); + } + + /** + * 单个对象变动内容 + * + * @param oldObjectList 旧的对象列表 + * @param newObjectList 新的对象列表 + * @return 拼接后的内容 + */ + private String getUpdateContentList(List oldObjectList, List newObjectList) { + String oldContent = this.getObjectListContent(oldObjectList); + String newContent = this.getObjectListContent(newObjectList); + if (oldContent.equals(newContent)) { + return ""; + } + if (StringUtils.isEmpty(oldContent) && StringUtils.isEmpty(newContent)) { + return ""; + } + return "【原数据】:
" + oldContent + "
" + "【新数据】:
" + newContent; + } + + /** + * 获取一个对象的内容信息 + * + * @param objectList 对象列表 + * @param 类型 + * @return 内容 + */ + private String getObjectListContent(List objectList) { + if (CollectionUtils.isEmpty(objectList)) { + return ""; + } + List fields = this.getField(objectList.get(0)); + List contentList = Lists.newArrayList(); + for (Object objItem : objectList) { + Map beanParseMap = this.fieldParse(objItem, fields); + contentList.add(this.getAddDeleteContent(beanParseMap)); + } + return StringUtils.join(contentList, "
"); + } + + private String getAddDeleteContent(Map beanParseMap) { + List contentList = new ArrayList<>(); + for (Entry entry : beanParseMap.entrySet()) { + DataTracerContentBO dataTracerContentBO = entry.getValue(); + boolean jsonFlag = JSONUtil.isTypeJSON(dataTracerContentBO.getFieldContent()); + String filedDesc = dataTracerContentBO.getFieldDesc(); + if (jsonFlag) { + contentList.add(filedDesc + "(请进入详情查看)"); + } else { + contentList.add(dataTracerContentBO.getFieldDesc() + ":" + dataTracerContentBO.getFieldContent()); + } + } + String operateContent = StringUtils.join(contentList, "
"); + if (StringUtils.isEmpty(operateContent)) { + return ""; + } + return operateContent; + } + + + /** + * 获取更新操作内容 + * + * @param oldObject 原始对象 + * @param newObject 新对象 + * @return + */ + private String getUpdateContent(T oldObject, T newObject) { + List fields = this.getField(oldObject); + List contentList = new ArrayList<>(); + Map oldBeanParseMap = this.fieldParse(oldObject, fields); + Map newBeanParseMap = this.fieldParse(newObject, fields); + //oldBeanParseMap与newBeanParseMap size一定相同 + for (Entry entry : oldBeanParseMap.entrySet()) { + String fieldName = entry.getKey(); + // 新旧对象内容 + DataTracerContentBO oldContentBO = entry.getValue(); + DataTracerContentBO newContentBO = newBeanParseMap.get(fieldName); + // fieldContent + String oldContent = oldContentBO == null || oldContentBO.getFieldContent() == null ? "" : oldContentBO.getFieldContent(); + String newContent = newContentBO == null || newContentBO.getFieldContent() == null ? "" : newContentBO.getFieldContent(); + + if (oldContent.equals(newContent)) { + continue; + } + String fieldDesc = oldContentBO.getFieldDesc(); + boolean jsonFlag = JSONUtil.isTypeJSON(oldContent) || JSONUtil.isTypeJSON(newContent); + if (jsonFlag) { + String content = fieldDesc + "【进入详情查看】"; + contentList.add(content); + continue; + } + String content = fieldDesc + ":" + "由【" + oldContent + "】变更为【" + newContent + "】"; + contentList.add(content); + } + if (CollectionUtils.isEmpty(contentList)) { + return ""; + } + String operateContent = StringUtils.join(contentList, "
"); + if (StringUtils.isEmpty(operateContent)) { + return ""; + } + return operateContent; + } + + + /** + * 接bean对象 + * + * @param object 对象 + * @param fields 字段 + * @return + */ + private Map fieldParse(Object object, List fields) { + if (fields == null || fields.isEmpty()) { + return new HashMap<>(); + } + //对象解析结果 + Map objectParse = new HashMap<>(16); + for (Field field : fields) { + field.setAccessible(true); + String desc = this.getFieldDesc(field); + if (StringUtils.isEmpty(desc)) { + continue; + } + DataTracerContentBO dataTracerContentBO = this.getFieldValue(field, object); + if (dataTracerContentBO != null) { + dataTracerContentBO.setFieldDesc(desc); + objectParse.put(field.getName(), dataTracerContentBO); + } + } + return objectParse; + } + + /** + * 获取字段值 + */ + private DataTracerContentBO getFieldValue(Field field, Object object) { + Object fieldValue = ""; + Class clazz = object.getClass(); + try { + PropertyDescriptor pd = new PropertyDescriptor(field.getName(), clazz); + Method get = pd.getReadMethod(); + fieldValue = get.invoke(object); + } catch (Exception e) { + log.error("bean operate log: reflect field value error " + field.getName()); + return null; + } + if (fieldValue == null) { + return null; + } + + String fieldContent = ""; + DataTracerFieldEnum dataTracerFieldEnum = field.getAnnotation(DataTracerFieldEnum.class); + DataTracerFieldSql dataTracerFieldSql = field.getAnnotation(DataTracerFieldSql.class); + DataTracerFieldDict dataTracerFieldDict = field.getAnnotation(DataTracerFieldDict.class); + if (dataTracerFieldEnum != null) { + if (fieldValue instanceof Collection) { + fieldContent = SmartEnumUtil.getEnumDescByValueList((Collection) fieldValue, dataTracerFieldEnum.enumClass()); + } else { + fieldContent = SmartEnumUtil.getEnumDescByValue(fieldValue, dataTracerFieldEnum.enumClass()); + } + } else if (dataTracerFieldDict != null) { + DictDataVO dictData = dictService.getDictData(dataTracerFieldDict.dictCode(), fieldValue.toString()); + fieldContent = dictData == null ? fieldValue.toString() : dictData.getDataLabel(); + } else if (dataTracerFieldSql != null) { + fieldContent = this.getRelateDisplayValue(fieldValue, dataTracerFieldSql); + } else if (fieldValue instanceof Date) { + fieldContent = DateUtil.formatDateTime((Date) fieldValue); + } else if (fieldValue instanceof LocalDateTime) { + fieldContent = LocalDateTimeUtil.formatNormal((LocalDateTime) fieldValue); + } else if (fieldValue instanceof LocalDate) { + fieldContent = LocalDateTimeUtil.formatNormal((LocalDate) fieldValue); + } else if (fieldValue instanceof BigDecimal) { + DataTracerFieldBigDecimal dataTracerFieldBigDecimal = field.getAnnotation(DataTracerFieldBigDecimal.class); + if (dataTracerFieldBigDecimal != null) { + BigDecimal value = SmartBigDecimalUtil.setScale((BigDecimal) fieldValue, dataTracerFieldBigDecimal.scale()); + fieldContent = value.toString(); + } + } else { + fieldContent = JSON.toJSONString(fieldValue); + } + DataTracerContentBO dataTracerContentBO = new DataTracerContentBO(); + dataTracerContentBO.setField(field); + dataTracerContentBO.setFieldValue(fieldValue); + dataTracerContentBO.setFieldContent(fieldContent); + return dataTracerContentBO; + } + + /** + * 获取关联字段的显示值 + */ + private String getRelateDisplayValue(Object fieldValue, DataTracerFieldSql dataTracerFieldSql) { + Class relateMapper = dataTracerFieldSql.relateMapper(); + BaseMapper mapper = applicationContext.getBean(relateMapper); + if (mapper == null) { + return ""; + } + String relateFieldValue = fieldValue.toString(); + QueryWrapper qw = new QueryWrapper(); + qw.select(CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, dataTracerFieldSql.relateDisplayColumn())); + qw.in(CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, dataTracerFieldSql.relateColumn()), relateFieldValue); + List displayValue = mapper.selectObjs(qw); + if (CollectionUtils.isEmpty(displayValue)) { + return ""; + } + return SmartStringUtil.join(",", displayValue); + } + + /** + * 获取字段描述信息 优先 OperateField 没得话swagger判断 + */ + private String getFieldDesc(Field field) { + // 根据字段名称 从缓存中查询 + String fieldName = field.toGenericString(); + String desc = fieldDescCacheMap.get(fieldName); + if (null != desc) { + return desc; + } + DataTracerFieldLabel operateField = field.getAnnotation(DataTracerFieldLabel.class); + if (operateField != null) { + return operateField.value(); + } + fieldDescCacheMap.put(fieldName, desc); + return desc; + } + + /** + * 获取操作类型 + */ + private String getOperateType(Object oldObject, Object newObject) { + if (oldObject == null && newObject != null) { + return DataTracerConst.INSERT; + } + if (oldObject != null && newObject == null) { + return DataTracerConst.DELETE; + } + return DataTracerConst.UPDATE; + } + + /** + * 校验是否进行比对 + */ + private boolean valid(Object oldObject, Object newObject) { + if (oldObject == null && newObject == null) { + return false; + } + if (oldObject == null) { + return true; + } + if (newObject == null) { + return true; + } + String oldClass = oldObject.getClass().getName(); + String newClass = newObject.getClass().getName(); + return oldClass.equals(newClass); + } + + + /** + * 校验 + */ + private boolean valid(List oldObjectList, List newObjectList) { + if (CollectionUtils.isEmpty(oldObjectList) && CollectionUtils.isEmpty(newObjectList)) { + return false; + } + if (CollectionUtils.isEmpty(oldObjectList) && CollectionUtils.isNotEmpty(newObjectList)) { + return true; + } + if (CollectionUtils.isNotEmpty(oldObjectList) && CollectionUtils.isEmpty(newObjectList)) { + return true; + } + if (CollectionUtils.isNotEmpty(oldObjectList) && CollectionUtils.isNotEmpty(newObjectList)) { + T oldObject = oldObjectList.get(0); + T newObject = newObjectList.get(0); + String oldClass = oldObject.getClass().getName(); + String newClass = newObject.getClass().getName(); + return oldClass.equals(newClass); + } + return true; + } + + /** + * 查询 包含 file key 注解的字段 + * 使用缓存 + */ + private List getField(Object obj) { + // 从缓存中查询 + Class tClass = obj.getClass(); + List fieldList = fieldMap.get(tClass); + if (null != fieldList) { + return fieldList; + } + + // 这一段递归代码 是为了 从父类中获取属性 + Class tempClass = tClass; + fieldList = new ArrayList<>(); + while (tempClass != null) { + Field[] declaredFields = tempClass.getDeclaredFields(); + for (Field field : declaredFields) { + // 过虑出有注解字段 + if (!field.isAnnotationPresent(DataTracerFieldLabel.class)) { + continue; + } + field.setAccessible(true); + fieldList.add(field); + } + tempClass = tempClass.getSuperclass(); + } + fieldMap.put(tClass, fieldList); + return fieldList; + } + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/service/DataTracerService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/service/DataTracerService.java new file mode 100644 index 0000000..d023ca0 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/service/DataTracerService.java @@ -0,0 +1,227 @@ +package net.lab1024.sa.base.module.support.datatracer.service; + + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.RequestUser; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartIpUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.common.util.SmartRequestUtil; +import net.lab1024.sa.base.module.support.datatracer.constant.DataTracerConst; +import net.lab1024.sa.base.module.support.datatracer.constant.DataTracerTypeEnum; +import net.lab1024.sa.base.module.support.datatracer.dao.DataTracerDao; +import net.lab1024.sa.base.module.support.datatracer.domain.entity.DataTracerEntity; +import net.lab1024.sa.base.module.support.datatracer.domain.form.DataTracerForm; +import net.lab1024.sa.base.module.support.datatracer.domain.form.DataTracerQueryForm; +import net.lab1024.sa.base.module.support.datatracer.domain.vo.DataTracerVO; +import net.lab1024.sa.base.module.support.datatracer.manager.DataTracerManger; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * 数据变动记录 Service + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-07-23 19:38:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@Service +public class DataTracerService { + + @Resource + private DataTracerDao dataTracerDao; + + @Resource + private DataTracerManger dataTracerManger; + + @Resource + private DataTracerChangeContentService dataTracerChangeContentService; + + /** + * 获取变更内容 + * + * @param object + * @return + */ + public String getChangeContent(Object object) { + return dataTracerChangeContentService.getChangeContent(object); + } + + /** + * 获取变更内容 + */ + public String getChangeContent(Object oldObject, Object newObject) { + return dataTracerChangeContentService.getChangeContent(oldObject, newObject); + } + + + /** + * 获取变更内容 + */ + public String getChangeContent(List oldObjectList, List newObjectList) { + return dataTracerChangeContentService.getChangeContent(oldObjectList, newObjectList); + } + + + /** + * 保存【修改】数据变动记录 + * + * @param dataId + * @param type + */ + public void update(Long dataId, DataTracerTypeEnum type, Object oldObject, Object newObject) { + DataTracerForm form = DataTracerForm.builder() + .dataId(dataId) + .type(type) + .content(dataTracerChangeContentService.getChangeContent(oldObject, newObject)) + .build(); + this.addTrace(form); + } + + + /** + * 保存【新增】数据变动记录 + * + * @param dataId + * @param type + */ + public void insert(Long dataId, DataTracerTypeEnum type) { + DataTracerForm form = DataTracerForm.builder().dataId(dataId).type(type).content(DataTracerConst.INSERT).build(); + this.addTrace(form); + } + + /** + * 保存【删除】数据变动记录 + * + * @param dataId + * @param type + */ + public void delete(Long dataId, DataTracerTypeEnum type) { + DataTracerForm form = DataTracerForm.builder().dataId(dataId).type(type).content(DataTracerConst.DELETE).build(); + this.addTrace(form); + } + + /** + * 保存【删除】数据变动记录 + * + * @param dataId + * @param type + */ + public void delete(Long dataId, DataTracerTypeEnum type, Object object) { + DataTracerForm form = DataTracerForm.builder().dataId(dataId).type(type).content(DataTracerConst.DELETE).build(); + this.addTrace(form); + } + + /** + * 保存【批量删除】数据变动记录 + * + * @param dataIdList + * @param type + */ + public void batchDelete(List dataIdList, DataTracerTypeEnum type) { + if (CollectionUtils.isEmpty(dataIdList)) { + return; + } + + this.addTraceList(dataIdList.stream().map(e -> DataTracerForm.builder() + .dataId(e) + .type(type) + .content(DataTracerConst.DELETE) + .build()) + .collect(Collectors.toList()) + ); + } + + /** + * 保存数据变动记录 + * + * @param dataId + * @param type + * @param content + */ + public void addTrace(Long dataId, DataTracerTypeEnum type, String content) { + DataTracerForm form = DataTracerForm.builder().dataId(dataId).type(type).content(content).build(); + this.addTrace(form); + } + + /** + * 保存数据变动记录 + */ + public void addTrace(DataTracerForm tracerForm) { + RequestUser requestUser = SmartRequestUtil.getRequestUser(); + this.addTrace(tracerForm, requestUser); + } + + + /** + * 保存数据变动记录 + */ + public void addTrace(DataTracerForm tracerForm, RequestUser requestUser) { + DataTracerEntity tracerEntity = SmartBeanUtil.copy(tracerForm, DataTracerEntity.class); + tracerEntity.setType(tracerForm.getType().getValue()); + if (requestUser != null) { + tracerEntity.setIp(requestUser.getIp()); + tracerEntity.setIpRegion(SmartIpUtil.getRegion(requestUser.getIp())); + tracerEntity.setUserAgent(requestUser.getUserAgent()); + tracerEntity.setUserId(requestUser.getUserId()); + tracerEntity.setUserType(requestUser.getUserType().getValue()); + tracerEntity.setUserName(requestUser.getUserName()); + } + dataTracerManger.save(tracerEntity); + } + + /** + * 批量保存数据变动记录 + */ + public void addTraceList(List tracerFormList) { + RequestUser requestUser = SmartRequestUtil.getRequestUser(); + this.addTraceList(tracerFormList, requestUser); + } + + /** + * 批量保存数据变动记录 + */ + public void addTraceList(List tracerFormList, RequestUser requestUser) { + if (CollectionUtils.isEmpty(tracerFormList)) { + return; + } + + List tracerEntityList = tracerFormList.stream().map(e -> { + DataTracerEntity tracerEntity = SmartBeanUtil.copy(e, DataTracerEntity.class); + tracerEntity.setType(e.getType().getValue()); + tracerEntity.setIp(requestUser.getIp()); + tracerEntity.setIpRegion(SmartIpUtil.getRegion(requestUser.getIp())); + tracerEntity.setUserAgent(requestUser.getUserAgent()); + tracerEntity.setUserId(requestUser.getUserId()); + tracerEntity.setUserType(requestUser.getUserType().getValue()); + tracerEntity.setUserName(requestUser.getUserName()); + return tracerEntity; + }).collect(Collectors.toList()); + dataTracerManger.saveBatch(tracerEntityList); + } + + + /** + * 分页查询 + * + * @param queryForm + * @return + */ + public ResponseDTO> query(DataTracerQueryForm queryForm) { + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List list = dataTracerDao.query(page, queryForm); + PageResult pageResult = SmartPageUtil.convert2PageResult(page, list); + return ResponseDTO.ok(pageResult); + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/dao/DictDao.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/dao/DictDao.java new file mode 100644 index 0000000..b53bdf0 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/dao/DictDao.java @@ -0,0 +1,36 @@ +package net.lab1024.sa.base.module.support.dict.dao; + +import java.util.List; + +import net.lab1024.sa.base.module.support.dict.domain.entity.DictEntity; +import net.lab1024.sa.base.module.support.dict.domain.form.DictQueryForm; +import net.lab1024.sa.base.module.support.dict.domain.vo.DictVO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +/** + * 数据字典 Dao + * + * @Author 1024创新实验室-主任-卓大 + * @Date 2025-03-25 22:25:04 + * @Copyright 1024创新实验室 + */ + +@Mapper +@Component +public interface DictDao extends BaseMapper { + + /** + * 分页 查询 + */ + List queryPage(Page page, @Param("queryForm") DictQueryForm queryForm); + + /** + * 根据 dictCode 去查询 + */ + DictEntity selectByCode(@Param("code") String code); + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/dao/DictDataDao.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/dao/DictDataDao.java new file mode 100644 index 0000000..3390be6 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/dao/DictDataDao.java @@ -0,0 +1,34 @@ +package net.lab1024.sa.base.module.support.dict.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import net.lab1024.sa.base.module.support.dict.domain.entity.DictDataEntity; +import net.lab1024.sa.base.module.support.dict.domain.vo.DictDataVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.Collection; +import java.util.List; + +/** + * 字典数据表 Dao + * + * @Author 1024创新实验室-主任-卓大 + * @Date 2025-03-25 23:12:59 + * @Copyright 1024创新实验室 + */ + +@Mapper +@Component +public interface DictDataDao extends BaseMapper { + + List queryByDictId(@Param("dictId") Long dictId); + + List selectByDictDataIds(@Param("dictDataIdList") Collection dictDataIds); + + DictDataEntity selectByDictIdAndValue(@Param("dictId") Long dictId, @Param("dataValue") String dataValue); + + List getAll(); + + List queryDictDataByCode(@Param("dictCode")String dictCode); +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/entity/DictDataEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/entity/DictDataEntity.java new file mode 100644 index 0000000..61b25b3 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/entity/DictDataEntity.java @@ -0,0 +1,68 @@ +package net.lab1024.sa.base.module.support.dict.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 字典数据表 实体类 + * + * @Author 1024创新实验室-主任-卓大 + * @Date 2025-03-25 23:12:59 + * @Copyright 1024创新实验室 + */ + +@Data +@TableName("t_dict_data") +public class DictDataEntity { + + /** + * 字典数据id + */ + @TableId(type = IdType.AUTO) + private Long dictDataId; + + /** + * 字典id + */ + private Long dictId; + + /** + * 字典项值 + */ + private String dataValue; + + /** + * 字典项显示名称 + */ + private String dataLabel; + + /** + * 备注 + */ + private String remark; + + /** + * 排序(越大越靠前) + */ + private Integer sortOrder; + + /** + * 禁用状态 + */ + private Boolean disabledFlag; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/entity/DictEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/entity/DictEntity.java new file mode 100644 index 0000000..0dc5d36 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/entity/DictEntity.java @@ -0,0 +1,58 @@ +package net.lab1024.sa.base.module.support.dict.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 数据字典 实体类 + * + * @Author 1024创新实验室-主任-卓大 + * @Date 2025-03-25 22:25:04 + * @Copyright 1024创新实验室 + */ + +@Data +@TableName("t_dict") +public class DictEntity { + + /** + * 字典id + */ + @TableId(type = IdType.AUTO) + private Long dictId; + + /** + * 字典名字 + */ + private String dictName; + + /** + * 字典编码 + */ + private String dictCode; + + /** + * 字典备注 + */ + private String remark; + + /** + * 禁用状态 + */ + private Boolean disabledFlag; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/form/DictAddForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/form/DictAddForm.java new file mode 100644 index 0000000..22e1137 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/form/DictAddForm.java @@ -0,0 +1,29 @@ +package net.lab1024.sa.base.module.support.dict.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +/** + * 数据字典 新建表单 + * + * @Author 1024创新实验室-主任-卓大 + * @Date 2025-03-25 22:25:04 + * @Copyright 1024创新实验室 + */ + +@Data +public class DictAddForm { + + @Schema(description = "字典名字", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "字典名字 不能为空") + private String dictName; + + @Schema(description = "字典编码", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "字典编码 不能为空") + private String dictCode; + + @Schema(description = "字典备注") + private String remark; + +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/form/DictDataAddForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/form/DictDataAddForm.java new file mode 100644 index 0000000..4b34466 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/form/DictDataAddForm.java @@ -0,0 +1,38 @@ +package net.lab1024.sa.base.module.support.dict.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 字典数据表 新建表单 + * + * @Author 1024创新实验室-主任-卓大 + * @Date 2025-03-25 23:12:59 + * @Copyright 1024创新实验室 + */ + +@Data +public class DictDataAddForm { + + @Schema(description = "字典id", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "字典id 不能为空") + private Long dictId; + + @Schema(description = "字典项值", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "字典项值 不能为空") + private String dataValue; + + @Schema(description = "字典项显示名称", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "字典项显示名称 不能为空") + private String dataLabel; + + @Schema(description = "备注") + private String remark; + + @Schema(description = "排序(越大越靠前)", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "排序(越大越靠前) 不能为空") + private Integer sortOrder; + +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/form/DictDataUpdateForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/form/DictDataUpdateForm.java new file mode 100644 index 0000000..513325b --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/form/DictDataUpdateForm.java @@ -0,0 +1,28 @@ +package net.lab1024.sa.base.module.support.dict.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + + +/** + * 字典数据表 更新表单 + * + * @Author 1024创新实验室-主任-卓大 + * @Date 2025-03-25 23:12:59 + * @Copyright 1024创新实验室 + */ + +@Data +public class DictDataUpdateForm extends DictDataAddForm { + + @Schema(description = "字典数据id", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "字典数据id 不能为空") + private Long dictDataId; + + @Schema(description = "字典数据编码", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "字典数据编码 不能为空") + private String dictCode; + + +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/form/DictQueryForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/form/DictQueryForm.java new file mode 100644 index 0000000..5fb8da1 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/form/DictQueryForm.java @@ -0,0 +1,26 @@ +package net.lab1024.sa.base.module.support.dict.domain.form; + +import net.lab1024.sa.base.common.domain.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 数据字典 分页查询表单 + * + * @Author 1024创新实验室-主任-卓大 + * @Date 2025-03-25 22:25:04 + * @Copyright 1024创新实验室 + */ + +@Data +@EqualsAndHashCode(callSuper = false) +public class DictQueryForm extends PageParam { + + @Schema(description = "关键字") + private String keywords; + + @Schema(description = "禁用状态") + private Boolean disabledFlag; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/form/DictUpdateForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/form/DictUpdateForm.java new file mode 100644 index 0000000..d4469b6 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/form/DictUpdateForm.java @@ -0,0 +1,34 @@ +package net.lab1024.sa.base.module.support.dict.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 数据字典 更新表单 + * + * @Author 1024创新实验室-主任-卓大 + * @Date 2025-03-25 22:25:04 + * @Copyright 1024创新实验室 + */ + +@Data +public class DictUpdateForm { + + @Schema(description = "字典id", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "字典id 不能为空") + private Long dictId; + + @Schema(description = "字典名字", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "字典名字 不能为空") + private String dictName; + + @Schema(description = "字典编码", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "字典编码 不能为空") + private String dictCode; + + @Schema(description = "字典备注") + private String remark; + +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/vo/DictDataVO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/vo/DictDataVO.java new file mode 100644 index 0000000..8dc19f3 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/vo/DictDataVO.java @@ -0,0 +1,49 @@ +package net.lab1024.sa.base.module.support.dict.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 字典数据表 列表VO + * + * @Author 1024创新实验室-主任-卓大 + * @Date 2025-03-25 23:12:59 + * @Copyright 1024创新实验室 + */ + +@Data +public class DictDataVO { + + @Schema(description = "字典数据id") + private Long dictDataId; + + @Schema(description = "字典id") + private Long dictId; + + @Schema(description = "字典编码") + private String dictCode; + + @Schema(description = "字典项值") + private String dataValue; + + @Schema(description = "字典项显示名称") + private String dataLabel; + + @Schema(description = "备注") + private String remark; + + @Schema(description = "排序(越大越靠前)") + private Integer sortOrder; + + @Schema(description = "禁用状态") + private Boolean disabledFlag; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "更新时间") + private LocalDateTime updateTime; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/vo/DictVO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/vo/DictVO.java new file mode 100644 index 0000000..1625ce4 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/vo/DictVO.java @@ -0,0 +1,41 @@ +package net.lab1024.sa.base.module.support.dict.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.time.LocalDateTime; + +import lombok.Data; + +/** + * 数据字典 列表VO + * + * @Author 1024创新实验室-主任-卓大 + * @Date 2025-03-25 22:25:04 + * @Copyright 1024创新实验室 + */ + +@Data +public class DictVO { + + @Schema(description = "字典id") + private Long dictId; + + @Schema(description = "字典名字") + private String dictName; + + @Schema(description = "字典编码") + private String dictCode; + + @Schema(description = "字典备注") + private String remark; + + @Schema(description = "禁用状态") + private Integer disabledFlag; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "更新时间") + private LocalDateTime updateTime; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/manager/DictManager.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/manager/DictManager.java new file mode 100644 index 0000000..4a88133 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/manager/DictManager.java @@ -0,0 +1,47 @@ +package net.lab1024.sa.base.module.support.dict.manager; + +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.constant.CacheKeyConst; +import net.lab1024.sa.base.module.support.dict.dao.DictDao; +import net.lab1024.sa.base.module.support.dict.dao.DictDataDao; +import net.lab1024.sa.base.module.support.dict.domain.entity.DictDataEntity; +import net.lab1024.sa.base.module.support.dict.domain.entity.DictEntity; +import net.lab1024.sa.base.module.support.dict.domain.vo.DictDataVO; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + + +/** + * 数据字典 缓存 + * + * @Author 1024创新实验室-主任-卓大 + * @Date 2025-03-25 22:25:04 + * @Copyright 1024创新实验室 + */ + +@Service +public class DictManager { + + @Resource + private DictDao dictDao; + + @Resource + private DictDataDao dictDataDao; + + + /** + * 获取字典 + */ + @Cacheable(value = CacheKeyConst.Dict.DICT_DATA, key = "#dictCode + '_' + #dataValue") + public DictDataVO getDictData(String dictCode, String dataValue) { + DictEntity dictEntity = dictDao.selectByCode(dictCode); + if (dictEntity == null) { + return null; + } + + DictDataEntity dictDataEntity = dictDataDao.selectByDictIdAndValue(dictEntity.getDictId(), dataValue); + return SmartBeanUtil.copy(dictDataEntity, DictDataVO.class); + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/service/DictService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/service/DictService.java new file mode 100644 index 0000000..fd6683f --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/service/DictService.java @@ -0,0 +1,278 @@ +package net.lab1024.sa.base.module.support.dict.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.constant.CacheKeyConst; +import net.lab1024.sa.base.module.support.dict.dao.DictDao; +import net.lab1024.sa.base.module.support.dict.dao.DictDataDao; +import net.lab1024.sa.base.module.support.dict.domain.entity.DictDataEntity; +import net.lab1024.sa.base.module.support.dict.domain.entity.DictEntity; +import net.lab1024.sa.base.module.support.dict.domain.form.*; +import net.lab1024.sa.base.module.support.dict.domain.vo.DictDataVO; +import net.lab1024.sa.base.module.support.dict.domain.vo.DictVO; +import net.lab1024.sa.base.module.support.dict.manager.DictManager; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 数据字典 Service + * + * @Author 1024创新实验室-主任-卓大 + * @Date 2025-03-25 22:25:04 + * @Copyright 1024创新实验室 + */ + +@Service +public class DictService { + + @Resource + private DictDao dictDao; + + @Resource + private DictDataDao dictDataDao; + + @Resource + private CacheManager cacheManager; + + @Resource + private DictManager dictManager; + + /** + * 获取全部数据 + */ + public List getAll() { + return dictDataDao.getAll(); + } + + /** + * 获取所有字典 + */ + public List getAllDict() { + List dictEntityList = dictDao.selectList(null).stream().filter(e -> !e.getDisabledFlag()).collect(Collectors.toList()); + return SmartBeanUtil.copyList(dictEntityList, DictVO.class); + } + + /** + * 分页查询 + */ + public PageResult queryPage(DictQueryForm queryForm) { + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List list = dictDao.queryPage(page, queryForm); + return SmartPageUtil.convert2PageResult(page, list); + } + + /** + * 添加 + */ + public synchronized ResponseDTO add(DictAddForm addForm) { + DictEntity existDictCode = dictDao.selectByCode(addForm.getDictCode()); + if (null != existDictCode) { + return ResponseDTO.userErrorParam("数据字典编码已经存在!"); + } + + DictEntity dictEntity = SmartBeanUtil.copy(addForm, DictEntity.class); + dictDao.insert(dictEntity); + return ResponseDTO.ok(); + } + + /** + * 禁用 启用 + */ + public ResponseDTO updateDisabled(Long dictId) { + DictEntity dictEntity = dictDao.selectById(dictId); + if (dictEntity == null) { + return ResponseDTO.userErrorParam("数据不存在"); + } + + dictEntity.setDisabledFlag(!dictEntity.getDisabledFlag()); + dictDao.updateById(dictEntity); + return ResponseDTO.ok(); + } + + /** + * 更新 + */ + @CacheEvict(CacheKeyConst.Dict.DICT_DATA) + public synchronized ResponseDTO update(DictUpdateForm updateForm) { + DictEntity existDictCode = dictDao.selectByCode(updateForm.getDictCode()); + if (null != existDictCode && !existDictCode.getDictId().equals(updateForm.getDictId())) { + return ResponseDTO.userErrorParam("数据字典编码已经存在!"); + } + + DictEntity dictEntity = SmartBeanUtil.copy(updateForm, DictEntity.class); + dictDao.updateById(dictEntity); + return ResponseDTO.ok(); + } + + /** + * 批量删除 + */ + @CacheEvict(CacheKeyConst.Dict.DICT_DATA) + public synchronized ResponseDTO batchDelete(List idList) { + if (CollectionUtils.isEmpty(idList)) { + return ResponseDTO.ok(); + } + + dictDao.deleteBatchIds(idList); + return ResponseDTO.ok(); + } + + /** + * 单个删除 + */ + @CacheEvict(CacheKeyConst.Dict.DICT_DATA) + public synchronized ResponseDTO delete(Long dictId) { + if (null == dictId) { + return ResponseDTO.ok(); + } + + dictDao.deleteById(dictId); + return ResponseDTO.ok(); + } + + + // -------------- 字典数据 -------------------- + + /** + * 分页查询 + */ + public List queryDictData(Long dictId) { + return dictDataDao.queryByDictId(dictId); + } + + /** + * 获取字典 + */ + + public DictDataVO getDictData(String dictCode, String dataValue) { + return dictManager.getDictData(dictCode, dataValue); + } + + /** + * 获取字典Label + */ + public String getDictDataLabel(String dictCode, String dataValue) { + DictDataVO dictData = getDictData(dictCode, dataValue); + return dictData == null ? "" : dictData.getDataLabel(); + } + + /** + * 添加 + */ + public synchronized ResponseDTO addDictData(DictDataAddForm addForm) { + + addForm.setDataValue(SmartStringUtil.trim(addForm.getDataValue())); + + DictEntity dictEntity = dictDao.selectById(addForm.getDictId()); + if (null == dictEntity) { + return ResponseDTO.userErrorParam("数据字典不存在"); + } + + DictDataEntity existData = dictDataDao.selectByDictIdAndValue(addForm.getDictId(), addForm.getDataValue()); + if (null != existData) { + return ResponseDTO.userErrorParam("已存在相同value的数据"); + } + + DictDataEntity dictDataEntity = SmartBeanUtil.copy(addForm, DictDataEntity.class); + dictDataDao.insert(dictDataEntity); + return ResponseDTO.ok(); + } + + /** + * 更新 + */ + @CacheEvict(value = CacheKeyConst.Dict.DICT_DATA, key = "#updateForm.dictCode + '_' + #updateForm.dataValue") + public synchronized ResponseDTO updateDictData(DictDataUpdateForm updateForm) { + + updateForm.setDataValue(SmartStringUtil.trim(updateForm.getDataValue())); + + DictEntity dictEntity = dictDao.selectById(updateForm.getDictId()); + if (null == dictEntity) { + return ResponseDTO.userErrorParam("数据字典不存在"); + } + + DictDataEntity existData = dictDataDao.selectByDictIdAndValue(updateForm.getDictId(), updateForm.getDataValue()); + if (null != existData && !existData.getDictDataId().equals(updateForm.getDictDataId())) { + return ResponseDTO.userErrorParam("已存在相同value的数据"); + } + + DictDataEntity dictDataEntity = SmartBeanUtil.copy(updateForm, DictDataEntity.class); + dictDataDao.updateById(dictDataEntity); + return ResponseDTO.ok(); + } + + /** + * 批量删除 + */ + public synchronized ResponseDTO batchDeleteDictData(List idList) { + if (CollectionUtils.isEmpty(idList)) { + return ResponseDTO.ok(); + } + // 清除缓存 + clearDictDataCache(idList); + // 删除 + dictDataDao.deleteBatchIds(idList); + return ResponseDTO.ok(); + } + + /** + * 单个删除 + */ + public synchronized ResponseDTO deleteDictData(Long dictDataId) { + if (null == dictDataId) { + return ResponseDTO.ok(); + } + // 清除缓存 + clearDictDataCache(Collections.singletonList(dictDataId)); + // 删除 + dictDataDao.deleteById(dictDataId); + return ResponseDTO.ok(); + } + + + /** + * 清空字典数据缓存 + */ + private void clearDictDataCache(List idList) { + List dictDataList = dictDataDao.selectByDictDataIds(idList); + Cache cache = cacheManager.getCache(CacheKeyConst.Dict.DICT_DATA); + if (cache == null) { + return; + } + + for (DictDataVO dictDataVO : dictDataList) { + cache.evict(dictDataVO.getDictCode() + "_" + dictDataVO.getDataValue()); + } + } + + + /** + * 更新启用/禁用 + */ + public synchronized ResponseDTO updateDictDataDisabled(Long dictDataId) { + DictDataEntity dictDataEntity = dictDataDao.selectById(dictDataId); + if (dictDataEntity == null) { + return ResponseDTO.userErrorParam("数据不存在"); + } + + dictDataEntity.setDisabledFlag(!dictDataEntity.getDisabledFlag()); + dictDataDao.updateById(dictDataEntity); + return ResponseDTO.ok(); + } + + public List queryDictDataByCode(String dictCode) { + return dictDataDao.queryDictDataByCode(dictCode); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/controller/FeedbackController.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/controller/FeedbackController.java new file mode 100644 index 0000000..1479db1 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/controller/FeedbackController.java @@ -0,0 +1,51 @@ +package net.lab1024.sa.base.module.support.feedback.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.controller.SupportBaseController; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.RequestUser; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartRequestUtil; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import net.lab1024.sa.base.module.support.feedback.domain.FeedbackAddForm; +import net.lab1024.sa.base.module.support.feedback.domain.FeedbackQueryForm; +import net.lab1024.sa.base.module.support.feedback.domain.FeedbackVO; +import net.lab1024.sa.base.module.support.feedback.service.FeedbackService; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * 意见反馈 + * + * @Author 1024创新实验室: 开云 + * @Date 2022-08-11 20:48:09 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@Tag(name = SwaggerTagConst.Support.FEEDBACK) +@RestController +public class FeedbackController extends SupportBaseController { + + @Resource + private FeedbackService feedbackService; + + @Operation(summary = "意见反馈-分页查询 @author 开云") + @PostMapping("/feedback/query") + public ResponseDTO> query(@RequestBody @Valid FeedbackQueryForm queryForm) { + return feedbackService.query(queryForm); + } + + @Operation(summary = "意见反馈-新增 @author 开云") + @PostMapping("/feedback/add") + public ResponseDTO add(@RequestBody @Valid FeedbackAddForm addForm) { + RequestUser employee = SmartRequestUtil.getRequestUser(); + return feedbackService.add(addForm, employee); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/dao/FeedbackDao.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/dao/FeedbackDao.java new file mode 100644 index 0000000..034a8af --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/dao/FeedbackDao.java @@ -0,0 +1,30 @@ +package net.lab1024.sa.base.module.support.feedback.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.base.module.support.feedback.domain.FeedbackEntity; +import net.lab1024.sa.base.module.support.feedback.domain.FeedbackQueryForm; +import net.lab1024.sa.base.module.support.feedback.domain.FeedbackVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 意见反馈 dao + * + * @Author 1024创新实验室: 开云 + * @Date 2022-08-11 20:48:09 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface FeedbackDao extends BaseMapper { + + /** + * 分页查询 + */ + List queryPage(Page page, @Param("query") FeedbackQueryForm query); +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/domain/FeedbackAddForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/domain/FeedbackAddForm.java new file mode 100644 index 0000000..f3e57bd --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/domain/FeedbackAddForm.java @@ -0,0 +1,32 @@ +package net.lab1024.sa.base.module.support.feedback.domain; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import net.lab1024.sa.base.common.json.deserializer.FileKeyVoDeserializer; +import net.lab1024.sa.base.common.json.serializer.FileKeyVoSerializer; + +/** + * 意见反馈 添加表单 + * + * @Author 1024创新实验室: 开云 + * @Date 2022-08-11 20:48:09 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class FeedbackAddForm { + + @Schema(description = "反馈内容") + @NotBlank(message = "反馈内容不能为空") + private String feedbackContent; + + @Schema(description = "反馈图片") + @JsonSerialize(using = FileKeyVoSerializer.class) + @JsonDeserialize(using = FileKeyVoDeserializer.class) + private String feedbackAttachment; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/domain/FeedbackEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/domain/FeedbackEntity.java new file mode 100644 index 0000000..85d7b4c --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/domain/FeedbackEntity.java @@ -0,0 +1,62 @@ +package net.lab1024.sa.base.module.support.feedback.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 意见反馈 表 + * + * @Author 1024创新实验室: 开云 + * @Date 2022-08-11 20:48:09 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName("t_feedback") +public class FeedbackEntity { + /** + * 主键 + */ + @TableId(type = IdType.AUTO) + private Long feedbackId; + + /** + * 反馈内容 + */ + private String feedbackContent; + + /** + * 反馈附件 + */ + private String feedbackAttachment; + + /** + * 创建人id + */ + private Long userId; + + /** + * 用户类型 + */ + private Integer userType; + + /** + * 创建人姓名 + */ + private String userName; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + + /** + * 创建时间 + */ + private LocalDateTime createTime; +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/domain/FeedbackQueryForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/domain/FeedbackQueryForm.java new file mode 100644 index 0000000..2bda147 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/domain/FeedbackQueryForm.java @@ -0,0 +1,31 @@ +package net.lab1024.sa.base.module.support.feedback.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; +import org.hibernate.validator.constraints.Length; + +import java.time.LocalDate; + +/** + * 意见反馈 查询 + * + * @Author 1024创新实验室: 开云 + * @Date 2022-08-11 20:48:09 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class FeedbackQueryForm extends PageParam { + + @Schema(description = "搜索词") + @Length(max = 25, message = "搜索词最多25字符") + private String searchWord; + + @Schema(description = "开始时间", example = "2021-02-14") + private LocalDate startDate; + + @Schema(description = "截止时间", example = "2022-10-15") + private LocalDate endDate; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/domain/FeedbackVO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/domain/FeedbackVO.java new file mode 100644 index 0000000..5cba8ec --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/domain/FeedbackVO.java @@ -0,0 +1,51 @@ +package net.lab1024.sa.base.module.support.feedback.domain; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.enumeration.UserTypeEnum; +import net.lab1024.sa.base.common.json.deserializer.FileKeyVoDeserializer; +import net.lab1024.sa.base.common.json.serializer.FileKeyVoSerializer; +import net.lab1024.sa.base.common.swagger.SchemaEnum; + +import java.time.LocalDateTime; + +/** + * 意见反馈 返回对象 + * + * @Author 1024创新实验室: 开云 + * @Date 2022-08-11 20:48:09 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class FeedbackVO { + + @Schema(description = "主键") + private Long feedbackId; + + @Schema(description = "反馈内容") + private String feedbackContent; + + @Schema(description = "反馈图片") + @JsonSerialize(using = FileKeyVoSerializer.class) + @JsonDeserialize(using = FileKeyVoDeserializer.class) + private String feedbackAttachment; + + @Schema(description = "创建人id") + private Long userId; + + @Schema(description = "创建人姓名") + private String userName; + + @SchemaEnum(value = UserTypeEnum.class, desc = "创建人类型") + private Integer userType; + + @Schema(description = "更新时间") + private LocalDateTime updateTime; + + @Schema(description = "创建时间") + private LocalDateTime createTime; +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/service/FeedbackService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/service/FeedbackService.java new file mode 100644 index 0000000..fd66e3e --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/service/FeedbackService.java @@ -0,0 +1,60 @@ +package net.lab1024.sa.base.module.support.feedback.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.RequestUser; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.module.support.feedback.dao.FeedbackDao; +import net.lab1024.sa.base.module.support.feedback.domain.FeedbackAddForm; +import net.lab1024.sa.base.module.support.feedback.domain.FeedbackEntity; +import net.lab1024.sa.base.module.support.feedback.domain.FeedbackQueryForm; +import net.lab1024.sa.base.module.support.feedback.domain.FeedbackVO; +import org.springframework.stereotype.Service; + +import java.util.List; + + +/** + * 意见反馈 + * + * @Author 1024创新实验室: 开云 + * @Date 2022-08-11 20:48:09 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class FeedbackService { + + @Resource + private FeedbackDao feedbackDao; + + /** + * 分页查询 + * + */ + public ResponseDTO> query(FeedbackQueryForm queryForm) { + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List list = feedbackDao.queryPage(page, queryForm); + PageResult pageResultDTO = SmartPageUtil.convert2PageResult(page, list); + if (pageResultDTO.getEmptyFlag()) { + return ResponseDTO.ok(pageResultDTO); + } + return ResponseDTO.ok(pageResultDTO); + } + + /** + * 新建 + */ + public ResponseDTO add(FeedbackAddForm addForm, RequestUser requestUser) { + FeedbackEntity feedback = SmartBeanUtil.copy(addForm, FeedbackEntity.class); + feedback.setUserType(requestUser.getUserType().getValue()); + feedback.setUserId(requestUser.getUserId()); + feedback.setUserName(requestUser.getUserName()); + feedbackDao.insert(feedback); + return ResponseDTO.ok(); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/constant/FileFolderTypeEnum.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/constant/FileFolderTypeEnum.java new file mode 100644 index 0000000..d69c6c4 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/constant/FileFolderTypeEnum.java @@ -0,0 +1,63 @@ +package net.lab1024.sa.base.module.support.file.constant; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 文件服务 文件夹位置类型枚举类 + * + * @Author 1024创新实验室: 胡克 + * @Date 2019年10月11日 15:34:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@AllArgsConstructor +@Getter +public enum FileFolderTypeEnum implements BaseEnum { + + /** + * 通用 + */ + COMMON(1, FileFolderTypeEnum.FOLDER_PUBLIC + "/common/", "通用"), + + /** + * 公告 + */ + NOTICE(2, FileFolderTypeEnum.FOLDER_PUBLIC + "/notice/", "公告"), + + /** + * 帮助中心 + */ + HELP_DOC(3, FileFolderTypeEnum.FOLDER_PUBLIC + "/help-doc/", "帮助中心"), + + /** + * 意见反馈 + */ + FEEDBACK(4, FileFolderTypeEnum.FOLDER_PUBLIC + "/feedback/", "意见反馈"), + + /** + * 工作手册 + */ + WORK_BOOK(5, FileFolderTypeEnum.FOLDER_PUBLIC + "/work-book/", "工作手册"), + + ; + + /** + * 公用读取文件夹 public + */ + public static final String FOLDER_PUBLIC = "public"; + + /** + * 私有读取文件夹 private, 私有文件夹会设置 只读权限,并且 文件url 拥有过期时间 + */ + public static final String FOLDER_PRIVATE = "private"; + + private final Integer value; + + private final String folder; + + private final String desc; +} + diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/controller/FileController.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/controller/FileController.java new file mode 100644 index 0000000..75d3913 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/controller/FileController.java @@ -0,0 +1,73 @@ +package net.lab1024.sa.base.module.support.file.controller; + +import cn.hutool.extra.servlet.JakartaServletUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import net.lab1024.sa.base.common.constant.RequestHeaderConst; +import net.lab1024.sa.base.common.controller.SupportBaseController; +import net.lab1024.sa.base.common.domain.RequestUser; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartRequestUtil; +import net.lab1024.sa.base.common.util.SmartResponseUtil; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import net.lab1024.sa.base.module.support.file.domain.vo.FileDownloadVO; +import net.lab1024.sa.base.module.support.file.domain.vo.FileUploadVO; +import net.lab1024.sa.base.module.support.file.service.FileService; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; + +/** + * 文件服务 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2019年10月11日 15:34:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@RestController +@Tag(name = SwaggerTagConst.Support.FILE) +public class FileController extends SupportBaseController { + + @Resource + private FileService fileService; + + + @Operation(summary = "文件上传 @author 胡克") + @PostMapping("/file/upload") + public ResponseDTO upload(@RequestParam MultipartFile file, @RequestParam Integer folder) { + RequestUser requestUser = SmartRequestUtil.getRequestUser(); + return fileService.fileUpload(file, folder, requestUser); + } + + @Operation(summary = "获取文件URL:根据fileKey @author 胡克") + @GetMapping("/file/getFileUrl") + public ResponseDTO getUrl(@RequestParam String fileKey) { + return fileService.getFileUrl(fileKey); + } + + @Operation(summary = "下载文件流(根据fileKey) @author 胡克") + @GetMapping("/file/downLoad") + public void downLoad(@RequestParam String fileKey, HttpServletRequest request, HttpServletResponse response) throws IOException { + String userAgent = JakartaServletUtil.getHeaderIgnoreCase(request, RequestHeaderConst.USER_AGENT); + ResponseDTO downloadFileResult = fileService.getDownloadFile(fileKey, userAgent); + if (!downloadFileResult.getOk()) { + SmartResponseUtil.write(response, downloadFileResult); + return; + } + // 下载文件信息 + FileDownloadVO fileDownloadVO = downloadFileResult.getData(); + // 设置下载消息头 + SmartResponseUtil.setDownloadFileHeader(response, fileDownloadVO.getMetadata().getFileName(), fileDownloadVO.getMetadata().getFileSize()); + // 下载 + response.getOutputStream().write(fileDownloadVO.getData()); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/dao/FileDao.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/dao/FileDao.java new file mode 100644 index 0000000..15f7aab --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/dao/FileDao.java @@ -0,0 +1,50 @@ +package net.lab1024.sa.base.module.support.file.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.base.module.support.file.domain.vo.FileVO; +import net.lab1024.sa.base.module.support.file.domain.entity.FileEntity; +import net.lab1024.sa.base.module.support.file.domain.form.FileQueryForm; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.Collection; +import java.util.List; + +/** + * 文件服务 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2019年10月11日 15:34:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface FileDao extends BaseMapper { + + /** + * 文件key单个查询 + * + * @param fileKey + * @return + */ + FileVO getByFileKey(@Param("fileKey") String fileKey); + + + /** + * 批量获取 + */ + List selectByFileKeyList(@Param("fileKeyList") Collection fileKeyList); + + /** + * 分页 查询 + * + * @param page + * @param queryForm + * @return + */ + List queryPage(Page page, @Param("queryForm") FileQueryForm queryForm); + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/entity/FileEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/entity/FileEntity.java new file mode 100644 index 0000000..2fdb341 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/entity/FileEntity.java @@ -0,0 +1,73 @@ +package net.lab1024.sa.base.module.support.file.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 文件服务 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2019年10月11日 15:34:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName(value = "t_file") +public class FileEntity { + + /** + * 主键id + */ + @TableId(type = IdType.AUTO) + private Long fileId; + + /** + * 文件夹类型 + */ + private Integer folderType; + + /** + * 文件名称 + */ + private String fileName; + + /** + * 文件大小 + */ + private Long fileSize; + + /** + * 文件key,用于文件下载 + */ + private String fileKey; + + /** + * 文件类型 + */ + private String fileType; + + /** + * 创建人,即上传人 + */ + private Long creatorId; + + /** + * 用户类型 + */ + private Integer creatorUserType; + + /** + * 创建人 姓名 + */ + private String creatorName; + + private LocalDateTime updateTime; + + private LocalDateTime createTime; +} + diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/form/FileQueryForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/form/FileQueryForm.java new file mode 100644 index 0000000..84d7a00 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/form/FileQueryForm.java @@ -0,0 +1,46 @@ +package net.lab1024.sa.base.module.support.file.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; +import net.lab1024.sa.base.module.support.file.constant.FileFolderTypeEnum; + +import java.time.LocalDate; + +/** + * 文件信息查询 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2019年10月11日 15:34:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class FileQueryForm extends PageParam { + + @SchemaEnum(value = FileFolderTypeEnum.class, desc = "文件夹类型") + @CheckEnum(value = FileFolderTypeEnum.class, message = "文件夹类型 错误") + private Integer folderType; + + @Schema(description = "文件名词") + private String fileName; + + @Schema(description = "文件Key") + private String fileKey; + + @Schema(description = "文件类型") + private String fileType; + + @Schema(description = "创建人") + private String creatorName; + + @Schema(description = "创建时间") + private LocalDate createTimeBegin; + + @Schema(description = "创建时间") + private LocalDate createTimeEnd; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/form/FileUrlUploadForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/form/FileUrlUploadForm.java new file mode 100644 index 0000000..0a62b71 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/form/FileUrlUploadForm.java @@ -0,0 +1,30 @@ +package net.lab1024.sa.base.module.support.file.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; +import net.lab1024.sa.base.module.support.file.constant.FileFolderTypeEnum; + +/** + * url上传文件 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2019年10月11日 15:34:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class FileUrlUploadForm { + + @SchemaEnum(value = FileFolderTypeEnum.class, desc = "业务类型") + @CheckEnum(value = FileFolderTypeEnum.class, required = true, message = "业务类型错误") + private Integer folder; + + @Schema(description = "文件url") + @NotBlank(message = "文件url不能为空") + private String fileUrl; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/vo/FileDownloadVO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/vo/FileDownloadVO.java new file mode 100644 index 0000000..fd2756f --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/vo/FileDownloadVO.java @@ -0,0 +1,28 @@ +package net.lab1024.sa.base.module.support.file.domain.vo; + +import lombok.Data; + +/** + * 文件下载 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2019年10月11日 15:34:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class FileDownloadVO { + + /** + * 文件字节数据 + */ + private byte[] data; + + /** + * 文件元数据 + */ + private FileMetadataVO metadata; + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/vo/FileMetadataVO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/vo/FileMetadataVO.java new file mode 100644 index 0000000..51b497f --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/vo/FileMetadataVO.java @@ -0,0 +1,31 @@ +package net.lab1024.sa.base.module.support.file.domain.vo; + +import lombok.Data; + +/** + * 文件元数据 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2019年10月11日 15:34:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class FileMetadataVO { + + /** + * 文件名称 + */ + private String fileName; + + /** + * 文件大小/字节 + */ + private Long fileSize; + + /** + * 文件格式 + */ + private String fileFormat; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/vo/FileUploadVO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/vo/FileUploadVO.java new file mode 100644 index 0000000..11772f5 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/vo/FileUploadVO.java @@ -0,0 +1,35 @@ +package net.lab1024.sa.base.module.support.file.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 文件信息 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2019年10月11日 15:34:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class FileUploadVO { + + @Schema(description = "文件id") + private Long fileId; + + @Schema(description = "文件名称") + private String fileName; + + @Schema(description = "fileUrl") + private String fileUrl; + + @Schema(description = "fileKey") + private String fileKey; + + @Schema(description = "文件大小") + private Long fileSize; + + @Schema(description = "文件类型") + private String fileType; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/vo/FileVO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/vo/FileVO.java new file mode 100644 index 0000000..e4fd696 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/vo/FileVO.java @@ -0,0 +1,56 @@ +package net.lab1024.sa.base.module.support.file.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.enumeration.UserTypeEnum; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.module.support.file.constant.FileFolderTypeEnum; + +import java.time.LocalDateTime; + +/** + * 文件信息 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2019年10月11日 15:34:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class FileVO { + + @Schema(description = "主键") + private Long fileId; + + @Schema(description = "存储文件夹类型") + @SchemaEnum(FileFolderTypeEnum.class) + private Integer folderType; + + @Schema(description = "文件名称") + private String fileName; + + @Schema(description = "文件大小") + private Integer fileSize; + + @Schema(description = "文件类型") + private String fileType; + + @Schema(description = "文件路径") + private String fileKey; + + @Schema(description = "上传人") + private Long creatorId; + + @Schema(description = "上传人") + private String creatorName; + + @SchemaEnum(value = UserTypeEnum.class, desc = "创建人类型") + private Integer creatorUserType; + + @Schema(description = "文件展示url") + private String fileUrl; + + @Schema(description = "创建时间") + private LocalDateTime createTime; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/service/FileService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/service/FileService.java new file mode 100644 index 0000000..f31a797 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/service/FileService.java @@ -0,0 +1,209 @@ +package net.lab1024.sa.base.module.support.file.service; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.google.common.collect.Lists; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.code.UserErrorCode; +import net.lab1024.sa.base.common.constant.StringConst; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.RequestUser; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartEnumUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.module.support.file.constant.FileFolderTypeEnum; +import net.lab1024.sa.base.module.support.file.dao.FileDao; +import net.lab1024.sa.base.module.support.file.domain.entity.FileEntity; +import net.lab1024.sa.base.module.support.file.domain.form.FileQueryForm; +import net.lab1024.sa.base.module.support.file.domain.vo.FileDownloadVO; +import net.lab1024.sa.base.module.support.file.domain.vo.FileUploadVO; +import net.lab1024.sa.base.module.support.file.domain.vo.FileVO; +import net.lab1024.sa.base.module.support.redis.RedisService; +import net.lab1024.sa.base.module.support.securityprotect.service.SecurityFileService; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 文件服务 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2019年10月11日 15:34:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class FileService { + + /** + * 文件名最大长度 + */ + private static final int FILE_NAME_MAX_LENGTH = 100; + + @Resource + private IFileStorageService fileStorageService; + + @Resource + private FileDao fileDao; + + @Resource + private RedisService redisService; + + @Resource + private SecurityFileService securityFileService; + + /** + * 文件上传服务 + * + * @param file + * @param folderType 文件夹类型 + * @return + */ + public ResponseDTO fileUpload(MultipartFile file, Integer folderType, RequestUser requestUser) { + FileFolderTypeEnum folderTypeEnum = SmartEnumUtil.getEnumByValue(folderType, FileFolderTypeEnum.class); + if (null == folderTypeEnum) { + return ResponseDTO.userErrorParam("文件夹错误"); + } + + if (null == file || file.getSize() == 0) { + return ResponseDTO.userErrorParam("上传文件不能为空"); + } + + // 校验文件名称 + String originalFilename = file.getOriginalFilename(); + if (StringUtils.isBlank(originalFilename)) { + return ResponseDTO.userErrorParam("上传文件名称不能为空"); + } + + if (originalFilename.length() > FILE_NAME_MAX_LENGTH) { + return ResponseDTO.userErrorParam("文件名称最大长度为:" + FILE_NAME_MAX_LENGTH); + } + + // 校验文件大小以及安全性 + ResponseDTO validateFile = securityFileService.checkFile(file); + if (!validateFile.getOk()) { + return ResponseDTO.error(validateFile); + } + + // 进行上传 + ResponseDTO response = fileStorageService.upload(file, folderTypeEnum.getFolder()); + if (!response.getOk()) { + return response; + } + + // 上传成功 保存记录数据库 + FileUploadVO uploadVO = response.getData(); + FileEntity fileEntity = new FileEntity(); + fileEntity.setFolderType(folderTypeEnum.getValue()); + fileEntity.setFileName(originalFilename); + fileEntity.setFileSize(file.getSize()); + fileEntity.setFileKey(uploadVO.getFileKey()); + fileEntity.setFileType(uploadVO.getFileType()); + fileEntity.setCreatorId(requestUser == null ? null : requestUser.getUserId()); + fileEntity.setCreatorName(requestUser == null ? null : requestUser.getUserName()); + fileEntity.setCreatorUserType(requestUser == null ? null : requestUser.getUserType().getValue()); + fileDao.insert(fileEntity); + + // 将fileId 返回给前端 + uploadVO.setFileId(fileEntity.getFileId()); + + return response; + } + + /** + * 批量获取文件信息 + * + * @param fileKeyList + * @return + */ + public List getFileList(List fileKeyList) { + if (CollectionUtils.isEmpty(fileKeyList)) { + return Lists.newArrayList(); + } + + // 查询数据库,并获取 file url + HashSet fileKeySet = new HashSet<>(fileKeyList); + Map fileMap = fileDao.selectByFileKeyList(fileKeySet) + .stream().collect(Collectors.toMap(FileVO::getFileKey, Function.identity())); + + for (FileVO fileVO : fileMap.values()) { + ResponseDTO fileUrlResponse = fileStorageService.getFileUrl(fileVO.getFileKey()); + if (fileUrlResponse.getOk()) { + fileVO.setFileUrl(fileUrlResponse.getData()); + } + } + + // 返回结果 + List result = Lists.newArrayListWithCapacity(fileKeyList.size()); + for (String fileKey : fileKeyList) { + FileVO fileVO = fileMap.get(fileKey); + if (fileVO != null) { + result.add(fileVO); + } + } + + return result; + } + + + /** + * 根据文件绝对路径 获取文件URL + * 支持单个 key 逗号分隔的形式 + * + * @param fileKeys + * @return + */ + public ResponseDTO getFileUrl(String fileKeys) { + if (StringUtils.isBlank(fileKeys)) { + return ResponseDTO.error(UserErrorCode.PARAM_ERROR); + } + + List fileKeyArray = StrUtil.split(fileKeys, StringConst.SEPARATOR); + List fileUrlList = Lists.newArrayListWithCapacity(fileKeyArray.size()); + for (String fileKey : fileKeyArray) { + ResponseDTO fileUrlResponse = fileStorageService.getFileUrl(fileKey); + if (fileUrlResponse.getOk()) { + fileUrlList.add(fileUrlResponse.getData()); + } + } + return ResponseDTO.ok(SmartStringUtil.join(StringConst.SEPARATOR, fileUrlList)); + } + + + /** + * 根据文件服务类型 和 FileKey 下载文件 + */ + public ResponseDTO getDownloadFile(String fileKey, String userAgent) { + FileVO fileVO = fileDao.getByFileKey(fileKey); + if (fileVO == null) { + return ResponseDTO.userErrorParam("文件不存在"); + } + + // 根据文件服务类 获取对应文件服务 查询 url + ResponseDTO download = fileStorageService.download(fileKey); + if (download.getOk()) { + download.getData().getMetadata().setFileName(fileVO.getFileName()); + } + return download; + } + + /** + * 分页查询 + */ + public PageResult queryPage(FileQueryForm queryForm) { + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List list = fileDao.queryPage(page, queryForm); + return SmartPageUtil.convert2PageResult(page, list); + } + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/service/FileStorageCloudServiceImpl.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/service/FileStorageCloudServiceImpl.java new file mode 100644 index 0000000..311f3d7 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/service/FileStorageCloudServiceImpl.java @@ -0,0 +1,246 @@ +package net.lab1024.sa.base.module.support.file.service; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.LocalDateTimeUtil; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.CannedAccessControlList; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.S3Object; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.code.SystemErrorCode; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.config.FileConfig; +import net.lab1024.sa.base.constant.RedisKeyConst; +import net.lab1024.sa.base.module.support.file.constant.FileFolderTypeEnum; +import net.lab1024.sa.base.module.support.file.dao.FileDao; +import net.lab1024.sa.base.module.support.file.domain.vo.FileDownloadVO; +import net.lab1024.sa.base.module.support.file.domain.vo.FileMetadataVO; +import net.lab1024.sa.base.module.support.file.domain.vo.FileUploadVO; +import net.lab1024.sa.base.module.support.file.domain.vo.FileVO; +import net.lab1024.sa.base.module.support.redis.RedisService; +import org.apache.commons.collections4.MapUtils; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.util.FileCopyUtils; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * 云计算 实现 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2019年10月11日 15:34:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +public class FileStorageCloudServiceImpl implements IFileStorageService { + + /** + * 自定义元数据 文件名称 + */ + private static final String USER_METADATA_FILE_NAME = "file-name"; + + /** + * 自定义元数据 文件格式 + */ + private static final String USER_METADATA_FILE_FORMAT = "file-format"; + + /** + * 自定义元数据 文件大小 + */ + private static final String USER_METADATA_FILE_SIZE = "file-size"; + + @Resource + private AmazonS3 amazonS3; + + @Resource + private FileConfig cloudConfig; + + @Resource + private RedisService redisService; + + @Resource + private FileDao fileDao; + + @Override + public ResponseDTO upload(MultipartFile file, String path) { + // 设置文件 key + String originalFileName = file.getOriginalFilename(); + if (SmartStringUtil.isEmpty(originalFileName)) { + return ResponseDTO.userErrorParam("上传文件名为空"); + } + + String fileType = FilenameUtils.getExtension(originalFileName); + String uuid = UUID.randomUUID().toString().replaceAll("-", ""); + String time = LocalDateTimeUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_FORMATTER); + String fileKey = path + uuid + "_" + time+ "." + fileType; + + // 文件名称 URL 编码 + String urlEncoderFilename; + try { + urlEncoderFilename = URLEncoder.encode(originalFileName, StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException e) { + log.error("文件上传服务URL ENCODE-发生异常:", e); + return ResponseDTO.error(SystemErrorCode.SYSTEM_ERROR, "上传失败"); + } + ObjectMetadata meta = new ObjectMetadata(); + meta.setContentEncoding(StandardCharsets.UTF_8.name()); + meta.setContentDisposition("attachment;filename=" + urlEncoderFilename); + Map userMetadata = new HashMap<>(10); + userMetadata.put(USER_METADATA_FILE_NAME, urlEncoderFilename); + userMetadata.put(USER_METADATA_FILE_FORMAT, fileType); + userMetadata.put(USER_METADATA_FILE_SIZE, String.valueOf(file.getSize())); + meta.setUserMetadata(userMetadata); + meta.setContentLength(file.getSize()); + meta.setContentType(this.getContentType(fileType)); + try { + amazonS3.putObject(cloudConfig.getBucketName(), fileKey, file.getInputStream(), meta); + } catch (IOException e) { + log.error("文件上传-发生异常:", e); + return ResponseDTO.error(SystemErrorCode.SYSTEM_ERROR, "上传失败"); + } + // 根据文件路径获取并设置访问权限 + CannedAccessControlList acl = this.getACL(path); + amazonS3.setObjectAcl(cloudConfig.getBucketName(), fileKey, acl); + // 返回上传结果 + FileUploadVO uploadVO = new FileUploadVO(); + uploadVO.setFileName(originalFileName); + uploadVO.setFileType(fileType); + // 根据 访问权限 返回不同的 URL + String url = cloudConfig.getUrlPrefix() + fileKey; + if (CannedAccessControlList.Private.equals(acl)) { + // 获取临时访问的URL + url = this.getFileUrl(fileKey).getData(); + } + uploadVO.setFileUrl(url); + uploadVO.setFileKey(fileKey); + uploadVO.setFileSize(file.getSize()); + return ResponseDTO.ok(uploadVO); + } + + /** + * 获取文件url + * + * @param fileKey + * @return + */ + @Override + public ResponseDTO getFileUrl(String fileKey) { + if (StringUtils.isBlank(fileKey)) { + return ResponseDTO.userErrorParam("文件不存在,key为空"); + } + + if (!fileKey.startsWith(FileFolderTypeEnum.FOLDER_PRIVATE)) { + // 不是私有的 都公共读 + return ResponseDTO.ok(cloudConfig.getUrlPrefix() + fileKey); + } + + // 如果是私有的,则规定时间内可以访问,超过规定时间,则连接失效 + + String fileRedisKey = RedisKeyConst.Support.FILE_PRIVATE_VO + fileKey; + FileVO fileVO = redisService.getObject(fileRedisKey, FileVO.class); + if (fileVO == null) { + fileVO = fileDao.getByFileKey(fileKey); + if (fileVO == null) { + return ResponseDTO.userErrorParam("文件不存在"); + } + + Date expiration = new Date(System.currentTimeMillis() + cloudConfig.getPrivateUrlExpireSeconds() * 1000L); + URL url = amazonS3.generatePresignedUrl(cloudConfig.getBucketName(), fileKey, expiration); + fileVO.setFileUrl(url.toString()); + redisService.set(fileRedisKey, fileVO, cloudConfig.getPrivateUrlExpireSeconds() - 5); + } + + return ResponseDTO.ok(fileVO.getFileUrl()); + } + + + /** + * 流式下载(名称为原文件) + */ + @Override + public ResponseDTO download(String key) { + //获取oss对象 + S3Object s3Object = amazonS3.getObject(cloudConfig.getBucketName(), key); + // 获取文件 meta + ObjectMetadata metadata = s3Object.getObjectMetadata(); + Map userMetadata = metadata.getUserMetadata(); + FileMetadataVO metadataDTO = null; + if (MapUtils.isNotEmpty(userMetadata)) { + metadataDTO = new FileMetadataVO(); + metadataDTO.setFileFormat(userMetadata.get(USER_METADATA_FILE_FORMAT)); + metadataDTO.setFileName(userMetadata.get(USER_METADATA_FILE_NAME)); + String fileSizeStr = userMetadata.get(USER_METADATA_FILE_SIZE); + Long fileSize = StringUtils.isBlank(fileSizeStr) ? null : Long.valueOf(fileSizeStr); + metadataDTO.setFileSize(fileSize); + } + + // 获得输入流 + InputStream objectContent = s3Object.getObjectContent(); + try { + // 输入流转换为字节流 + byte[] buffer = FileCopyUtils.copyToByteArray(objectContent); + + FileDownloadVO fileDownloadVO = new FileDownloadVO(); + fileDownloadVO.setData(buffer); + fileDownloadVO.setMetadata(metadataDTO); + return ResponseDTO.ok(fileDownloadVO); + } catch (IOException e) { + log.error("文件下载-发生异常:", e); + return ResponseDTO.error(SystemErrorCode.SYSTEM_ERROR, "下载失败"); + } finally { + try { + // 关闭输入流 + objectContent.close(); + s3Object.close(); + } catch (IOException e) { + log.error("文件下载-发生异常:", e); + } + } + } + + /** + * 根据文件夹路径 返回对应的访问权限 + * + * @param fileKey + * @return + */ + private CannedAccessControlList getACL(String fileKey) { + // 公用读 + if (fileKey.contains(FileFolderTypeEnum.FOLDER_PUBLIC)) { + return CannedAccessControlList.PublicRead; + } + // 其他默认私有读写 + return CannedAccessControlList.Private; + } + + /** + * 单个删除文件 + * 根据 file key 删除文件 + * ps:不能删除fileKey不为空的文件夹 + * + * @param fileKey 文件or文件夹 + * @return + */ + @Override + public ResponseDTO delete(String fileKey) { + amazonS3.deleteObject(cloudConfig.getBucketName(), fileKey); + return ResponseDTO.ok(); + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/service/FileStorageLocalServiceImpl.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/service/FileStorageLocalServiceImpl.java new file mode 100644 index 0000000..8e3ce86 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/service/FileStorageLocalServiceImpl.java @@ -0,0 +1,194 @@ +package net.lab1024.sa.base.module.support.file.service; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.net.NetUtil; +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.code.SystemErrorCode; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.module.support.file.domain.vo.FileDownloadVO; +import net.lab1024.sa.base.module.support.file.domain.vo.FileMetadataVO; +import net.lab1024.sa.base.module.support.file.domain.vo.FileUploadVO; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.util.FileCopyUtils; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.time.LocalDateTime; +import java.util.UUID; + +/** + * 本地存储 实现 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2019年10月11日 15:34:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 ( 1024创新实验室 ) + */ +@Slf4j +public class FileStorageLocalServiceImpl implements IFileStorageService { + + + public static final String UPLOAD_MAPPING = "/upload"; + + @Value("${file.storage.local.upload-path}") + private String uploadPath; + + @Value("${file.storage.local.url-prefix}") + private String urlPrefix; + + @Value("${server.servlet.context-path}") + private String contextPath; + + @Value("${server.port}") + private String port; + + @PostConstruct + public void initUrlPrefix() { + if (SmartStringUtil.isNotEmpty(urlPrefix)) { + return; + } + + String localhostIp = NetUtil.getLocalhostStr(); + String finalContextPath = contextPath.startsWith("/") ? contextPath : "/" + contextPath; + if (finalContextPath.endsWith("/")) { + finalContextPath = finalContextPath.substring(0, finalContextPath.length() - 1); + } + urlPrefix = "http://" + localhostIp + ":" + port + finalContextPath + UPLOAD_MAPPING; + urlPrefix = urlPrefix.endsWith("/") ? urlPrefix : urlPrefix + "/"; + } + + @Override + public ResponseDTO upload(MultipartFile multipartFile, String path) { + if (null == multipartFile) { + return ResponseDTO.userErrorParam("上传文件不能为空"); + } + String fileName = multipartFile.getOriginalFilename(); + String filePath = uploadPath + path; + File directory = new File(filePath); + if (!directory.exists()) { + // 目录不存在,新建 + directory.mkdirs(); + } + if (!path.endsWith("/")) { + path = path + "/"; + } + FileUploadVO fileUploadVO = new FileUploadVO(); + //原文件名 + String originalFileName = multipartFile.getOriginalFilename(); + //新文件名 + String uuid = UUID.randomUUID().toString().replaceAll("-", ""); + String time = LocalDateTimeUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_FORMATTER); + String newFileName = uuid + "_" + time; + String fileType = FilenameUtils.getExtension(originalFileName); + if (SmartStringUtil.isNotEmpty(fileType)) { + newFileName = newFileName + "." + fileType; + } + //生成文件key + String fileKey = path + newFileName; + //创建文件 + File fileTemp = new File(new File(filePath + newFileName).getAbsolutePath()); + try { + multipartFile.transferTo(fileTemp); + fileUploadVO.setFileUrl(this.generateFileUrl(fileKey)); + fileUploadVO.setFileName(fileName); + fileUploadVO.setFileKey(fileKey); + fileUploadVO.setFileSize(multipartFile.getSize()); + fileUploadVO.setFileType(FilenameUtils.getExtension(originalFileName)); + } catch (IOException e) { + if (fileTemp.exists() && fileTemp.isFile()) { + fileTemp.delete(); + } + log.error("", e); + return ResponseDTO.error(SystemErrorCode.SYSTEM_ERROR, "上传失败"); + } + return ResponseDTO.ok(fileUploadVO); + } + + /** + * 生成fileUrl地址 + * + * @param filePath + * @return + */ + public String generateFileUrl(String filePath) { + return urlPrefix + filePath; + } + + /** + * 获取文件Url + * + * @param fileKey + * @return + */ + @Override + public ResponseDTO getFileUrl(String fileKey) { + if (StringUtils.isBlank(fileKey)) { + return ResponseDTO.userErrorParam("文件不存在,key为空"); + } + + String fileUrl = this.generateFileUrl(fileKey); + return ResponseDTO.ok(fileUrl); + } + + /** + * 文件下载 + * + * @param fileKey + * @return + */ + @Override + public ResponseDTO download(String fileKey) { + String filePath = uploadPath + fileKey; + File localFile = new File(filePath); + InputStream in = null; + try { + in = Files.newInputStream(localFile.toPath()); + // 输入流转换为字节流 + byte[] buffer = FileCopyUtils.copyToByteArray(in); + FileDownloadVO fileDownloadVO = new FileDownloadVO(); + fileDownloadVO.setData(buffer); + + FileMetadataVO fileMetadataDTO = new FileMetadataVO(); + fileMetadataDTO.setFileName(localFile.getName()); + fileMetadataDTO.setFileSize(localFile.length()); + fileMetadataDTO.setFileFormat(FilenameUtils.getExtension(localFile.getName())); + fileDownloadVO.setMetadata(fileMetadataDTO); + + return ResponseDTO.ok(fileDownloadVO); + } catch (IOException e) { + log.error("文件下载-发生异常:", e); + return ResponseDTO.error(SystemErrorCode.SYSTEM_ERROR, "文件下载失败"); + } finally { + try { + // 关闭输入流 + if (in != null) { + in.close(); + } + } catch (IOException e) { + log.error("文件下载-发生异常:", e); + } + } + } + + @Override + public ResponseDTO delete(String fileKey) { + String filePath = uploadPath + fileKey; + File localFile = new File(filePath); + try { + FileUtils.forceDelete(localFile); + } catch (IOException e) { + log.error("删除本地文件失败:{}", filePath, e); + } + return ResponseDTO.ok(); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/service/IFileStorageService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/service/IFileStorageService.java new file mode 100644 index 0000000..aa4ec75 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/service/IFileStorageService.java @@ -0,0 +1,99 @@ +package net.lab1024.sa.base.module.support.file.service; + +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.module.support.file.domain.vo.FileDownloadVO; +import net.lab1024.sa.base.module.support.file.domain.vo.FileUploadVO; +import org.springframework.web.multipart.MultipartFile; + +/** + * 接口 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2019年10月11日 15:34:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public interface IFileStorageService { + + /** + * 文件上传 + * + * @param file + * @param path + * @return + */ + ResponseDTO upload(MultipartFile file, String path); + + /** + * 获取文件url + * + * @param fileKey + * @return + */ + ResponseDTO getFileUrl(String fileKey); + + + /** + * 流式下载(名称为原文件) + * + * @param key + * @return + */ + ResponseDTO download(String key); + + /** + * 单个删除文件 + * 根据文件key删除 + * + * @param fileKey + * @return + */ + ResponseDTO delete(String fileKey); + + + /** + * 获取文件类型 + * + * @param fileExt + * @return + */ + default String getContentType(String fileExt) { + // 文件的后缀名 + if ("bmp".equalsIgnoreCase(fileExt)) { + return "image/bmp"; + } + if ("gif".equalsIgnoreCase(fileExt)) { + return "image/gif"; + } + if ("jpeg".equalsIgnoreCase(fileExt) || "jpg".equalsIgnoreCase(fileExt)) { + return "image/jpeg"; + } + if ("png".equalsIgnoreCase(fileExt)) { + return "image/png"; + } + if ("html".equalsIgnoreCase(fileExt)) { + return "text/html"; + } + if ("txt".equalsIgnoreCase(fileExt)) { + return "text/plain"; + } + if ("vsd".equalsIgnoreCase(fileExt)) { + return "application/vnd.visio"; + } + if ("ppt".equalsIgnoreCase(fileExt) || "pptx".equalsIgnoreCase(fileExt)) { + return "application/vnd.ms-powerpoint"; + } + if ("doc".equalsIgnoreCase(fileExt) || "docx".equalsIgnoreCase(fileExt)) { + return "application/msword"; + } + if ("pdf".equalsIgnoreCase(fileExt)) { + return "application/pdf"; + } + if ("xml".equalsIgnoreCase(fileExt)) { + return "text/xml"; + } + return ""; + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/HeartBeatRecordDao.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/HeartBeatRecordDao.java new file mode 100644 index 0000000..df1ab21 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/HeartBeatRecordDao.java @@ -0,0 +1,49 @@ +package net.lab1024.sa.base.module.support.heartbeat; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.base.module.support.heartbeat.domain.HeartBeatRecordEntity; +import net.lab1024.sa.base.module.support.heartbeat.domain.HeartBeatRecordQueryForm; +import net.lab1024.sa.base.module.support.heartbeat.domain.HeartBeatRecordVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 心跳记录 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-01-09 20:57:24 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface HeartBeatRecordDao extends BaseMapper { + + /** + * 更新心跳日志 + * + * @param id + * @param heartBeatTime + */ + void updateHeartBeatTimeById(@Param("id") Long id, @Param("heartBeatTime") LocalDateTime heartBeatTime); + + /** + * 查询心跳日志 + * + * @param heartBeatRecordEntity + * @return + */ + HeartBeatRecordEntity query(HeartBeatRecordEntity heartBeatRecordEntity); + + /** + * 分页查询 + * @param heartBeatRecordQueryForm + * @return + */ + List pageQuery(Page page, @Param("query") HeartBeatRecordQueryForm heartBeatRecordQueryForm); +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/HeartBeatRecordHandler.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/HeartBeatRecordHandler.java new file mode 100644 index 0000000..28b5081 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/HeartBeatRecordHandler.java @@ -0,0 +1,42 @@ +package net.lab1024.sa.base.module.support.heartbeat; + +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.module.support.heartbeat.core.HeartBeatRecord; +import net.lab1024.sa.base.module.support.heartbeat.core.IHeartBeatRecordHandler; +import net.lab1024.sa.base.module.support.heartbeat.domain.HeartBeatRecordEntity; +import org.springframework.stereotype.Service; + +/** + * 心跳记录 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-01-09 20:57:24 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@Service +public class HeartBeatRecordHandler implements IHeartBeatRecordHandler { + + @Resource + private HeartBeatRecordDao heartBeatRecordDao; + + /** + * 心跳日志处理方法 + * @param heartBeatRecord + */ + @Override + public void handler(HeartBeatRecord heartBeatRecord) { + HeartBeatRecordEntity heartBeatRecordEntity = SmartBeanUtil.copy(heartBeatRecord, HeartBeatRecordEntity.class); + HeartBeatRecordEntity heartBeatRecordOld = heartBeatRecordDao.query(heartBeatRecordEntity); + if (heartBeatRecordOld == null) { + heartBeatRecordDao.insert(heartBeatRecordEntity); + } else { + heartBeatRecordDao.updateHeartBeatTimeById(heartBeatRecordOld.getHeartBeatRecordId(), heartBeatRecordEntity.getHeartBeatTime()); + } + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/HeartBeatService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/HeartBeatService.java new file mode 100644 index 0000000..f9847ad --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/HeartBeatService.java @@ -0,0 +1,37 @@ +package net.lab1024.sa.base.module.support.heartbeat; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.module.support.heartbeat.domain.HeartBeatRecordQueryForm; +import net.lab1024.sa.base.module.support.heartbeat.domain.HeartBeatRecordVO; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 心跳记录 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-01-09 20:57:24 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@Service +public class HeartBeatService { + + @Resource + private HeartBeatRecordDao heartBeatRecordDao; + + public ResponseDTO> pageQuery(HeartBeatRecordQueryForm pageParam) { + Page pageQueryInfo = SmartPageUtil.convert2PageQuery(pageParam); + List recordVOList = heartBeatRecordDao.pageQuery(pageQueryInfo,pageParam); + PageResult pageResult = SmartPageUtil.convert2PageResult(pageQueryInfo, recordVOList); + return ResponseDTO.ok(pageResult); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/core/HeartBeatManager.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/core/HeartBeatManager.java new file mode 100644 index 0000000..010d5d5 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/core/HeartBeatManager.java @@ -0,0 +1,59 @@ +package net.lab1024.sa.base.module.support.heartbeat.core; + +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * 心跳核心调度管理器 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-01-09 20:57:24 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class HeartBeatManager { + + private static final String THREAD_NAME_PREFIX = "smart-heart-beat"; + private static final int THREAD_COUNT = 1; + private static final long INITIAL_DELAY = 60 * 1000L; + + private ScheduledThreadPoolExecutor threadPoolExecutor; + + /** + * 服务状态持久化处理类 + */ + private IHeartBeatRecordHandler heartBeatRecordHandler; + + /** + * 调度配置信息 + */ + private long intervalMilliseconds; + + /** + * @param intervalMilliseconds 间隔执行时间(毫秒) + */ + public HeartBeatManager(Long intervalMilliseconds, + IHeartBeatRecordHandler heartBeatRecordHandler) { + this.intervalMilliseconds = intervalMilliseconds; + this.heartBeatRecordHandler = heartBeatRecordHandler; + //使用守护线程去处理 + this.threadPoolExecutor = new ScheduledThreadPoolExecutor(THREAD_COUNT, r -> { + Thread t = new Thread(r, THREAD_NAME_PREFIX); + if (!t.isDaemon()) { + t.setDaemon(true); + } + return t; + }); + // 开始心跳 + this.beginHeartBeat(); + } + + /** + * 开启心跳 + */ + private void beginHeartBeat() { + HeartBeatRunnable heartBeatRunnable = new HeartBeatRunnable(heartBeatRecordHandler); + threadPoolExecutor.scheduleWithFixedDelay(heartBeatRunnable, INITIAL_DELAY, intervalMilliseconds, TimeUnit.MILLISECONDS); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/core/HeartBeatRecord.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/core/HeartBeatRecord.java new file mode 100644 index 0000000..587f3b4 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/core/HeartBeatRecord.java @@ -0,0 +1,41 @@ +package net.lab1024.sa.base.module.support.heartbeat.core; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 心跳记录日志 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-01-09 20:57:24 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class HeartBeatRecord { + + /** + * 项目名字 + */ + private String projectPath; + /** + * 服务器ip + */ + private String serverIp; + /** + * 进程号 + */ + private Integer processNo; + /** + * 进程开启时间 + */ + private LocalDateTime processStartTime; + /** + * 心跳当前时间 + */ + private LocalDateTime heartBeatTime; + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/core/HeartBeatRunnable.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/core/HeartBeatRunnable.java new file mode 100644 index 0000000..65d06ba --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/core/HeartBeatRunnable.java @@ -0,0 +1,71 @@ +package net.lab1024.sa.base.module.support.heartbeat.core; + +import cn.hutool.core.net.NetUtil; +import org.apache.commons.lang3.StringUtils; + +import java.lang.management.ManagementFactory; +import java.lang.management.RuntimeMXBean; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.List; + +/** + * 心跳线程 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-01-09 20:57:24 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class HeartBeatRunnable implements Runnable { + + /** + * 项目路径 + */ + private String projectPath; + /** + * 服务器ip(多网卡) + */ + private List serverIps; + /** + * 进程号 + */ + private Integer processNo; + /** + * 进程开启时间 + */ + private LocalDateTime processStartTime; + + private IHeartBeatRecordHandler recordHandler; + + public HeartBeatRunnable(IHeartBeatRecordHandler recordHandler) { + this.recordHandler = recordHandler; + this.initServerInfo(); + } + + /** + * 初始化心跳相关信息 + */ + private void initServerInfo(){ + RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean(); + this.projectPath = System.getProperty("user.dir"); + this.serverIps = new ArrayList<>(NetUtil.localIpv4s()); + this.processNo = Integer.valueOf(runtimeMXBean.getName().split("@")[0]).intValue(); + this.processStartTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(runtimeMXBean.getStartTime()), ZoneId.systemDefault()); + } + + + @Override + public void run() { + HeartBeatRecord heartBeatRecord = new HeartBeatRecord(); + heartBeatRecord.setProjectPath(this.projectPath); + heartBeatRecord.setServerIp(StringUtils.join(this.serverIps, ";")); + heartBeatRecord.setProcessNo(this.processNo); + heartBeatRecord.setProcessStartTime(this.processStartTime); + heartBeatRecord.setHeartBeatTime(LocalDateTime.now()); + recordHandler.handler(heartBeatRecord); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/core/IHeartBeatRecordHandler.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/core/IHeartBeatRecordHandler.java new file mode 100644 index 0000000..d0157d2 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/core/IHeartBeatRecordHandler.java @@ -0,0 +1,20 @@ +package net.lab1024.sa.base.module.support.heartbeat.core; + +/** + * 心跳处理接口 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-01-09 20:57:24 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public interface IHeartBeatRecordHandler { + + /** + * 心跳日志处理方法 + * + * @param heartBeatRecord + */ + void handler(HeartBeatRecord heartBeatRecord); +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/domain/HeartBeatRecordEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/domain/HeartBeatRecordEntity.java new file mode 100644 index 0000000..ba10db1 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/domain/HeartBeatRecordEntity.java @@ -0,0 +1,52 @@ +package net.lab1024.sa.base.module.support.heartbeat.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 心跳记录 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-01-09 20:57:24 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName(value = "t_heart_beat_record") +public class HeartBeatRecordEntity implements Serializable { + + /** + * 主键id + */ + @TableId(type = IdType.AUTO) + private Long heartBeatRecordId; + + /** + * 项目名字 + */ + private String projectPath; + /** + * 服务器ip + */ + private String serverIp; + /** + * 进程号 + */ + private Integer processNo; + /** + * 进程开启时间 + */ + private LocalDateTime processStartTime; + /** + * 心跳当前时间 + */ + private LocalDateTime heartBeatTime; + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/domain/HeartBeatRecordQueryForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/domain/HeartBeatRecordQueryForm.java new file mode 100644 index 0000000..873d8cc --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/domain/HeartBeatRecordQueryForm.java @@ -0,0 +1,30 @@ +package net.lab1024.sa.base.module.support.heartbeat.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; + +import java.time.LocalDate; + +/** + * 心跳记录 查询 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-01-09 20:57:24 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class HeartBeatRecordQueryForm extends PageParam { + + @Schema(description = "关键字") + private String keywords; + + @Schema(description = "开始日期") + private LocalDate startDate; + + @Schema(description = "结束日期") + private LocalDate endDate; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/domain/HeartBeatRecordVO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/domain/HeartBeatRecordVO.java new file mode 100644 index 0000000..10ac931 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/domain/HeartBeatRecordVO.java @@ -0,0 +1,38 @@ +package net.lab1024.sa.base.module.support.heartbeat.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.Date; + +/** + * 心跳记录 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-01-09 20:57:24 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class HeartBeatRecordVO { + + private Integer heartBeatRecordId; + + @Schema(description = "项目路径") + private String projectPath; + + @Schema(description = "服务器ip") + private String serverIp; + + @Schema(description = "进程号") + private Integer processNo; + + @Schema(description = "进程开启时间") + private Date processStartTime; + + @Schema(description = "心跳当前时间") + private Date heartBeatTime; + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/controller/HelpDocController.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/controller/HelpDocController.java new file mode 100644 index 0000000..a20112b --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/controller/HelpDocController.java @@ -0,0 +1,77 @@ +package net.lab1024.sa.base.module.support.helpdoc.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import net.lab1024.sa.base.common.controller.SupportBaseController; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartRequestUtil; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import net.lab1024.sa.base.module.support.helpdoc.domain.form.HelpDocViewRecordQueryForm; +import net.lab1024.sa.base.module.support.helpdoc.domain.vo.HelpDocCatalogVO; +import net.lab1024.sa.base.module.support.helpdoc.domain.vo.HelpDocDetailVO; +import net.lab1024.sa.base.module.support.helpdoc.domain.vo.HelpDocVO; +import net.lab1024.sa.base.module.support.helpdoc.domain.vo.HelpDocViewRecordVO; +import net.lab1024.sa.base.module.support.helpdoc.service.HelpDocCatalogService; +import net.lab1024.sa.base.module.support.helpdoc.service.HelpDocUserService; +import net.lab1024.sa.base.module.support.repeatsubmit.annoation.RepeatSubmit; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 帮助文档 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Tag(name = SwaggerTagConst.Support.HELP_DOC) +@RestController +public class HelpDocController extends SupportBaseController { + + @Resource + private HelpDocCatalogService helpDocCatalogService; + + @Resource + private HelpDocUserService helpDocUserService; + + // --------------------- 帮助文档 【目录】 ------------------------- + + @Operation(summary = "帮助文档目录-获取全部 @author 卓大") + @GetMapping("/helpDoc/helpDocCatalog/getAll") + public ResponseDTO> getAll() { + return ResponseDTO.ok(helpDocCatalogService.getAll()); + } + + // --------------------- 帮助文档 【用户】------------------------- + + @Operation(summary = "【用户】帮助文档-查看详情 @author 卓大") + @GetMapping("/helpDoc/user/view/{helpDocId}") + @RepeatSubmit + public ResponseDTO view(@PathVariable Long helpDocId, HttpServletRequest request) { + return helpDocUserService.view( + SmartRequestUtil.getRequestUser(), + helpDocId); + } + + @Operation(summary = "【用户】帮助文档-查询全部 @author 卓大") + @GetMapping("/helpDoc/user/queryAllHelpDocList") + @RepeatSubmit + public ResponseDTO> queryAllHelpDocList() { + return helpDocUserService.queryAllHelpDocList(); + } + + + @Operation(summary = "【用户】帮助文档-查询 查看记录 @author 卓大") + @PostMapping("/helpDoc/user/queryViewRecord") + @RepeatSubmit + public ResponseDTO> queryViewRecord(@RequestBody @Valid HelpDocViewRecordQueryForm helpDocViewRecordQueryForm) { + return ResponseDTO.ok(helpDocUserService.queryViewRecord(helpDocViewRecordQueryForm)); + } +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/dao/HelpDocCatalogDao.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/dao/HelpDocCatalogDao.java new file mode 100644 index 0000000..c04ec08 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/dao/HelpDocCatalogDao.java @@ -0,0 +1,20 @@ +package net.lab1024.sa.base.module.support.helpdoc.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import net.lab1024.sa.base.module.support.helpdoc.domain.entity.HelpDocCatalogEntity; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.stereotype.Component; + +/** + * 帮助文档目录 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface HelpDocCatalogDao extends BaseMapper { + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/dao/HelpDocDao.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/dao/HelpDocDao.java new file mode 100644 index 0000000..902f107 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/dao/HelpDocDao.java @@ -0,0 +1,136 @@ +package net.lab1024.sa.base.module.support.helpdoc.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.base.module.support.helpdoc.domain.entity.HelpDocEntity; +import net.lab1024.sa.base.module.support.helpdoc.domain.form.HelpDocQueryForm; +import net.lab1024.sa.base.module.support.helpdoc.domain.form.HelpDocRelationForm; +import net.lab1024.sa.base.module.support.helpdoc.domain.form.HelpDocViewRecordQueryForm; +import net.lab1024.sa.base.module.support.helpdoc.domain.vo.HelpDocRelationVO; +import net.lab1024.sa.base.module.support.helpdoc.domain.vo.HelpDocVO; +import net.lab1024.sa.base.module.support.helpdoc.domain.vo.HelpDocViewRecordVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 帮助文档 dao + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface HelpDocDao extends BaseMapper { + + // ================================= 帮助文档【主表 t_help_doc 】 ================================= + + + /** + * 查询 全部相关文档 + * + * @return + */ + List queryAllHelpDocList(); + + /** + * 后管分页查询帮助文档 + * + * @param page + * @param queryForm + * @return + */ + List query(Page page, @Param("query") HelpDocQueryForm queryForm); + + + /** + * 更新 阅读量 + * @param helpDocId + * @param userViewCountIncrease + * @param pageViewCountIncrease + */ + void updateViewCount(@Param("helpDocId")Long helpDocId, @Param("userViewCountIncrease")Integer userViewCountIncrease,@Param("pageViewCountIncrease") Integer pageViewCountIncrease); + + + /** + * 根据目录,查询文档 + * + * @param helpDocCatalogId + * @return + */ + List queryHelpDocByCatalogId( @Param("helpDocCatalogId") Long helpDocCatalogId); + + /** + * 根据关联文档id,查询文档 + * + * @param relationId + * @return + */ + List queryHelpDocByRelationId( @Param("relationId") Long relationId); + + // ================================= 关联项目 【子表 t_help_doc_relation 】 ================================= + + /** + * 保存 关联 + * + * @param helpDocId + * @param relationList + */ + void insertRelation(@Param("helpDocId") Long helpDocId, @Param("relationList") List relationList); + + /** + * 删除关联 + * + * @param helpDocId + */ + void deleteRelation(@Param("helpDocId") Long helpDocId); + + /** + * 查询关联 + * + * @param helpDocId + */ + List queryRelationByHelpDoc(@Param("helpDocId") Long helpDocId); + + // ================================= 查看记录【子表 t_help_doc_view_record】 ================================= + + /** + * 查询某个用户的指定文档的阅读量 + * @param helpDocId + * @param userId + * @return + */ + long viewRecordCount(@Param("helpDocId")Long helpDocId, @Param("userId")Long userId); + + /** + * 查询帮助文档的 查看记录 + * @param page + * @param helpDocViewRecordQueryForm + * @return + */ + List queryViewRecordList(Page page, @Param("queryForm") HelpDocViewRecordQueryForm helpDocViewRecordQueryForm); + + /** + * 保存查看记录 + * @param helpDocId + * @param userId + * @param userName + * @param ip + * @param userAgent + */ + void insertViewRecord(@Param("helpDocId") Long helpDocId, @Param("userId") Long userId, @Param("userName") String userName, @Param("ip") String ip, @Param("userAgent") String userAgent,@Param("pageViewCount") Integer pageViewCount); + + /** + * 更新查看记录 + * @param helpDocId + * @param userId + * @param ip + * @param userAgent + */ + void updateViewRecord(@Param("helpDocId")Long helpDocId, @Param("userId")Long userId,@Param("ip") String ip, @Param("userAgent")String userAgent); + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/entity/HelpDocCatalogEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/entity/HelpDocCatalogEntity.java new file mode 100644 index 0000000..3b6a2cc --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/entity/HelpDocCatalogEntity.java @@ -0,0 +1,53 @@ +package net.lab1024.sa.base.module.support.helpdoc.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +/** + * 帮助文档的 类型 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName("t_help_doc_catalog") +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class HelpDocCatalogEntity { + + @TableId(type = IdType.AUTO) + private Long helpDocCatalogId; + + /** + * 名称 + */ + private String name; + + /** + * 父id + */ + private Long parentId; + + /** + * 排序 + */ + @TableField("`sort`") + private Integer sort; + + + private LocalDateTime updateTime; + + private LocalDateTime createTime; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/entity/HelpDocEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/entity/HelpDocEntity.java new file mode 100644 index 0000000..6dd0238 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/entity/HelpDocEntity.java @@ -0,0 +1,77 @@ +package net.lab1024.sa.base.module.support.helpdoc.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 帮助文档 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName("t_help_doc") +public class HelpDocEntity { + + @TableId(type = IdType.AUTO) + private Long helpDocId; + + /** + * 类型 + */ + private Long helpDocCatalogId; + + /** + * 标题 + */ + private String title; + + /** + * 内容 纯文本 + */ + private String contentText; + + /** + * 内容 html + */ + private String contentHtml; + + /** + * 附件 + * 多个英文逗号分隔 + */ + private String attachment; + + /** + * 排序 + */ + @TableField("`sort`") + private Integer sort; + + /** + * 页面浏览量 + */ + private Integer pageViewCount; + + /** + * 用户浏览量 + */ + private Integer userViewCount; + + /** + * 作者 + */ + private String author; + + private LocalDateTime updateTime; + + private LocalDateTime createTime; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocAddForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocAddForm.java new file mode 100644 index 0000000..0351b46 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocAddForm.java @@ -0,0 +1,57 @@ +package net.lab1024.sa.base.module.support.helpdoc.domain.form; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import net.lab1024.sa.base.common.json.deserializer.FileKeyVoDeserializer; +import org.hibernate.validator.constraints.Length; + +import java.util.List; + +/** + * 帮助文档 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class HelpDocAddForm { + + @Schema(description = "标题") + @NotBlank(message = "标题不能为空") + @Length(max = 200, message = "标题最多200字符") + private String title; + + @Schema(description = "分类") + @NotNull(message = "分类不能为空") + private Long helpDocCatalogId; + + @Schema(description = "纯文本内容") + @NotNull(message = "文本内容不能为空") + private String contentText; + + @Schema(description = "html内容") + @NotNull(message = "html内容不能为空") + private String contentHtml; + + @Schema(description = "附件,多个英文逗号分隔|可选") + @Length(max = 1000, message = "最多1000字符") + @JsonDeserialize(using = FileKeyVoDeserializer.class) + private String attachment; + + @Schema(description = "排序") + @NotNull(message = "排序不能为空") + private Integer sort; + + @Schema(description = "关联的集合") + private List relationList; + + @Schema(description = "作者") + @NotBlank(message = "作者不能为空") + private String author; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocCatalogAddForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocCatalogAddForm.java new file mode 100644 index 0000000..5c376d6 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocCatalogAddForm.java @@ -0,0 +1,30 @@ +package net.lab1024.sa.base.module.support.helpdoc.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +/** + * 帮助文档 目录 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class HelpDocCatalogAddForm { + + @Schema(description = "名称") + @NotBlank(message = "名称不能为空") + @Length(max = 200, message = "名称最多200字符") + private String name; + + @Schema(description = "父级") + private Long parentId; + + @Schema(description = "排序") + private Integer sort; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocCatalogUpdateForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocCatalogUpdateForm.java new file mode 100644 index 0000000..415af65 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocCatalogUpdateForm.java @@ -0,0 +1,22 @@ +package net.lab1024.sa.base.module.support.helpdoc.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 帮助文档 目录 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class HelpDocCatalogUpdateForm extends HelpDocCatalogAddForm { + + @Schema(description = "id") + @NotNull(message = "id") + private Long helpDocCatalogId; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocQueryForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocQueryForm.java new file mode 100644 index 0000000..bee968c --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocQueryForm.java @@ -0,0 +1,33 @@ +package net.lab1024.sa.base.module.support.helpdoc.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; + +import java.time.LocalDate; + +/** + * 帮助文档 分页查询 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class HelpDocQueryForm extends PageParam { + + @Schema(description = "分类") + private Long helpDocCatalogId; + + @Schema(description = "标题") + private String keywords; + + @Schema(description = "创建-开始时间") + private LocalDate createTimeBegin; + + @Schema(description = "创建-截止时间") + private LocalDate createTimeEnd; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocRelationForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocRelationForm.java new file mode 100644 index 0000000..5801701 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocRelationForm.java @@ -0,0 +1,27 @@ +package net.lab1024.sa.base.module.support.helpdoc.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 帮助文档 关联项目 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class HelpDocRelationForm { + + @Schema(description = "关联名称") + @NotBlank(message = "关联名称不能为空") + private String relationName; + + @Schema(description = "关联id") + @NotNull(message = "关联id不能为空") + private Long relationId; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocUpdateForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocUpdateForm.java new file mode 100644 index 0000000..3afb994 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocUpdateForm.java @@ -0,0 +1,23 @@ +package net.lab1024.sa.base.module.support.helpdoc.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 更新 帮助文档 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class HelpDocUpdateForm extends HelpDocAddForm { + + @Schema(description = "id") + @NotNull(message = "通知id不能为空") + private Long helpDocId; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocViewRecordQueryForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocViewRecordQueryForm.java new file mode 100644 index 0000000..0b9bd97 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocViewRecordQueryForm.java @@ -0,0 +1,31 @@ +package net.lab1024.sa.base.module.support.helpdoc.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; + +/** + * 查阅记录 查询 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class HelpDocViewRecordQueryForm extends PageParam { + + @Schema(description = "帮助文档id") + @NotNull(message = "帮助文档id不能为空") + private Long helpDocId; + + @Schema(description = "用户id") + private Long userId; + + @Schema(description = "关键字") + private String keywords; + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/vo/HelpDocCatalogVO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/vo/HelpDocCatalogVO.java new file mode 100644 index 0000000..59d0117 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/vo/HelpDocCatalogVO.java @@ -0,0 +1,30 @@ +package net.lab1024.sa.base.module.support.helpdoc.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 帮助文档的 目录 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class HelpDocCatalogVO { + + @Schema(description = "帮助文档目录id") + private Long helpDocCatalogId; + + @Schema(description = "帮助文档目录-名称") + private String name; + + @Schema(description = "帮助文档目录-父级id") + private Long parentId; + + @Schema(description = "帮助文档目录-排序") + private Integer sort; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/vo/HelpDocDetailVO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/vo/HelpDocDetailVO.java new file mode 100644 index 0000000..02d0aeb --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/vo/HelpDocDetailVO.java @@ -0,0 +1,65 @@ +package net.lab1024.sa.base.module.support.helpdoc.domain.vo; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import net.lab1024.sa.base.common.json.serializer.FileKeyVoSerializer; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 帮助文档 详情 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class HelpDocDetailVO { + + @Schema(description = "id") + private Long helpDocId; + + @Schema(description = "标题") + private String title; + + @Schema(description = "分类") + private Long helpDocCatalogId; + + @Schema(description = "分类名称") + private Long helpDocCatalogName; + + @Schema(description = "纯文本内容") + private String contentText; + + @Schema(description = "html内容") + private String contentHtml; + + @Schema(description = "附件") + @JsonSerialize(using = FileKeyVoSerializer.class) + private String attachment; + + @Schema(description = "作者") + @NotBlank(message = "作者不能为空") + private String author; + + @Schema(description = "页面浏览量") + private Integer pageViewCount; + + @Schema(description = "用户浏览量") + private Integer userViewCount; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "更新时间") + private LocalDateTime updateTime; + + @Schema(description = "关联项目") + private List relationList; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/vo/HelpDocRecordVO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/vo/HelpDocRecordVO.java new file mode 100644 index 0000000..86b9e97 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/vo/HelpDocRecordVO.java @@ -0,0 +1,49 @@ +package net.lab1024.sa.base.module.support.helpdoc.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 帮助文档 - 浏览记录 VO + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class HelpDocRecordVO { + + @Schema(description = "员工ID") + private Long employeeId; + + @Schema(description = "员工姓名") + private String employeeName; + + @Schema(description = "员工部门名称") + private String departmentName; + + @Schema(description = "查看次数") + private Integer pageViewCount; + + @Schema(description = "首次ip") + private String firstIp; + + @Schema(description = "首次用户设备等标识") + private String firstUserAgent; + + @Schema(description = "首次查看时间") + private LocalDateTime createTime; + + @Schema(description = "最后一次 ip") + private String lastIp; + + @Schema(description = "最后一次 用户设备等标识") + private String lastUserAgent; + + @Schema(description = "最后一次查看时间") + private LocalDateTime updateTime; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/vo/HelpDocRelationVO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/vo/HelpDocRelationVO.java new file mode 100644 index 0000000..4b75391 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/vo/HelpDocRelationVO.java @@ -0,0 +1,23 @@ +package net.lab1024.sa.base.module.support.helpdoc.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 帮助文档 关联项目 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class HelpDocRelationVO { + + @Schema(description = "关联名称") + private String relationName; + + @Schema(description = "关联id") + private Long relationId; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/vo/HelpDocVO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/vo/HelpDocVO.java new file mode 100644 index 0000000..7917189 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/vo/HelpDocVO.java @@ -0,0 +1,50 @@ +package net.lab1024.sa.base.module.support.helpdoc.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 帮助文档 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class HelpDocVO { + + @Schema(description = "id") + private Long helpDocId; + + @Schema(description = "标题") + private String title; + + @Schema(description = "分类") + private Long helpDocCatalogId; + + @Schema(description = "分类名称") + private String helpDocCatalogName; + + @Schema(description = "作者") + private String author; + + @Schema(description = "排序") + private Integer sort; + + @Schema(description = "页面浏览量") + private Integer pageViewCount; + + @Schema(description = "用户浏览量") + private Integer userViewCount; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "更新时间") + private LocalDateTime updateTime; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/vo/HelpDocViewRecordVO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/vo/HelpDocViewRecordVO.java new file mode 100644 index 0000000..a0b4000 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/vo/HelpDocViewRecordVO.java @@ -0,0 +1,46 @@ +package net.lab1024.sa.base.module.support.helpdoc.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 帮助文档 - 浏览记录 VO + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class HelpDocViewRecordVO { + + @Schema(description = "ID") + private Long userId; + + @Schema(description = "姓名") + private String userName; + + @Schema(description = "查看次数") + private Integer pageViewCount; + + @Schema(description = "首次ip") + private String firstIp; + + @Schema(description = "首次用户设备等标识") + private String firstUserAgent; + + @Schema(description = "首次查看时间") + private LocalDateTime createTime; + + @Schema(description = "最后一次 ip") + private String lastIp; + + @Schema(description = "最后一次 用户设备等标识") + private String lastUserAgent; + + @Schema(description = "最后一次查看时间") + private LocalDateTime updateTime; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/manager/HelpDocManager.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/manager/HelpDocManager.java new file mode 100644 index 0000000..162a7b5 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/manager/HelpDocManager.java @@ -0,0 +1,60 @@ +package net.lab1024.sa.base.module.support.helpdoc.manager; + +import jakarta.annotation.Resource; +import net.lab1024.sa.base.module.support.helpdoc.dao.HelpDocDao; +import net.lab1024.sa.base.module.support.helpdoc.domain.entity.HelpDocEntity; +import net.lab1024.sa.base.module.support.helpdoc.domain.form.HelpDocRelationForm; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/** + * 帮助文档 manager + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class HelpDocManager { + + @Resource + private HelpDocDao helpDocDao; + + /** + * 保存 + * + * @param helpDocEntity + * @param relationList + */ + @Transactional(rollbackFor = Throwable.class) + public void save(HelpDocEntity helpDocEntity, List relationList) { + helpDocDao.insert(helpDocEntity); + Long helpDocId = helpDocEntity.getHelpDocId(); + // 保存关联 + if (CollectionUtils.isNotEmpty(relationList)) { + helpDocDao.insertRelation(helpDocId, relationList); + } + } + + /** + * 更新 + * + * @param helpDocEntity + * @param relationList + */ + @Transactional(rollbackFor = Throwable.class) + public void update(HelpDocEntity helpDocEntity, List relationList) { + helpDocDao.updateById(helpDocEntity); + Long helpDocId = helpDocEntity.getHelpDocId(); + // 保存关联 + if (CollectionUtils.isNotEmpty(relationList)) { + helpDocDao.deleteRelation(helpDocId); + helpDocDao.insertRelation(helpDocId, relationList); + } + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/service/HelpDocCatalogService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/service/HelpDocCatalogService.java new file mode 100644 index 0000000..3fa1170 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/service/HelpDocCatalogService.java @@ -0,0 +1,115 @@ +package net.lab1024.sa.base.module.support.helpdoc.service; + +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.module.support.helpdoc.dao.HelpDocCatalogDao; +import net.lab1024.sa.base.module.support.helpdoc.dao.HelpDocDao; +import net.lab1024.sa.base.module.support.helpdoc.domain.entity.HelpDocCatalogEntity; +import net.lab1024.sa.base.module.support.helpdoc.domain.form.HelpDocCatalogAddForm; +import net.lab1024.sa.base.module.support.helpdoc.domain.form.HelpDocCatalogUpdateForm; +import net.lab1024.sa.base.module.support.helpdoc.domain.vo.HelpDocCatalogVO; +import net.lab1024.sa.base.module.support.helpdoc.domain.vo.HelpDocVO; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +/** + * 帮助文档 目录 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class HelpDocCatalogService { + + @Resource + private HelpDocCatalogDao helpDocCatalogDao; + + @Resource + private HelpDocDao helpDocDao; + + /** + * 查询全部目录 + * + * @return + */ + public List getAll() { + return SmartBeanUtil.copyList(helpDocCatalogDao.selectList(null), HelpDocCatalogVO.class); + } + + /** + * 添加目录 + * + * @param helpDocCatalogAddForm + * @return + */ + public synchronized ResponseDTO add(HelpDocCatalogAddForm helpDocCatalogAddForm) { + List helpDocCatalogList = getAll(); + Optional exist = helpDocCatalogList.stream().filter(e -> helpDocCatalogAddForm.getName().equals(e.getName())).findFirst(); + if (exist.isPresent()) { + return ResponseDTO.userErrorParam("存在相同名称的目录了"); + } + + helpDocCatalogDao.insert(SmartBeanUtil.copy(helpDocCatalogAddForm, HelpDocCatalogEntity.class)); + return ResponseDTO.ok(); + } + + /** + * 更新目录 + * + * @param updateForm + * @return + */ + public synchronized ResponseDTO update(HelpDocCatalogUpdateForm updateForm) { + HelpDocCatalogEntity helpDocCatalogEntity = helpDocCatalogDao.selectById(updateForm.getHelpDocCatalogId()); + if (helpDocCatalogEntity == null) { + return ResponseDTO.userErrorParam("目录不存在"); + } + + List helpDocCatalogList = getAll(); + Optional exist = helpDocCatalogList.stream().filter(e -> updateForm.getName().equals(e.getName())).findFirst(); + if (exist.isPresent() && !exist.get().getHelpDocCatalogId().equals(updateForm.getHelpDocCatalogId())) { + return ResponseDTO.userErrorParam("存在相同名称的目录了"); + } + helpDocCatalogDao.updateById(SmartBeanUtil.copy(updateForm, HelpDocCatalogEntity.class)); + return ResponseDTO.ok(); + } + + /** + * 删除目录(如果有子目录、或者有帮助文档,则不能删除) + * + * @param helpDocCatalogId + * @return + */ + public synchronized ResponseDTO delete(Long helpDocCatalogId) { + if (helpDocCatalogId == null) { + return ResponseDTO.ok(); + } + + HelpDocCatalogEntity helpDocCatalogEntity = helpDocCatalogDao.selectById(helpDocCatalogId); + if (helpDocCatalogEntity == null) { + return ResponseDTO.userErrorParam("目录不存在"); + } + + //如果有子目录,则不能删除 + Optional existOptional = getAll().stream().filter(e -> helpDocCatalogId.equals(e.getParentId())).findFirst(); + if (existOptional.isPresent()) { + return ResponseDTO.userErrorParam("存在子目录:" + existOptional.get().getName()); + } + + //查询是否有帮助文档 + List helpDocVOList = helpDocDao.queryHelpDocByCatalogId(helpDocCatalogId); + if (CollectionUtils.isNotEmpty(helpDocVOList)) { + return ResponseDTO.userErrorParam("目录下存在文档,不能删除"); + } + helpDocCatalogDao.deleteById(helpDocCatalogId); + return ResponseDTO.ok(); + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/service/HelpDocService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/service/HelpDocService.java new file mode 100644 index 0000000..f641cc0 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/service/HelpDocService.java @@ -0,0 +1,120 @@ +package net.lab1024.sa.base.module.support.helpdoc.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.module.support.helpdoc.dao.HelpDocDao; +import net.lab1024.sa.base.module.support.helpdoc.domain.entity.HelpDocEntity; +import net.lab1024.sa.base.module.support.helpdoc.domain.form.HelpDocAddForm; +import net.lab1024.sa.base.module.support.helpdoc.domain.form.HelpDocQueryForm; +import net.lab1024.sa.base.module.support.helpdoc.domain.form.HelpDocUpdateForm; +import net.lab1024.sa.base.module.support.helpdoc.domain.vo.HelpDocDetailVO; +import net.lab1024.sa.base.module.support.helpdoc.domain.vo.HelpDocVO; +import net.lab1024.sa.base.module.support.helpdoc.manager.HelpDocManager; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/** + * 后台管理业务 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class HelpDocService { + + @Resource + private HelpDocDao helpDocDao; + + @Resource + private HelpDocManager helpDaoManager; + + + /** + * 查询 帮助文档 + * + * @param queryForm + * @return + */ + public PageResult query(HelpDocQueryForm queryForm) { + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List list = helpDocDao.query(page, queryForm); + return SmartPageUtil.convert2PageResult(page, list); + } + + /** + * 添加 + * + * @param addForm + * @return + */ + public ResponseDTO add(HelpDocAddForm addForm) { + HelpDocEntity helpDaoEntity = SmartBeanUtil.copy(addForm, HelpDocEntity.class); + helpDaoManager.save(helpDaoEntity, addForm.getRelationList()); + return ResponseDTO.ok(); + } + + + /** + * 更新 + * + * @param updateForm + * @return + */ + public ResponseDTO update(HelpDocUpdateForm updateForm) { + // 更新 + HelpDocEntity helpDaoEntity = SmartBeanUtil.copy(updateForm, HelpDocEntity.class); + helpDaoManager.update(helpDaoEntity, updateForm.getRelationList()); + return ResponseDTO.ok(); + } + + + /** + * 删除 + * + * @param helpDocId + * @return + */ + @Transactional(rollbackFor = Exception.class) + public ResponseDTO delete(Long helpDocId) { + HelpDocEntity helpDaoEntity = helpDocDao.selectById(helpDocId); + if (helpDaoEntity != null) { + helpDocDao.deleteById(helpDocId); + helpDocDao.deleteRelation(helpDocId); + } + return ResponseDTO.ok(); + } + + /** + * 获取详情 + * + * @param helpDocId + * @return + */ + public HelpDocDetailVO getDetail(Long helpDocId) { + HelpDocEntity helpDaoEntity = helpDocDao.selectById(helpDocId); + HelpDocDetailVO detail = SmartBeanUtil.copy(helpDaoEntity, HelpDocDetailVO.class); + if (detail != null) { + detail.setRelationList(helpDocDao.queryRelationByHelpDoc(helpDocId)); + } + return detail; + } + + /** + * 获取详情 + * + * @param relationId + * @return + */ + public List queryHelpDocByRelationId(Long relationId) { + return helpDocDao.queryHelpDocByRelationId(relationId); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/service/HelpDocUserService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/service/HelpDocUserService.java new file mode 100644 index 0000000..ae9c25b --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/service/HelpDocUserService.java @@ -0,0 +1,85 @@ +package net.lab1024.sa.base.module.support.helpdoc.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.RequestUser; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.module.support.helpdoc.dao.HelpDocDao; +import net.lab1024.sa.base.module.support.helpdoc.domain.entity.HelpDocEntity; +import net.lab1024.sa.base.module.support.helpdoc.domain.form.HelpDocViewRecordQueryForm; +import net.lab1024.sa.base.module.support.helpdoc.domain.vo.HelpDocDetailVO; +import net.lab1024.sa.base.module.support.helpdoc.domain.vo.HelpDocVO; +import net.lab1024.sa.base.module.support.helpdoc.domain.vo.HelpDocViewRecordVO; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 用户查看 帮助文档 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class HelpDocUserService { + + @Resource + private HelpDocDao helpDocDao; + + + /** + * 查询全部 帮助文档 + * + * @return + */ + public ResponseDTO> queryAllHelpDocList() { + return ResponseDTO.ok(helpDocDao.queryAllHelpDocList()); + } + + + /** + * 查询我的 待查看的 帮助文档清单 + * + * @return + */ + public ResponseDTO view(RequestUser requestUser, Long helpDocId) { + HelpDocEntity helpDocEntity = helpDocDao.selectById(helpDocId); + if (helpDocEntity == null) { + return ResponseDTO.userErrorParam("帮助文档不存在"); + } + + HelpDocDetailVO helpDocDetailVO = SmartBeanUtil.copy(helpDocEntity, HelpDocDetailVO.class); + long viewCount = helpDocDao.viewRecordCount(helpDocId, requestUser.getUserId()); + if (viewCount == 0) { + helpDocDao.insertViewRecord(helpDocId, requestUser.getUserId(), requestUser.getUserName(), requestUser.getIp(), requestUser.getUserAgent(), 1); + helpDocDao.updateViewCount(helpDocId, 1, 1); + helpDocDetailVO.setPageViewCount(helpDocDetailVO.getPageViewCount() + 1); + helpDocDetailVO.setUserViewCount(helpDocDetailVO.getUserViewCount() + 1); + } else { + helpDocDao.updateViewRecord(helpDocId, requestUser.getUserId(), requestUser.getIp(), requestUser.getUserAgent()); + helpDocDao.updateViewCount(helpDocId, 0, 1); + helpDocDetailVO.setPageViewCount(helpDocDetailVO.getPageViewCount() + 1); + } + + return ResponseDTO.ok(helpDocDetailVO); + } + + + /** + * 分页查询 查看记录 + * + * @param helpDocViewRecordQueryForm + * @return + */ + public PageResult queryViewRecord(HelpDocViewRecordQueryForm helpDocViewRecordQueryForm) { + Page page = SmartPageUtil.convert2PageQuery(helpDocViewRecordQueryForm); + List noticeViewRecordVOS = helpDocDao.queryViewRecordList(page, helpDocViewRecordQueryForm); + return SmartPageUtil.convert2PageResult(page, noticeViewRecordVOS); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/SmartJobClientManager.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/SmartJobClientManager.java new file mode 100644 index 0000000..6c61dd2 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/SmartJobClientManager.java @@ -0,0 +1,164 @@ +package net.lab1024.sa.base.module.support.job.api; + +import cn.hutool.core.util.IdUtil; +import com.google.common.collect.Lists; +import jakarta.annotation.PreDestroy; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.module.support.job.api.domain.SmartJobMsg; +import net.lab1024.sa.base.module.support.job.config.SmartJobAutoConfiguration; +import net.lab1024.sa.base.module.support.job.core.SmartJob; +import net.lab1024.sa.base.module.support.job.core.SmartJobExecutor; +import net.lab1024.sa.base.module.support.job.core.SmartJobLauncher; +import net.lab1024.sa.base.module.support.job.repository.SmartJobRepository; +import net.lab1024.sa.base.module.support.job.repository.domain.SmartJobEntity; +import org.redisson.api.RLock; +import org.redisson.api.RTopic; +import org.redisson.api.RedissonClient; +import org.redisson.api.listener.MessageListener; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +/** + * smart job 执行端管理 + * 分布式系统之间 用发布/订阅消息的形式 来管理多个job + * + * @author huke + * @date 2024/6/22 20:31 + */ +@ConditionalOnBean(SmartJobAutoConfiguration.class) +@Slf4j +@Service +public class SmartJobClientManager { + + private final SmartJobLauncher jobLauncher; + + private final SmartJobRepository jobRepository; + + private final List jobInterfaceList; + + private static final String EXECUTE_LOCK = "smart-job-lock-msg-execute-"; + + private static final String TOPIC = "smart-job-instance"; + + private final RedissonClient redissonClient; + + private final RTopic topic; + + private final SmartJobMsgListener jobMsgListener; + + public SmartJobClientManager(SmartJobLauncher jobLauncher, + SmartJobRepository jobRepository, + List jobInterfaceList, + RedissonClient redissonClient) { + this.jobLauncher = jobLauncher; + this.jobRepository = jobRepository; + this.jobInterfaceList = jobInterfaceList; + this.redissonClient = redissonClient; + + // 添加监听器 + this.topic = redissonClient.getTopic(TOPIC); + this.jobMsgListener = new SmartJobMsgListener(); + topic.addListener(SmartJobMsg.class, jobMsgListener); + log.info("==== SmartJob ==== client-manager init"); + } + + /** + * 发布消息 + */ + public void publishToClient(SmartJobMsg msgDTO) { + msgDTO.setMsgId(IdUtil.fastSimpleUUID()); + topic.publish(msgDTO); + } + + /** + * 处理消息 + */ + private class SmartJobMsgListener implements MessageListener { + + @Override + public void onMessage(CharSequence channel, SmartJobMsg msg) { + log.info("==== SmartJob ==== on-message :{}", msg); + // 判断消息类型 业务简单就直接判断 复杂的话可以策略模式 + SmartJobMsg.MsgTypeEnum msgType = msg.getMsgType(); + // 更新任务 + if (SmartJobMsg.MsgTypeEnum.UPDATE_JOB == msgType) { + updateJob(msg.getJobId()); + } + // 执行任务 + if (SmartJobMsg.MsgTypeEnum.EXECUTE_JOB == msgType) { + executeJob(msg); + } + } + } + + /** + * 获取任务执行类 + * + * @param jobClass + * @return + */ + private Optional queryJobImpl(String jobClass) { + return jobInterfaceList.stream().filter(e -> Objects.equals(e.getClassName(), jobClass)).findFirst(); + } + + /** + * 更新任务 + * + * @param jobId + */ + private void updateJob(Integer jobId) { + SmartJobEntity jobEntity = jobRepository.getJobDao().selectById(jobId); + if (null == jobEntity) { + return; + } + jobLauncher.startOrRefreshJob(Lists.newArrayList(jobEntity)); + } + + /** + * 立即执行任务 + * + * @param msg + */ + private void executeJob(SmartJobMsg msg) { + Integer jobId = msg.getJobId(); + SmartJobEntity jobEntity = jobRepository.getJobDao().selectById(jobId); + if (null == jobEntity) { + return; + } + // 获取定时任务实现类 + Optional optional = this.queryJobImpl(jobEntity.getJobClass()); + if (!optional.isPresent()) { + return; + } + + // 获取执行锁 无需主动释放 + RLock rLock = redissonClient.getLock(EXECUTE_LOCK + msg.getMsgId()); + try { + boolean getLock = rLock.tryLock(0, 20, TimeUnit.SECONDS); + if (!getLock) { + return; + } + } catch (InterruptedException e) { + log.error("==== SmartJob ==== msg execute err:", e); + return; + } + + // 通过执行器 执行任务 + jobEntity.setParam(msg.getParam()); + SmartJobExecutor jobExecutor = new SmartJobExecutor(jobEntity, jobRepository, optional.get(), redissonClient); + jobExecutor.execute(msg.getUpdateName()); + } + + + @PreDestroy + public void destroy() { + topic.removeListener(jobMsgListener); + } + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/SmartJobService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/SmartJobService.java new file mode 100644 index 0000000..836f42f --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/SmartJobService.java @@ -0,0 +1,296 @@ +package net.lab1024.sa.base.module.support.job.api; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.google.common.collect.Lists; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.code.UserErrorCode; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.RequestUser; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.module.support.job.api.domain.*; +import net.lab1024.sa.base.module.support.job.config.SmartJobAutoConfiguration; +import net.lab1024.sa.base.module.support.job.constant.SmartJobTriggerTypeEnum; +import net.lab1024.sa.base.module.support.job.constant.SmartJobUtil; +import net.lab1024.sa.base.module.support.job.repository.SmartJobDao; +import net.lab1024.sa.base.module.support.job.repository.SmartJobLogDao; +import net.lab1024.sa.base.module.support.job.repository.domain.SmartJobEntity; +import net.lab1024.sa.base.module.support.job.repository.domain.SmartJobLogEntity; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * 定时任务 接口业务管理 + * 如果不需要通过接口管理定时任务 可以删除此类 + * + * @author huke + * @date 2024/6/17 20:41 + */ +@ConditionalOnBean(SmartJobAutoConfiguration.class) +@Service +public class SmartJobService { + + @Resource + private SmartJobDao jobDao; + + @Resource + private SmartJobLogDao jobLogDao; + + @Resource + private SmartJobClientManager jobClientManager; + + /** + * 查询 定时任务详情 + * + * @param jobId + * @return + */ + public ResponseDTO queryJobInfo(Integer jobId) { + SmartJobEntity jobEntity = jobDao.selectById(jobId); + if (null == jobEntity) { + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST); + } + SmartJobVO jobVO = SmartBeanUtil.copy(jobEntity, SmartJobVO.class); + // 处理设置job详情 + this.handleJobInfo(Lists.newArrayList(jobVO)); + return ResponseDTO.ok(jobVO); + } + + /** + * 分页查询 定时任务 + * + * @param queryForm + * @return + */ + public ResponseDTO> queryJob(SmartJobQueryForm queryForm) { + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List jobList = jobDao.query(page, queryForm); + PageResult pageResult = SmartPageUtil.convert2PageResult(page, jobList); + // 处理设置job详情 + this.handleJobInfo(jobList); + return ResponseDTO.ok(pageResult); + } + + /** + * 处理设置 任务信息 + * + * @param jobList + */ + private void handleJobInfo(List jobList) { + if (CollectionUtils.isEmpty(jobList)) { + return; + } + // 查询最后一次执行记录 + List logIdList = jobList.stream().map(SmartJobVO::getLastExecuteLogId).filter(Objects::nonNull).collect(Collectors.toList()); + Map lastLogMap = Collections.emptyMap(); + if (CollectionUtils.isNotEmpty(logIdList)) { + lastLogMap = jobLogDao.selectBatchIds(logIdList) + .stream() + .collect(Collectors.toMap(SmartJobLogEntity::getLogId, e -> SmartBeanUtil.copy(e, SmartJobLogVO.class))); + } + + // 循环处理任务信息 + for (SmartJobVO jobVO : jobList) { + // 设置最后一次执行记录 + Long lastExecuteLogId = jobVO.getLastExecuteLogId(); + if (null != lastExecuteLogId) { + jobVO.setLastJobLog(lastLogMap.get(lastExecuteLogId)); + } + // 计算未来5次执行时间 + if (jobVO.getEnabledFlag()) { + List nextTimeList = SmartJobUtil.queryNextTimeFromNow(jobVO.getTriggerType(), jobVO.getTriggerValue(), jobVO.getLastExecuteTime(), 5); + jobVO.setNextJobExecuteTimeList(nextTimeList); + } + } + } + + /** + * 分页查询 定时任务-执行记录 + * + * @param queryForm + * @return + */ + public ResponseDTO> queryJobLog(SmartJobLogQueryForm queryForm) { + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List jobList = jobLogDao.query(page, queryForm); + PageResult pageResult = SmartPageUtil.convert2PageResult(page, jobList); + return ResponseDTO.ok(pageResult); + } + + /** + * 添加定时任务 + * + * @param addForm + * @return + */ + public synchronized ResponseDTO addJob(SmartJobAddForm addForm) { + // 校验参数 + ResponseDTO checkRes = this.checkParam(addForm); + if (!checkRes.getOk()) { + return checkRes; + } + + // 校验重复的执行类 + SmartJobEntity existJobClass = jobDao.selectByJobClass(addForm.getJobClass()); + if (null != existJobClass && !existJobClass.getDeletedFlag()) { + return ResponseDTO.userErrorParam("已经存在相同的执行类"); + } + + // 添加数据 + SmartJobEntity jobEntity = SmartBeanUtil.copy(addForm, SmartJobEntity.class); + jobDao.insert(jobEntity); + + // 更新执行端 + SmartJobMsg jobMsg = new SmartJobMsg(); + jobMsg.setJobId(jobEntity.getJobId()); + jobMsg.setMsgType(SmartJobMsg.MsgTypeEnum.UPDATE_JOB); + jobMsg.setUpdateName(addForm.getUpdateName()); + jobClientManager.publishToClient(jobMsg); + return ResponseDTO.ok(); + } + + /** + * 更新定时任务 + * + * @param updateForm + * @return + */ + public synchronized ResponseDTO updateJob(SmartJobUpdateForm updateForm) { + // 校验参数 + Integer jobId = updateForm.getJobId(); + SmartJobEntity jobEntity = jobDao.selectById(jobId); + if (null == jobEntity) { + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST); + } + + ResponseDTO checkRes = this.checkParam(updateForm); + if (!checkRes.getOk()) { + return checkRes; + } + + // 校验重复的执行类 + SmartJobEntity existJobClass = jobDao.selectByJobClass(updateForm.getJobClass()); + if (null != existJobClass && !existJobClass.getDeletedFlag() && !existJobClass.getJobId().equals(jobId)) { + return ResponseDTO.userErrorParam("已经存在相同的执行类"); + } + + // 更新数据 + jobEntity = SmartBeanUtil.copy(updateForm, SmartJobEntity.class); + jobDao.updateById(jobEntity); + + // 更新执行端 + SmartJobMsg jobMsg = new SmartJobMsg(); + jobMsg.setJobId(jobId); + jobMsg.setMsgType(SmartJobMsg.MsgTypeEnum.UPDATE_JOB); + jobMsg.setUpdateName(updateForm.getUpdateName()); + jobClientManager.publishToClient(jobMsg); + return ResponseDTO.ok(); + } + + /** + * 校验参数 + * 如需其他校验,请自行添加校验逻辑 + * + * @param addForm + * @return + */ + private ResponseDTO checkParam(SmartJobAddForm addForm) { + // 校验触发时间配置 + String triggerType = addForm.getTriggerType(); + String triggerValue = addForm.getTriggerValue(); + if (SmartJobTriggerTypeEnum.CRON.equalsValue(triggerType) && !SmartJobUtil.checkCron(triggerValue)) { + return ResponseDTO.userErrorParam("cron表达式错误"); + } + if (SmartJobTriggerTypeEnum.FIXED_DELAY.equalsValue(triggerType) && !SmartJobUtil.checkFixedDelay(triggerValue)) { + return ResponseDTO.userErrorParam("固定间隔配置错误:必须是大于0的整数"); + } + // 校验job class + return SmartJobUtil.checkJobClass(addForm.getJobClass()); + } + + /** + * 更新定时任务-是否开启 + * + * @param updateForm + * @return + */ + public ResponseDTO updateJobEnabled(SmartJobEnabledUpdateForm updateForm) { + Integer jobId = updateForm.getJobId(); + SmartJobEntity jobEntity = jobDao.selectById(jobId); + if (null == jobEntity) { + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST); + } + Boolean enabledFlag = updateForm.getEnabledFlag(); + if (Objects.equals(enabledFlag, jobEntity.getEnabledFlag())) { + return ResponseDTO.ok(); + } + // 更新数据 + jobEntity = new SmartJobEntity(); + jobEntity.setJobId(jobId); + jobEntity.setEnabledFlag(enabledFlag); + jobEntity.setUpdateName(updateForm.getUpdateName()); + jobDao.updateById(jobEntity); + + // 更新执行端 + SmartJobMsg jobMsg = new SmartJobMsg(); + jobMsg.setJobId(jobId); + jobMsg.setMsgType(SmartJobMsg.MsgTypeEnum.UPDATE_JOB); + jobMsg.setUpdateName(updateForm.getUpdateName()); + jobClientManager.publishToClient(jobMsg); + return ResponseDTO.ok(); + } + + /** + * 执行定时任务 + * 忽略任务的开启状态,立即执行一次 + * + * @param executeForm + * @return + */ + public ResponseDTO execute(SmartJobExecuteForm executeForm) { + Integer jobId = executeForm.getJobId(); + SmartJobEntity jobEntity = jobDao.selectById(jobId); + if (null == jobEntity) { + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST); + } + + // 更新执行端 + SmartJobMsg jobMsg = new SmartJobMsg(); + jobMsg.setJobId(jobId); + jobMsg.setParam(executeForm.getParam()); + jobMsg.setMsgType(SmartJobMsg.MsgTypeEnum.EXECUTE_JOB); + jobMsg.setUpdateName(executeForm.getUpdateName()); + jobClientManager.publishToClient(jobMsg); + return ResponseDTO.ok(); + } + + /** + * 移除定时任务 + * 物理删除 + * + * @return + * @author huke + */ + public synchronized ResponseDTO deleteJob(Integer jobId, RequestUser requestUser) { + // 删除任务 + jobDao.updateDeletedFlag(jobId, Boolean.TRUE); + + // 更新执行端 + SmartJobMsg jobMsg = new SmartJobMsg(); + jobMsg.setJobId(jobId); + jobMsg.setMsgType(SmartJobMsg.MsgTypeEnum.UPDATE_JOB); + jobMsg.setUpdateName(requestUser.getUserName()); + jobClientManager.publishToClient(jobMsg); + return ResponseDTO.ok(); + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobAddForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobAddForm.java new file mode 100644 index 0000000..0ca70f1 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobAddForm.java @@ -0,0 +1,58 @@ +package net.lab1024.sa.base.module.support.job.api.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; +import net.lab1024.sa.base.module.support.job.constant.SmartJobTriggerTypeEnum; +import org.hibernate.validator.constraints.Length; + +/** + * 定时任务 添加 + * + * @author huke + * @date 2024/12/19 19:30 + */ +@Data +public class SmartJobAddForm { + + @Schema(description = "任务名称") + @NotBlank(message = "任务名称不能为空") + @Length(max = 100, message = "任务名称最多100字符") + private String jobName; + + @Schema(description = "任务执行类") + @NotBlank(message = "任务执行类不能为空") + @Length(max = 200, message = "任务执行类最多200字符") + private String jobClass; + + @SchemaEnum(desc = "触发类型", value = SmartJobTriggerTypeEnum.class) + @CheckEnum(value = SmartJobTriggerTypeEnum.class, required = true, message = "触发类型错误") + private String triggerType; + + @Schema(description = "触发配置") + @NotBlank(message = "触发配置不能为空") + @Length(max = 100, message = "触发配置最多100字符") + private String triggerValue; + + @Schema(description = "定时任务参数|可选") + @Length(max = 1000, message = "定时任务参数最多1000字符") + private String param; + + @Schema(description = "是否开启") + @NotNull(message = "是否开启不能为空") + private Boolean enabledFlag; + + @Schema(description = "备注") + @Length(max = 250, message = "任务备注最多250字符") + private String remark; + + @NotNull(message = "排序不能为空") + @Schema(description = "排序") + private Integer sort; + + @Schema(hidden = true) + private String updateName; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobEnabledUpdateForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobEnabledUpdateForm.java new file mode 100644 index 0000000..11de070 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobEnabledUpdateForm.java @@ -0,0 +1,26 @@ +package net.lab1024.sa.base.module.support.job.api.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 定时任务-更新-开启状态 + * + * @author huke + * @date 2024/6/17 21:30 + */ +@Data +public class SmartJobEnabledUpdateForm { + + @Schema(description = "任务id") + @NotNull(message = "任务id不能为空") + private Integer jobId; + + @Schema(description = "是否启用") + @NotNull(message = "是否启用不能为空") + private Boolean enabledFlag; + + @Schema(hidden = true) + private String updateName; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobExecuteForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobExecuteForm.java new file mode 100644 index 0000000..81f4cb2 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobExecuteForm.java @@ -0,0 +1,27 @@ +package net.lab1024.sa.base.module.support.job.api.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +/** + * 定时任务-手动执行 + * + * @author huke + * @date 2024/6/18 20:30 + */ +@Data +public class SmartJobExecuteForm { + + @Schema(description = "任务id") + @NotNull(message = "任务id不能为空") + private Integer jobId; + + @Schema(description = "定时任务参数|可选") + @Length(max = 2000, message = "定时任务参数最多2000字符") + private String param; + + @Schema(hidden = true) + private String updateName; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobLogQueryForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobLogQueryForm.java new file mode 100644 index 0000000..f405e7d --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobLogQueryForm.java @@ -0,0 +1,34 @@ +package net.lab1024.sa.base.module.support.job.api.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; +import org.hibernate.validator.constraints.Length; + +import java.time.LocalDate; + +/** + * 定时任务-执行记录 分页查询 + * + * @author huke + * @date 2024/6/17 20:50 + */ +@Data +public class SmartJobLogQueryForm extends PageParam { + + @Schema(description = "搜索词|可选") + @Length(max = 50, message = "搜索词最多50字符") + private String searchWord; + + @Schema(description = "任务id|可选") + private Integer jobId; + + @Schema(description = "是否成功|可选") + private Boolean successFlag; + + @Schema(description = "开始时间|可选", example = "2024-06-06") + private LocalDate startTime; + + @Schema(description = "截止时间|可选", example = "2025-10-15") + private LocalDate endTime; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobLogVO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobLogVO.java new file mode 100644 index 0000000..dc60cb7 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobLogVO.java @@ -0,0 +1,56 @@ +package net.lab1024.sa.base.module.support.job.api.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 定时任务-执行记录 vo + * + * @author huke + * @date 2024/6/17 21:30 + */ +@Data +public class SmartJobLogVO { + + @Schema(description = "logId") + private Long logId; + + @Schema(description = "任务id") + private Integer jobId; + + @Schema(description = "任务名称") + private String jobName; + + @Schema(description = "定时任务参数|可选") + private String param; + + @Schema(description = "执行结果是否成功") + private Boolean successFlag; + + @Schema(description = "开始执行时间") + private LocalDateTime executeStartTime; + + @Schema(description = "执行时长-毫秒") + private Long executeTimeMillis; + + @Schema(description = "执行结果描述") + private String executeResult; + + @Schema(description = "执行结束时间") + private LocalDateTime executeEndTime; + + @Schema(description = "ip") + private String ip; + + @Schema(description = "进程id") + private String processId; + + @Schema(description = "程序目录") + private String programPath; + + private String createName; + + private LocalDateTime createTime; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobMsg.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobMsg.java new file mode 100644 index 0000000..c7cba95 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobMsg.java @@ -0,0 +1,59 @@ +package net.lab1024.sa.base.module.support.job.api.domain; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Getter; +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 定时任务 发布/订阅消息对象 + * + * @author huke + * @date 2024/6/20 21:10 + */ +@Data +public class SmartJobMsg { + + /** + * 消息id 无需设置 + */ + private String msgId; + + /** + * 任务id + */ + private Integer jobId; + + /** + * 任务参数 + */ + private String param; + + /** + * 消息类型 + */ + private MsgTypeEnum msgType; + + /** + * 更新人 + */ + private String updateName; + + @Getter + @AllArgsConstructor + public enum MsgTypeEnum implements BaseEnum { + + /** + * 1 更新任务 + */ + UPDATE_JOB(1, "更新任务"), + + EXECUTE_JOB(2, "执行任务"), + + ; + + private final Integer value; + + private final String desc; + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobQueryForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobQueryForm.java new file mode 100644 index 0000000..39a56c7 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobQueryForm.java @@ -0,0 +1,33 @@ +package net.lab1024.sa.base.module.support.job.api.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; +import net.lab1024.sa.base.module.support.job.constant.SmartJobTriggerTypeEnum; +import org.hibernate.validator.constraints.Length; + +/** + * 定时任务 分页查询 + * + * @author huke + * @date 2024/6/17 20:50 + */ +@Data +public class SmartJobQueryForm extends PageParam { + + @Schema(description = "搜索词|可选") + @Length(max = 50, message = "搜索词最多50字符") + private String searchWord; + + @SchemaEnum(desc = "触发类型", value = SmartJobTriggerTypeEnum.class) + @CheckEnum(value = SmartJobTriggerTypeEnum.class, message = "触发类型错误") + private String triggerType; + + @Schema(description = "是否启用|可选") + private Boolean enabledFlag; + + @Schema(description = "是否删除|可选") + private Boolean deletedFlag; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobUpdateForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobUpdateForm.java new file mode 100644 index 0000000..860b4a9 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobUpdateForm.java @@ -0,0 +1,24 @@ +package net.lab1024.sa.base.module.support.job.api.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; +import net.lab1024.sa.base.module.support.job.constant.SmartJobTriggerTypeEnum; +import org.hibernate.validator.constraints.Length; + +/** + * 定时任务 更新 + * + * @author huke + * @date 2024/6/17 21:30 + */ +@Data +public class SmartJobUpdateForm extends SmartJobAddForm { + + @Schema(description = "任务id") + @NotNull(message = "任务id不能为空") + private Integer jobId; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobVO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobVO.java new file mode 100644 index 0000000..d83d98b --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobVO.java @@ -0,0 +1,66 @@ +package net.lab1024.sa.base.module.support.job.api.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.json.serializer.enumeration.EnumSerialize; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.module.support.job.constant.SmartJobTriggerTypeEnum; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 定时任务 vo + * + * @author huke + * @date 2024/6/17 21:30 + */ +@Data +public class SmartJobVO { + + @Schema(description = "任务id") + private Integer jobId; + + @Schema(description = "任务名称") + private String jobName; + + @Schema(description = "执行类") + private String jobClass; + + @SchemaEnum(desc = "触发类型", value = SmartJobTriggerTypeEnum.class) + @EnumSerialize(SmartJobTriggerTypeEnum.class) + private String triggerType; + + @Schema(description = "触发配置") + private String triggerValue; + + @Schema(description = "定时任务参数|可选") + private String param; + + @Schema(description = "是否启用") + private Boolean enabledFlag; + + @Schema(description = "最后一执行时间") + private LocalDateTime lastExecuteTime; + + @Schema(description = "最后一次执行记录id") + private Long lastExecuteLogId; + + @Schema(description = "备注") + private String remark; + + @Schema(description = "排序") + private Integer sort; + + private String updateName; + + private LocalDateTime updateTime; + + private LocalDateTime createTime; + + @Schema(description = "上次执行记录") + private SmartJobLogVO lastJobLog; + + @Schema(description = "未来N次任务执行时间") + private List nextJobExecuteTimeList; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/config/SmartJobAutoConfiguration.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/config/SmartJobAutoConfiguration.java new file mode 100644 index 0000000..25ad872 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/config/SmartJobAutoConfiguration.java @@ -0,0 +1,52 @@ +package net.lab1024.sa.base.module.support.job.config; + +import net.lab1024.sa.base.module.support.job.core.SmartJob; +import net.lab1024.sa.base.module.support.job.core.SmartJobLauncher; +import net.lab1024.sa.base.module.support.job.repository.SmartJobRepository; +import org.redisson.api.RedissonClient; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +/** + * 定时任务 配置 + * + * @author huke + * @date 2024/6/17 21:30 + */ +@Configuration +@EnableConfigurationProperties(SmartJobConfig.class) +@ConditionalOnProperty( + prefix = SmartJobConfig.CONFIG_PREFIX, + name = "enabled", + havingValue = "true" +) +public class SmartJobAutoConfiguration { + + private final SmartJobConfig jobConfig; + + private final SmartJobRepository jobRepository; + + private final List jobInterfaceList; + + public SmartJobAutoConfiguration(SmartJobConfig jobConfig, + SmartJobRepository jobRepository, + List jobInterfaceList) { + this.jobConfig = jobConfig; + this.jobRepository = jobRepository; + this.jobInterfaceList = jobInterfaceList; + } + + /** + * 定时任务启动器 + * + * @return + */ + @Bean + public SmartJobLauncher initJobLauncher(RedissonClient redissonClient) { + return new SmartJobLauncher(jobConfig, jobRepository, jobInterfaceList, redissonClient); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/config/SmartJobConfig.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/config/SmartJobConfig.java new file mode 100644 index 0000000..d2bee0d --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/config/SmartJobConfig.java @@ -0,0 +1,39 @@ +package net.lab1024.sa.base.module.support.job.config; + + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * smart job 配置 + * 与配置文件参数对应 + * + * @author huke + * @date 2024/6/17 21:30 + */ +@ConfigurationProperties(prefix = SmartJobConfig.CONFIG_PREFIX) +@Data +public class SmartJobConfig { + + public static final String CONFIG_PREFIX = "smart.job"; + + /** + * 任务执行核心线程数 偶数 默认2 + */ + private Integer corePoolSize = 2; + + /** + * 任务延迟初始化 默认30秒 + */ + private Integer initDelay = 30; + + /** + * 数据库配置检测-开关 默认开启 + */ + private Boolean dbRefreshEnabled = true; + + /** + * 数据库配置检测-执行间隔 默认120秒 + */ + private Integer dbRefreshInterval = 120; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/constant/SmartJobConst.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/constant/SmartJobConst.java new file mode 100644 index 0000000..4409ad3 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/constant/SmartJobConst.java @@ -0,0 +1,22 @@ +package net.lab1024.sa.base.module.support.job.constant; + +/** + * smart job 常量 + * + * @author huke + * @date 2024/6/19 20:25 + */ +public class SmartJobConst { + + public static final String SYSTEM_NAME = "system"; + + public static final String LOGO = " _____ __ __ __ \n" + + " / ___/____ ___ ____ ______/ /_ / /___ / /_ \n" + + " \\__ \\/ __ `__ \\/ __ `/ ___/ __/ __ / / __ \\/ __ \\\n" + + " ___/ / / / / / / /_/ / / / /_ / /_/ / /_/ / /_/ /\n" + + "/____/_/ /_/ /_/\\__,_/_/ \\__/ \\____/\\____/_.___/ \n" + + "-->任务执行线程池:%s\n" + + "-->任务初始化延迟:%s秒\n" + + "-->数据库配置检测:%s\n\n"; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/constant/SmartJobTriggerTypeEnum.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/constant/SmartJobTriggerTypeEnum.java new file mode 100644 index 0000000..c2368c6 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/constant/SmartJobTriggerTypeEnum.java @@ -0,0 +1,30 @@ +package net.lab1024.sa.base.module.support.job.constant; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * job 任务触发类型 枚举类 + * + * @author huke + * @date 2024年6月29日 + **/ +@AllArgsConstructor +@Getter +public enum SmartJobTriggerTypeEnum implements BaseEnum { + + /** + * 1 cron表达式 + */ + CRON("cron", "cron表达式"), + + FIXED_DELAY("fixed_delay", "固定间隔"), + + ; + + private final String value; + + private final String desc; +} + diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/constant/SmartJobUtil.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/constant/SmartJobUtil.java new file mode 100644 index 0000000..ac21b3d --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/constant/SmartJobUtil.java @@ -0,0 +1,210 @@ +package net.lab1024.sa.base.module.support.job.constant; + +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.module.support.job.core.SmartJob; +import org.springframework.scheduling.support.CronExpression; + +import java.lang.management.ManagementFactory; +import java.lang.management.RuntimeMXBean; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * smart job util + * + * @author huke + * @date 2024/6/18 20:00 + */ +public class SmartJobUtil { + + private SmartJobUtil() { + } + + /** + * 校验cron表达式 是否合法 + * + * @param cron + * @return + */ + public static boolean checkCron(String cron) { + return CronExpression.isValidExpression(cron); + } + + /** + * 校验固定间隔 值是否合法 + * + * @param val + * @return + */ + public static boolean checkFixedDelay(String val) { + int intVal; + try { + intVal = Integer.parseInt(val); + } catch (NumberFormatException e) { + return false; + } + return intVal > 0; + } + + /** + * 打印一些展示信息到控制台 + * 环保绿 + * + * @param info + */ + public static void printInfo(String info) { + System.out.printf("\033[32;1m %s \033[0m", info); + } + + /** + * 查询未来N次执行时间 从最后一次时间时间 开始计算 + * + * @param triggerType + * @param triggerVal + * @param lastExecuteTime + * @param num + * @return + */ + public static List queryNextTimeFromLast(String triggerType, + String triggerVal, + LocalDateTime lastExecuteTime, + int num) { + List nextTimeList = null; + if (SmartJobTriggerTypeEnum.CRON.equalsValue(triggerType)) { + nextTimeList = SmartJobUtil.queryNextTime(triggerVal, lastExecuteTime, num); + } else if (SmartJobTriggerTypeEnum.FIXED_DELAY.equalsValue(triggerType)) { + nextTimeList = SmartJobUtil.queryNextTime(getFixedDelayVal(triggerVal), lastExecuteTime, num); + } + return nextTimeList; + } + + /** + * 查询未来N次执行时间 从当前时间 开始计算 + * + * @param triggerType + * @param triggerVal + * @param lastExecuteTime + * @param num + * @return + */ + public static List queryNextTimeFromNow(String triggerType, + String triggerVal, + LocalDateTime lastExecuteTime, + int num) { + LocalDateTime nowTime = LocalDateTime.now(); + List nextTimeList = null; + if (SmartJobTriggerTypeEnum.CRON.equalsValue(triggerType)) { + nextTimeList = SmartJobUtil.queryNextTime(triggerVal, nowTime, num); + } else if (SmartJobTriggerTypeEnum.FIXED_DELAY.equalsValue(triggerType)) { + Integer fixedDelay = getFixedDelayVal(triggerVal); + LocalDateTime startTime = null == lastExecuteTime || lastExecuteTime.plusSeconds(fixedDelay).isBefore(nowTime) + ? nowTime : lastExecuteTime; + nextTimeList = SmartJobUtil.queryNextTime(fixedDelay, startTime, num); + } + return nextTimeList; + } + + /** + * 根据cron表达式 计算N次执行时间 + * + * @param cron + * @param startTime + * @param num + * @return + */ + public static List queryNextTime(String cron, LocalDateTime startTime, int num) { + if (null == startTime) { + return Collections.emptyList(); + } + CronExpression parse = CronExpression.parse(cron); + List timeList = new ArrayList<>(num); + for (int i = 0; i < num; i++) { + startTime = parse.next(startTime); + timeList.add(startTime); + } + return timeList; + } + + /** + * 根据 固定间隔 计算N次执行时间 + * + * @param fixDelaySecond + * @param startTime + * @param num + * @return + */ + public static List queryNextTime(Integer fixDelaySecond, LocalDateTime startTime, int num) { + if (null == startTime) { + return Collections.emptyList(); + } + List timeList = new ArrayList<>(num); + for (int i = 0; i < num; i++) { + startTime = startTime.plusSeconds(fixDelaySecond); + timeList.add(startTime); + } + return timeList; + } + + /** + * 获取固定间隔时间 + * + * @param val + * @return + */ + public static Integer getFixedDelayVal(String val) { + return Integer.parseInt(val); + } + + /** + * 获取当前 Java 应用程序的工作目录 + * + * @return + */ + public static String getProgramPath() { + return System.getProperty("user.dir"); + } + + /** + * 获取当前 Java 应用程序的进程id + * + * @return + */ + public static String getProcessId() { + RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean(); + return runtime.getName().split("@")[0]; + } + + + /** + * 根据className 判断job class + */ + public static ResponseDTO checkJobClass(String className) { + try { + Class aClass = Class.forName(className); + // 判断是否实现了 SmartJob + if (!SmartJob.class.isAssignableFrom(aClass)) { + return ResponseDTO.userErrorParam(className + " 执行类没有实现 SmartJob 接口"); + } + } catch (ClassNotFoundException e) { + return ResponseDTO.userErrorParam("没有在代码中发现执行类:" + className); + } + return ResponseDTO.ok(); + } + + + public static void main(String[] args) { + LocalDateTime startTime = LocalDateTime.now(); + List timeList = SmartJobUtil.queryNextTime("5 * * * * *", startTime, 3); + System.out.println(timeList); + + timeList = SmartJobUtil.queryNextTime(10, startTime, 3); + System.out.println(timeList); + + System.out.println("project path ->" + getProgramPath()); + System.out.println("project process id ->" + getProcessId()); + ResponseDTO res = checkJobClass("net.lab1024.sa.base.module.support.job.sample.SmartJobSample1"); + System.out.println(res.getMsg()); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/core/SmartJob.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/core/SmartJob.java new file mode 100644 index 0000000..7549642 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/core/SmartJob.java @@ -0,0 +1,28 @@ +package net.lab1024.sa.base.module.support.job.core; + +/** + * 定时任务 执行接口 + * + * @author huke + * @date 2024/6/17 21:30 + */ +public interface SmartJob { + + /** + * 默认方法 + * 获取当前任务类名 + * + * @return + */ + default String getClassName() { + return this.getClass().getName(); + } + + /** + * 执行定时任务 + * + * @param param 可选参数 任务不需要时不用管 + * @return 可null, 自行组织语言描述执行结果,例如:本次处理数据N条 等 + */ + String run(String param); +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/core/SmartJobExecutor.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/core/SmartJobExecutor.java new file mode 100644 index 0000000..721410a --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/core/SmartJobExecutor.java @@ -0,0 +1,168 @@ +package net.lab1024.sa.base.module.support.job.core; + +import cn.hutool.core.exceptions.ExceptionUtil; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.util.SmartIpUtil; +import net.lab1024.sa.base.module.support.job.constant.SmartJobConst; +import net.lab1024.sa.base.module.support.job.constant.SmartJobUtil; +import net.lab1024.sa.base.module.support.job.repository.SmartJobRepository; +import net.lab1024.sa.base.module.support.job.repository.domain.SmartJobEntity; +import net.lab1024.sa.base.module.support.job.repository.domain.SmartJobLogEntity; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.springframework.util.StopWatch; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.concurrent.TimeUnit; + +/** + * 定时任务 执行器 + * + * @author huke + * @date 2024/6/17 21:30 + */ +@Slf4j +public class SmartJobExecutor implements Runnable { + + private final SmartJobEntity jobEntity; + + private final SmartJobRepository jobRepository; + + private final SmartJob jobInterface; + + private final RedissonClient redissonClient; + + private static final String EXECUTE_LOCK = "smart-job-lock-execute-"; + + public SmartJobExecutor(SmartJobEntity jobEntity, + SmartJobRepository jobRepository, + SmartJob jobInterface, + RedissonClient redissonClient) { + this.jobEntity = jobEntity; + this.jobRepository = jobRepository; + this.jobInterface = jobInterface; + this.redissonClient = redissonClient; + } + + /** + * 系统线程执行 + */ + @Override + public void run() { + // 获取当前任务执行锁 最多持有30s自动释放 + Integer jobId = jobEntity.getJobId(); + RLock rLock = redissonClient.getLock(EXECUTE_LOCK + jobId); + try { + boolean lock = rLock.tryLock(0, 30, TimeUnit.SECONDS); + if (!lock) { + return; + } + // 查询上次执行时间 校验执行间隔 + SmartJobEntity dbJobEntity = jobRepository.getJobDao().selectById(jobId); + if (null == dbJobEntity) { + return; + } + LocalDateTime lastExecuteTime = dbJobEntity.getLastExecuteTime(); + if (null != lastExecuteTime) { + LocalDateTime nextTime = SmartJobUtil.queryNextTimeFromLast(jobEntity.getTriggerType(), jobEntity.getTriggerValue(), lastExecuteTime, 1).get(0); + if (LocalDateTime.now().isBefore(nextTime)) { + return; + } + } + // 执行任务 + SmartJobLogEntity logEntity = this.execute(SmartJobConst.SYSTEM_NAME); + log.info("==== SmartJob ==== execute job->{},time-millis->{}ms", jobEntity.getJobName(), logEntity.getExecuteTimeMillis()); + } catch (Throwable t) { + log.error("==== SmartJob ==== execute err:", t); + } finally { + if (rLock.isHeldByCurrentThread()) { + rLock.unlock(); + } + } + } + + /** + * 执行任务 + * + * @param executorName + */ + public SmartJobLogEntity execute(String executorName) { + // 保存执行记录 + LocalDateTime startTime = LocalDateTime.now(); + Long logId = this.saveLogBeforeExecute(jobEntity, executorName, startTime); + + // 执行计时 + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + + // 执行任务 + boolean successFlag = true; + String executeResult; + try { + executeResult = jobInterface.run(jobEntity.getParam()); + stopWatch.stop(); + } catch (Throwable t) { + stopWatch.stop(); + successFlag = false; + // ps:异常信息不大于数据库字段长度限制 + executeResult = ExceptionUtil.stacktraceToString(t, 1800); + log.error("==== SmartJob ==== execute err:", t); + } + + // 更新执行记录 + SmartJobLogEntity logEntity = new SmartJobLogEntity(); + logEntity.setLogId(logId); + logEntity.setSuccessFlag(successFlag); + long totalTimeMillis = stopWatch.getTotalTimeMillis(); + logEntity.setExecuteTimeMillis(totalTimeMillis); + logEntity.setExecuteEndTime(startTime.plus(totalTimeMillis, ChronoUnit.MILLIS)); + logEntity.setExecuteResult(executeResult); + jobRepository.getJobLogDao().updateById(logEntity); + return logEntity; + } + + /** + * 执行前 保存执行记录 + * + * @param jobEntity + * @param executorName + * @param executeTime + * @return 返回执行记录id + */ + private Long saveLogBeforeExecute(SmartJobEntity jobEntity, + String executorName, + LocalDateTime executeTime) { + Integer jobId = jobEntity.getJobId(); + // 保存执行记录 + SmartJobLogEntity logEntity = new SmartJobLogEntity(); + logEntity.setJobId(jobId); + logEntity.setJobName(jobEntity.getJobName()); + logEntity.setParam(jobEntity.getParam()); + logEntity.setSuccessFlag(true); + // 执行开始时间 + logEntity.setExecuteStartTime(executeTime); + logEntity.setExecuteEndTime(executeTime); + logEntity.setExecuteTimeMillis(0L); + logEntity.setCreateName(executorName); + logEntity.setIp(SmartIpUtil.getLocalFirstIp()); + logEntity.setProcessId(SmartJobUtil.getProcessId()); + logEntity.setProgramPath(SmartJobUtil.getProgramPath()); + + // 更新最后执行时间 + SmartJobEntity updateJobEntity = new SmartJobEntity(); + updateJobEntity.setJobId(jobId); + updateJobEntity.setLastExecuteTime(executeTime); + jobRepository.saveLog(logEntity, updateJobEntity); + return logEntity.getLogId(); + } + + /** + * 查询 当前任务信息 + * + * @return + */ + public SmartJobEntity getJob() { + return jobEntity; + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/core/SmartJobLauncher.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/core/SmartJobLauncher.java new file mode 100644 index 0000000..04dd72e --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/core/SmartJobLauncher.java @@ -0,0 +1,153 @@ +package net.lab1024.sa.base.module.support.job.core; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import jakarta.annotation.PreDestroy; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.module.support.job.config.SmartJobConfig; +import net.lab1024.sa.base.module.support.job.constant.SmartJobConst; +import net.lab1024.sa.base.module.support.job.constant.SmartJobUtil; +import net.lab1024.sa.base.module.support.job.repository.SmartJobRepository; +import net.lab1024.sa.base.module.support.job.repository.domain.SmartJobEntity; +import org.redisson.api.RedissonClient; +import org.springframework.util.CollectionUtils; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 定时任务 作业启动类 + * + * @author huke + * @date 2024/6/17 21:30 + */ +@Slf4j +public class SmartJobLauncher { + + private final SmartJobRepository jobRepository; + + private final List jobInterfaceList; + + private final RedissonClient redissonClient; + + public SmartJobLauncher(SmartJobConfig jobConfig, + SmartJobRepository jobRepository, + List jobInterfaceList, + RedissonClient redissonClient) { + this.jobRepository = jobRepository; + this.jobInterfaceList = jobInterfaceList; + this.redissonClient = redissonClient; + + // init job scheduler + SmartJobScheduler.init(jobConfig); + + // 任务自动检测配置 固定1个线程 + Integer initDelay = jobConfig.getInitDelay(); + Boolean refreshEnabled = jobConfig.getDbRefreshEnabled(); + Integer refreshInterval = jobConfig.getDbRefreshInterval(); + + ThreadFactory factory = new ThreadFactoryBuilder().setNameFormat("SmartJobLauncher-%d").build(); + ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1, factory); + Runnable launcherRunnable = () -> { + try { + // 查询所有任务 + List smartJobList = this.queryJob(); + this.startOrRefreshJob(smartJobList); + } catch (Throwable t) { + log.error("SmartJob Error:", t); + } + // 只在启动时 执行一次 + if (!refreshEnabled) { + executor.shutdown(); + } + }; + executor.scheduleWithFixedDelay(launcherRunnable, initDelay, refreshInterval, TimeUnit.SECONDS); + + // 打印信息 + String refreshDesc = refreshEnabled ? "开启|检测间隔" + refreshInterval + "秒" : "关闭"; + String format = String.format(SmartJobConst.LOGO, jobConfig.getCorePoolSize(), initDelay, refreshDesc); + SmartJobUtil.printInfo(format); + } + + /** + * 查询数据库 + * 启动/刷新任务 + */ + public void startOrRefreshJob(List smartJobList) { + // 查询任务配置 + if (CollectionUtils.isEmpty(smartJobList) || CollectionUtils.isEmpty(jobInterfaceList)) { + log.info("==== SmartJob ==== job list empty"); + return; + } + + // 任务实现类 + Map jobImplMap = jobInterfaceList.stream().collect(Collectors.toMap(SmartJob::getClassName, Function.identity())); + for (SmartJobEntity jobEntity : smartJobList) { + // 任务是否存在 判断是否需要更新 + Integer jobId = jobEntity.getJobId(); + SmartJobEntity oldJobEntity = SmartJobScheduler.getJobInfo(jobId); + if (null != oldJobEntity) { + // 不需要更新 + if (!isNeedUpdate(oldJobEntity, jobEntity)) { + continue; + } + // 需要更新 移除原任务 + SmartJobScheduler.removeJob(jobId); + } + // 任务未开启 + if (!jobEntity.getEnabledFlag()) { + continue; + } + // 任务删除 + if (jobEntity.getDeletedFlag()) { + continue; + } + // 查找任务实现类 + SmartJob jobImpl = jobImplMap.get(jobEntity.getJobClass()); + if (null == jobImpl) { + continue; + } + // 添加任务 + SmartJobExecutor jobExecute = new SmartJobExecutor(jobEntity, jobRepository, jobImpl, redissonClient); + SmartJobScheduler.addJob(jobExecute); + } + List runjJobList = SmartJobScheduler.getJobInfo(); + List jobNameList = runjJobList.stream().map(SmartJobEntity::getJobName).collect(Collectors.toList()); + log.info("==== SmartJob ==== start/refresh job num:{}->{}", runjJobList.size(), jobNameList); + } + + /** + * 查询全部任务 + * + * @return + */ + private List queryJob() { + return jobRepository.getJobDao().selectList(null); + } + + /** + * 手动判断 任务配置 是否需要更新 + * 新增字段的话 在这个方法里增加判断 + * + * @return + */ + private static boolean isNeedUpdate(SmartJobEntity oldJob, SmartJobEntity newJob) { + // cron为空时 fixedDelay 才有意义 + return !Objects.equals(oldJob.getEnabledFlag(), newJob.getEnabledFlag()) + || !Objects.equals(oldJob.getDeletedFlag(), newJob.getDeletedFlag()) + || !Objects.equals(oldJob.getTriggerType(), newJob.getTriggerType()) + || !Objects.equals(oldJob.getTriggerValue(), newJob.getTriggerValue()) + || !Objects.equals(oldJob.getJobClass(), newJob.getJobClass()); + } + + @PreDestroy + public void destroy() { + SmartJobScheduler.destroy(); + log.info("==== SmartJob ==== destroy job"); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/core/SmartJobScheduler.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/core/SmartJobScheduler.java new file mode 100644 index 0000000..6706736 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/core/SmartJobScheduler.java @@ -0,0 +1,178 @@ +package net.lab1024.sa.base.module.support.job.core; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.module.support.job.config.SmartJobConfig; +import net.lab1024.sa.base.module.support.job.constant.SmartJobTriggerTypeEnum; +import net.lab1024.sa.base.module.support.job.constant.SmartJobUtil; +import net.lab1024.sa.base.module.support.job.repository.domain.SmartJobEntity; +import org.apache.commons.lang3.tuple.Pair; +import org.springframework.scheduling.Trigger; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.scheduling.support.CronTrigger; +import org.springframework.scheduling.support.PeriodicTrigger; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * 定时任务 调度管理 + * + * @author huke + * @date 2024/6/22 21:30 + */ +@Slf4j +public class SmartJobScheduler { + + /** + * Spring线程池任务调度器 + */ + private static ThreadPoolTaskScheduler TASK_SCHEDULER; + + /** + * 定时任务 map + */ + private static Map>> JOB_FUTURE_MAP; + + private SmartJobScheduler() { + + } + + /** + * 初始化任务调度配置 + */ + public static void init(SmartJobConfig config) { + TASK_SCHEDULER = new ThreadPoolTaskScheduler(); + ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("SmartJobExecutor-%d").build(); + TASK_SCHEDULER.setThreadFactory(threadFactory); + TASK_SCHEDULER.setPoolSize(config.getCorePoolSize()); + // 线程池在关闭时会等待所有任务完成 + TASK_SCHEDULER.setWaitForTasksToCompleteOnShutdown(true); + // 在调用shutdown方法后,等待任务完成的最长时间 + TASK_SCHEDULER.setAwaitTerminationSeconds(10); + // 错误处理 + TASK_SCHEDULER.setErrorHandler((t) -> log.error("SmartJobExecute Err:", t)); + // 当一个任务在被调度执行前被取消时,是否应该从线程池的任务队列中移除 + TASK_SCHEDULER.setRemoveOnCancelPolicy(true); + TASK_SCHEDULER.initialize(); + + JOB_FUTURE_MAP = new ConcurrentHashMap<>(); + } + + /** + * 获取任务执行对象 + * + * @param jobId + * @return + */ + public static ScheduledFuture getJobFuture(Integer jobId) { + Pair> pair = JOB_FUTURE_MAP.get(jobId); + if (null == pair) { + return null; + } + return pair.getRight(); + } + + /** + * 获取当前所有执行任务 + * + * @return + */ + public static List getJobInfo() { + return JOB_FUTURE_MAP.values().stream().map(Pair::getLeft).collect(Collectors.toList()); + } + + /** + * 获取任务执行实体类 + * + * @param jobId + * @return + */ + public static SmartJobEntity getJobInfo(Integer jobId) { + Pair> pair = JOB_FUTURE_MAP.get(jobId); + if (null == pair) { + return null; + } + return pair.getLeft(); + } + + /** + * 添加任务 + * + * @param jobExecute + * @return + */ + public static void addJob(SmartJobExecutor jobExecute) { + // 任务是否存在 + SmartJobEntity jobEntity = jobExecute.getJob(); + Integer jobId = jobEntity.getJobId(); + if (JOB_FUTURE_MAP.containsKey(jobId)) { + // 移除任务 + removeJob(jobId); + } + // 任务触发类型 + Trigger trigger = null; + String triggerType = jobEntity.getTriggerType(); + String triggerValue = jobEntity.getTriggerValue(); + // 优先 cron 表达式 + if (SmartJobTriggerTypeEnum.CRON.equalsValue(triggerType)) { + trigger = new CronTrigger(triggerValue); + } else if (SmartJobTriggerTypeEnum.FIXED_DELAY.equalsValue(triggerType)) { + trigger = new PeriodicTrigger(SmartJobUtil.getFixedDelayVal(triggerValue), TimeUnit.SECONDS); + } + String jobName = jobEntity.getJobName(); + if (null == trigger) { + log.error("==== SmartJob ==== trigger-value not null {}", jobName); + return; + } + // 执行任务 + ScheduledFuture schedule = TASK_SCHEDULER.schedule(jobExecute, trigger); + JOB_FUTURE_MAP.put(jobId, Pair.of(jobEntity, schedule)); + log.info("==== SmartJob ==== add job:{}", jobName); + } + + /** + * 移除任务 + * 等待任务执行完成后移除 + * + * @param jobId + */ + public static void removeJob(Integer jobId) { + ScheduledFuture jobFuture = getJobFuture(jobId); + if (null == jobFuture) { + return; + } + // 结束任务 + stopJob(jobFuture); + JOB_FUTURE_MAP.remove(jobId); + log.info("==== SmartJob ==== remove job:{}", jobId); + } + + /** + * 停止所有定时任务 + */ + public static void destroy() { + // 启动一个有序的关闭过程,在这个过程中,不再接受新的任务提交,但已提交的任务(包括正在执行的和队列中等待的)会被允许执行完成。 + TASK_SCHEDULER.destroy(); + JOB_FUTURE_MAP.clear(); + } + + /** + * 结束任务 + * 如果任务还没有开始执行,会直接被取消。 + * 如果任务已经开始执行,此时不会中断执行中的线程,任务会执行完成再被取消 + * + * @param scheduledFuture + */ + private static void stopJob(ScheduledFuture scheduledFuture) { + if (null == scheduledFuture || scheduledFuture.isCancelled()) { + return; + } + scheduledFuture.cancel(false); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/repository/SmartJobDao.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/repository/SmartJobDao.java new file mode 100644 index 0000000..028f87c --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/repository/SmartJobDao.java @@ -0,0 +1,47 @@ +package net.lab1024.sa.base.module.support.job.repository; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.base.module.support.job.api.domain.SmartJobQueryForm; +import net.lab1024.sa.base.module.support.job.api.domain.SmartJobVO; +import net.lab1024.sa.base.module.support.job.repository.domain.SmartJobEntity; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 定时任务 dao + * + * @author huke + * @date 2024/6/17 21:30 + */ +@Mapper +public interface SmartJobDao extends BaseMapper { + + /** + * 定时任务-分页查询 + * + * @param page + * @param queryForm + * @return + */ + List query(Page page, @Param("query") SmartJobQueryForm queryForm); + + /** + * 假删除 + * + * @param jobId + * @return + */ + void updateDeletedFlag(@Param("jobId") Integer jobId, @Param("deletedFlag") Boolean deletedFlag); + + /** + * 根据 任务class 查找 + * + * @param jobClass + * @return + */ + SmartJobEntity selectByJobClass(@Param("jobClass") String jobClass); +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/repository/SmartJobLogDao.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/repository/SmartJobLogDao.java new file mode 100644 index 0000000..b3b6a3d --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/repository/SmartJobLogDao.java @@ -0,0 +1,31 @@ +package net.lab1024.sa.base.module.support.job.repository; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.base.module.support.job.api.domain.SmartJobLogQueryForm; +import net.lab1024.sa.base.module.support.job.api.domain.SmartJobLogVO; +import net.lab1024.sa.base.module.support.job.repository.domain.SmartJobLogEntity; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 定时任务-执行记录 dao + * + * @author huke + * @date 2024/6/17 21:30 + */ +@Mapper +public interface SmartJobLogDao extends BaseMapper { + + /** + * 定时任务-执行记录-分页查询 + * + * @param page + * @param queryForm + * @return + */ + List query(Page page, @Param("query") SmartJobLogQueryForm queryForm); +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/repository/SmartJobRepository.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/repository/SmartJobRepository.java new file mode 100644 index 0000000..f8d1773 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/repository/SmartJobRepository.java @@ -0,0 +1,45 @@ +package net.lab1024.sa.base.module.support.job.repository; + +import net.lab1024.sa.base.module.support.job.repository.domain.SmartJobEntity; +import net.lab1024.sa.base.module.support.job.repository.domain.SmartJobLogEntity; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * job 持久化业务 + * + * @author huke + * @date 2024/6/22 22:28 + */ +@Service +public class SmartJobRepository { + + @Autowired + private SmartJobDao jobDao; + + @Autowired + private SmartJobLogDao jobLogDao; + + public SmartJobDao getJobDao() { + return jobDao; + } + + public SmartJobLogDao getJobLogDao() { + return jobLogDao; + } + + /** + * 保存执行记录 + * + * @param logEntity + * @param jobEntity + */ + @Transactional(rollbackFor = Throwable.class) + public void saveLog(SmartJobLogEntity logEntity, SmartJobEntity jobEntity) { + jobLogDao.insert(logEntity); + + jobEntity.setLastExecuteLogId(logEntity.getLogId()); + jobDao.updateById(jobEntity); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/repository/domain/SmartJobEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/repository/domain/SmartJobEntity.java new file mode 100644 index 0000000..890bc63 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/repository/domain/SmartJobEntity.java @@ -0,0 +1,89 @@ +package net.lab1024.sa.base.module.support.job.repository.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import net.lab1024.sa.base.module.support.job.constant.SmartJobTriggerTypeEnum; + +import java.time.LocalDateTime; + +/** + * 定时任务 实体类 + * + * @author huke + * @date 2024/6/17 21:30 + */ +@Data +@TableName("t_smart_job") +public class SmartJobEntity { + + /** + * 任务id + */ + @TableId(type = IdType.AUTO) + private Integer jobId; + + /** + * 任务名称 + */ + private String jobName; + + /** + * 执行类 + */ + private String jobClass; + + /** + * 触发类型 + * + * @see SmartJobTriggerTypeEnum + */ + private String triggerType; + + /** + * 触发配置 + */ + private String triggerValue; + + /** + * 定时任务参数 可选 + */ + private String param; + + /** + * 是否启用 + */ + private Boolean enabledFlag; + + /** + * 最后一执行时间 + */ + private LocalDateTime lastExecuteTime; + + /** + * 最后一次执行记录id + */ + private Long lastExecuteLogId; + + /** + * 备注描述 可选 + */ + private String remark; + + /** + * 排序 + */ + private Integer sort; + + /** + * 是否删除 + */ + private Boolean deletedFlag; + + private String updateName; + + private LocalDateTime updateTime; + + private LocalDateTime createTime; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/repository/domain/SmartJobLogEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/repository/domain/SmartJobLogEntity.java new file mode 100644 index 0000000..95c1d49 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/repository/domain/SmartJobLogEntity.java @@ -0,0 +1,81 @@ +package net.lab1024.sa.base.module.support.job.repository.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 定时任务 执行记录 实体类 + * + * @author huke + * @date 2024/6/17 21:30 + */ +@Data +@TableName("t_smart_job_log") +public class SmartJobLogEntity { + + @TableId(type = IdType.AUTO) + private Long logId; + + /** + * 任务id + */ + private Integer jobId; + + /** + * 任务名称 + */ + private String jobName; + + /** + * 定时任务参数 可选 + */ + private String param; + + /** + * 执行结果 是否成功 + */ + private Boolean successFlag; + + /** + * 开始执行时间 + */ + private LocalDateTime executeStartTime; + + /** + * 执行时长-毫秒 + */ + private Long executeTimeMillis; + + /** + * 执行结束时间 + */ + private LocalDateTime executeEndTime; + + /** + * 执行结果 描述 可选 + */ + private String executeResult; + + /** + * ip + */ + private String ip; + + /** + * 进程id + */ + private String processId; + + /** + * 程序目录 + */ + private String programPath; + + private String createName; + + private LocalDateTime createTime; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/sample/SmartJobSample1.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/sample/SmartJobSample1.java new file mode 100644 index 0000000..7036915 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/sample/SmartJobSample1.java @@ -0,0 +1,29 @@ +package net.lab1024.sa.base.module.support.job.sample; + +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.module.support.job.core.SmartJob; +import org.springframework.stereotype.Service; + +/** + * 定时任务 示例1 + * + * @author huke + * @date 2024/6/17 21:30 + */ +@Slf4j +@Service +public class SmartJobSample1 implements SmartJob { + + /** + * 定时任务示例 + * + * @param param 可选参数 任务不需要时不用管 + * @return + */ + @Override + public String run(String param) { + // 写点什么业务逻辑 + return "执行完毕,随便说点什么吧"; + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/sample/SmartJobSample2.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/sample/SmartJobSample2.java new file mode 100644 index 0000000..18d9a21 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/sample/SmartJobSample2.java @@ -0,0 +1,48 @@ +package net.lab1024.sa.base.module.support.job.sample; + +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.module.support.config.ConfigDao; +import net.lab1024.sa.base.module.support.config.domain.ConfigEntity; +import net.lab1024.sa.base.module.support.job.core.SmartJob; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * 定时任务 示例2 + * + * @author huke + * @date 2024/6/17 21:30 + */ +@Slf4j +@Service +public class SmartJobSample2 implements SmartJob { + + @Autowired + private ConfigDao configDao; + + /** + * 定时任务示例 + * 需要事务时 添加 @Transactional 注解 + * + * @param param 可选参数 任务不需要时不用管 + * @return + */ + @Transactional(rollbackFor = Throwable.class) + @Override + public String run(String param) { + // 随便更新点什么东西 + ConfigEntity configEntity = new ConfigEntity(); + configEntity.setConfigId(1L); + configEntity.setRemark(param); + configDao.updateById(configEntity); + + configEntity = new ConfigEntity(); + configEntity.setConfigId(2L); + configEntity.setRemark("SmartJob Sample2 update"); + configDao.updateById(configEntity); + + return "执行成功,本次处理数据1条"; + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/sample/package-info.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/sample/package-info.java new file mode 100644 index 0000000..d72e5a7 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/sample/package-info.java @@ -0,0 +1,5 @@ +/** + * 定时任务 示例包 + * 可以删除 + */ +package net.lab1024.sa.base.module.support.job.sample; \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/loginlog/LoginLogDao.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/loginlog/LoginLogDao.java new file mode 100644 index 0000000..b0509f0 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/loginlog/LoginLogDao.java @@ -0,0 +1,45 @@ +package net.lab1024.sa.base.module.support.loginlog; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.base.module.support.loginlog.domain.LoginLogEntity; +import net.lab1024.sa.base.module.support.loginlog.domain.LoginLogQueryForm; +import net.lab1024.sa.base.module.support.loginlog.domain.LoginLogVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 登录日志 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022/07/22 19:46:23 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface LoginLogDao extends BaseMapper { + + /** + * 分页查询 + * + * @param page + * @param queryForm + * @return LoginLogVO + */ + List queryByPage(Page page, @Param("query") LoginLogQueryForm queryForm); + + /** + * 查询上一个登录记录 + * + * @param userId + * @param userType + * @return LoginLogVO + */ + LoginLogVO queryLastByUserId(@Param("userId") Long userId,@Param("userType") Integer userType, @Param("loginLogResult")Integer loginLogResult); + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/loginlog/LoginLogResultEnum.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/loginlog/LoginLogResultEnum.java new file mode 100644 index 0000000..e867a2c --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/loginlog/LoginLogResultEnum.java @@ -0,0 +1,37 @@ +package net.lab1024.sa.base.module.support.loginlog; + +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 登录类型 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022/07/22 19:46:23 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public enum LoginLogResultEnum implements BaseEnum { + + LOGIN_SUCCESS(0, "登录成功"), + LOGIN_FAIL(1, "登录失败"), + LOGIN_OUT(2, "退出登录"); + + private Integer type; + private String desc; + + LoginLogResultEnum(Integer type, String desc) { + this.type = type; + this.desc = desc; + } + + @Override + public Integer getValue() { + return type; + } + + @Override + public String getDesc() { + return desc; + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/loginlog/LoginLogService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/loginlog/LoginLogService.java new file mode 100644 index 0000000..ab478e9 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/loginlog/LoginLogService.java @@ -0,0 +1,67 @@ +package net.lab1024.sa.base.module.support.loginlog; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.enumeration.UserTypeEnum; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.module.support.loginlog.domain.LoginLogEntity; +import net.lab1024.sa.base.module.support.loginlog.domain.LoginLogQueryForm; +import net.lab1024.sa.base.module.support.loginlog.domain.LoginLogVO; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 登录日志 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022/07/22 19:46:23 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +@Slf4j +public class LoginLogService { + + @Resource + private LoginLogDao loginLogDao; + + /** + * @author 卓大 + * @description 分页查询 + */ + public ResponseDTO> queryByPage(LoginLogQueryForm queryForm) { + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List logList = loginLogDao.queryByPage(page, queryForm); + PageResult pageResult = SmartPageUtil.convert2PageResult(page, logList); + return ResponseDTO.ok(pageResult); + } + + /** + * @author 卓大 + * @description 添加 + */ + public void log(LoginLogEntity loginLogEntity) { + try { + loginLogDao.insert(loginLogEntity); + } catch (Throwable e) { + log.error(e.getMessage(), e); + } + } + + + /** + * 查询上一个登录记录 + * + * @author 卓大 + * @description 查询上一个登录记录 + */ + public LoginLogVO queryLastByUserId(Long userId, UserTypeEnum userTypeEnum, LoginLogResultEnum loginLogResultEnum) { + return loginLogDao.queryLastByUserId(userId,userTypeEnum.getValue(), loginLogResultEnum.getValue()); + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/loginlog/domain/LoginLogEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/loginlog/domain/LoginLogEntity.java new file mode 100644 index 0000000..2ef5dba --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/loginlog/domain/LoginLogEntity.java @@ -0,0 +1,77 @@ +package net.lab1024.sa.base.module.support.loginlog.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Builder; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 登录日志 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022/07/22 19:46:23 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@TableName("t_login_log") +@Data +@Builder +public class LoginLogEntity { + + @TableId(type = IdType.AUTO) + private Long loginLogId; + + /** + * 用户id + */ + private Long userId; + + /** + * 用户类型 + */ + private Integer userType; + + /** + * 用户名 + */ + private String userName; + + /** + * 登录ip + */ + private String loginIp; + + /** + * 登录ip地区 + */ + private String loginIpRegion; + + /** + * user-agent + */ + private String userAgent; + + /** + * 备注 + */ + private String remark; + + /** + * 登录设备 + */ + private String loginDevice; + + /** + * 登录类型 + */ + private Integer loginResult; + + private LocalDateTime updateTime; + + private LocalDateTime createTime; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/loginlog/domain/LoginLogQueryForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/loginlog/domain/LoginLogQueryForm.java new file mode 100644 index 0000000..7af0dbe --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/loginlog/domain/LoginLogQueryForm.java @@ -0,0 +1,37 @@ +package net.lab1024.sa.base.module.support.loginlog.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; + +/** + * 登录查询日志 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022/07/22 19:46:23 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class LoginLogQueryForm extends PageParam { + + @Schema(description = "用户ID") + private Long userId; + + @Schema(description = "用户类型") + private Integer userType; + + @Schema(description = "开始日期") + private String startDate; + + @Schema(description = "结束日期") + private String endDate; + + @Schema(description = "用户名称") + private String userName; + + @Schema(description = "ip") + private String ip; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/loginlog/domain/LoginLogVO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/loginlog/domain/LoginLogVO.java new file mode 100644 index 0000000..205a7fb --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/loginlog/domain/LoginLogVO.java @@ -0,0 +1,53 @@ +package net.lab1024.sa.base.module.support.loginlog.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.enumeration.UserTypeEnum; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.module.support.loginlog.LoginLogResultEnum; + +import java.time.LocalDateTime; + +/** + * 登录日志 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022/07/22 19:46:23 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class LoginLogVO { + + private Long loginLogId; + + @Schema(description = "用户id") + private Long userId; + + @SchemaEnum(value = UserTypeEnum.class, desc = "用户类型") + private Integer userType; + + @Schema(description = "用户名") + private String userName; + + @Schema(description = "登录ip") + private String loginIp; + + @Schema(description = "登录ip地区") + private String loginIpRegion; + + @Schema(description = "user-agent") + private String userAgent; + + @Schema(description = "remark") + private String remark; + + @SchemaEnum(LoginLogResultEnum.class) + private Integer loginResult; + + private String loginDevice; + + private LocalDateTime createTime; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/MailService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/MailService.java new file mode 100644 index 0000000..ac5255d --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/MailService.java @@ -0,0 +1,179 @@ +package net.lab1024.sa.base.module.support.mail; + + +import cn.hutool.core.lang.UUID; +import freemarker.cache.StringTemplateLoader; +import freemarker.template.Configuration; +import freemarker.template.Template; +import jakarta.annotation.Resource; +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMessage; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.domain.SystemEnvironment; +import net.lab1024.sa.base.module.support.mail.constant.MailTemplateCodeEnum; +import net.lab1024.sa.base.module.support.mail.constant.MailTemplateTypeEnum; +import net.lab1024.sa.base.module.support.mail.domain.MailTemplateEntity; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.text.StringSubstitutor; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.io.StringWriter; +import java.io.Writer; +import java.util.List; +import java.util.Map; + +/** + * + * 发生邮件:
+ * 1、支持直接发送
+ * 2、支持使用邮件模板发送 + * + * @Author 1024创新实验室-创始人兼主任:卓大 + * @Date 2024/8/5 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 ,Since 2012 + */ +@Slf4j +@Component +public class MailService { + + @Autowired + private JavaMailSender javaMailSender; + + @Resource + private MailTemplateDao mailTemplateDao; + + @Resource + private SystemEnvironment systemEnvironment; + + @Value("${spring.mail.username}") + private String clientMail; + + + /** + * 使用模板发送邮件 + */ + public ResponseDTO sendMail(MailTemplateCodeEnum templateCode, Map templateParamsMap, List receiverUserList, List fileList) { + + MailTemplateEntity mailTemplateEntity = mailTemplateDao.selectById(templateCode.name().toLowerCase()); + if (mailTemplateEntity == null) { + return ResponseDTO.userErrorParam("模版不存在"); + } + + if (mailTemplateEntity.getDisableFlag()) { + return ResponseDTO.userErrorParam("模版已禁用,无法发送"); + } + + String content = null; + if (MailTemplateTypeEnum.FREEMARKER.name().equalsIgnoreCase(mailTemplateEntity.getTemplateType().trim())) { + content = freemarkerResolverContent(mailTemplateEntity.getTemplateContent(), templateParamsMap); + } else if (MailTemplateTypeEnum.STRING.name().equalsIgnoreCase(mailTemplateEntity.getTemplateType().trim())) { + content = stringResolverContent(mailTemplateEntity.getTemplateContent(), templateParamsMap); + } else { + return ResponseDTO.userErrorParam("模版类型不存在"); + } + + try { + + this.sendMail(mailTemplateEntity.getTemplateSubject(), content, fileList, receiverUserList, true); + + } catch (Throwable e) { + log.error("邮件发送失败", e); + return ResponseDTO.userErrorParam("邮件发送失败"); + } + return ResponseDTO.ok(); + } + + /** + * 使用模板发送邮件 + */ + public ResponseDTO sendMail(MailTemplateCodeEnum templateCode, Map templateParamsMap, List receiverUserList) { + return this.sendMail(templateCode, templateParamsMap, receiverUserList, null); + } + + + /** + * 发送邮件 + * + * @param subject 主题 + * @param content 内容 + * @param fileList 文件 + * @param receiverUserList 接收方 + * @throws MessagingException + */ + public void sendMail(String subject, String content, List fileList, List receiverUserList, boolean isHtml) throws MessagingException { + + if (CollectionUtils.isEmpty(receiverUserList)) { + throw new RuntimeException("接收方不能为空"); + } + + if (StringUtils.isBlank(content)) { + throw new RuntimeException("邮件内容不能为空"); + } + + if (!systemEnvironment.isProd()) { + subject = "(测试)" + subject; + } + + MimeMessage mimeMessage = javaMailSender.createMimeMessage(); + + //是否为多文件上传 + boolean multiparty = !CollectionUtils.isEmpty(fileList); + MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, multiparty); + helper.setFrom(clientMail); + helper.setTo(receiverUserList.toArray(new String[0])); + helper.setSubject(subject); + //发送html格式 + helper.setText(content, isHtml); + + //附件 + if (multiparty) { + for (File file : fileList) { + helper.addAttachment(file.getName(), file); + } + } + javaMailSender.send(mimeMessage); + } + + /** + * 使用字符串生成最终内容 + */ + private String stringResolverContent(String stringTemplate, Map templateParamsMap) { + StringSubstitutor stringSubstitutor = new StringSubstitutor(templateParamsMap); + String contractHtml = stringSubstitutor.replace(stringTemplate); + Document doc = Jsoup.parse(contractHtml); + doc.outputSettings().syntax(Document.OutputSettings.Syntax.xml); + return doc.outerHtml(); + } + + + /** + * 使用 freemarker 生成最终内容 + */ + private String freemarkerResolverContent(String htmlTemplate, Map templateParamsMap) { + Configuration configuration = new Configuration(Configuration.VERSION_2_3_23); + StringTemplateLoader stringLoader = new StringTemplateLoader(); + String templateName = UUID.fastUUID().toString(true); + stringLoader.putTemplate(templateName, htmlTemplate); + configuration.setTemplateLoader(stringLoader); + try { + Template template = configuration.getTemplate(templateName, "utf-8"); + Writer out = new StringWriter(2048); + template.process(templateParamsMap, out); + return out.toString(); + } catch (Throwable e) { + log.error("freemarkerResolverContent error: ", e); + } + return ""; + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/MailTemplateDao.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/MailTemplateDao.java new file mode 100644 index 0000000..ecc6b07 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/MailTemplateDao.java @@ -0,0 +1,21 @@ +package net.lab1024.sa.base.module.support.mail; + + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import net.lab1024.sa.base.module.support.mail.domain.MailTemplateEntity; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.stereotype.Component; + +/** + * 邮件模板 + * + * @Author 1024创新实验室-创始人兼主任:卓大 + * @Date 2024/8/5 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 ,Since 2012 + */ +@Mapper +public interface MailTemplateDao extends BaseMapper { + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/constant/MailTemplateCodeEnum.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/constant/MailTemplateCodeEnum.java new file mode 100644 index 0000000..9eab6ca --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/constant/MailTemplateCodeEnum.java @@ -0,0 +1,19 @@ +package net.lab1024.sa.base.module.support.mail.constant; + +/** + * 模版编码 + * + * @Author 1024创新实验室-创始人兼主任:卓大 + * @Date 2024/8/5 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 ,Since 2012 + */ +public enum MailTemplateCodeEnum { + + /** + * 登录验证码 + */ + LOGIN_VERIFICATION_CODE + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/constant/MailTemplateTypeEnum.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/constant/MailTemplateTypeEnum.java new file mode 100644 index 0000000..f26ef24 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/constant/MailTemplateTypeEnum.java @@ -0,0 +1,30 @@ +package net.lab1024.sa.base.module.support.mail.constant; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 邮件模板类型 + * + * @Author 1024创新实验室-创始人兼主任:卓大 + * @Date 2024/8/5 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 ,Since 2012 + */ + +@Getter +@AllArgsConstructor +public enum MailTemplateTypeEnum implements BaseEnum { + + STRING("string", "字符串替代器"), + + FREEMARKER("freemarker", "freemarker模板引擎"); + + private String value; + + private String desc; + + +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/domain/MailTemplateEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/domain/MailTemplateEntity.java new file mode 100644 index 0000000..8f16163 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/domain/MailTemplateEntity.java @@ -0,0 +1,51 @@ +package net.lab1024.sa.base.module.support.mail.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * + * 邮件模板 + * + * @Author 1024创新实验室-创始人兼主任:卓大 + * @Date 2024/8/5 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 ,Since 2012 + */ +@Data +@TableName("t_mail_template") +public class MailTemplateEntity { + + @TableId(type = IdType.NONE) + private String templateCode; + + /** + * 主题 + */ + private String templateSubject; + + /** + * 模板类型 + */ + private String templateType; + + /** + * 模板内容 + */ + private String templateContent; + + /** + * 禁用标识 + */ + private Boolean disableFlag; + + + private LocalDateTime updateTime; + + private LocalDateTime createTime; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/constant/MessageTemplateEnum.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/constant/MessageTemplateEnum.java new file mode 100644 index 0000000..d9c6785 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/constant/MessageTemplateEnum.java @@ -0,0 +1,31 @@ +package net.lab1024.sa.base.module.support.message.constant; + + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 消息模板类型 + * + * @author luoyi + * @date 2024/06/22 20:20 + */ +@Getter +@AllArgsConstructor +public enum MessageTemplateEnum implements BaseEnum { + + + + ORDER_AUDIT(1000, "订单审批", MessageTypeEnum.ORDER, "您有一个订单等待审批,订单号【${orderNumber}】"), + + ; + + private final Integer value; + + private final String desc; + + private final MessageTypeEnum messageTypeEnum; + + private final String content; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/constant/MessageTypeEnum.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/constant/MessageTypeEnum.java new file mode 100644 index 0000000..b94cb10 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/constant/MessageTypeEnum.java @@ -0,0 +1,27 @@ +package net.lab1024.sa.base.module.support.message.constant; + + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.lab1024.sa.base.common.enumeration.BaseEnum; + + +/** + * 消息类型 + * + * @author luoyi + * @date 2024/06/22 20:20 + */ +@Getter +@AllArgsConstructor +public enum MessageTypeEnum implements BaseEnum { + + MAIL(1, "站内信"), + + ORDER(2, "订单"), + ; + + private final Integer value; + + private final String desc; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/controller/MessageController.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/controller/MessageController.java new file mode 100644 index 0000000..449a124 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/controller/MessageController.java @@ -0,0 +1,67 @@ +package net.lab1024.sa.base.module.support.message.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import net.lab1024.sa.base.common.controller.SupportBaseController; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.RequestUser; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartRequestUtil; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import net.lab1024.sa.base.module.support.message.domain.MessageQueryForm; +import net.lab1024.sa.base.module.support.message.domain.MessageVO; +import net.lab1024.sa.base.module.support.message.service.MessageService; +import org.springframework.web.bind.annotation.*; + +/** + * 消息 + * + * @author luoyi + * @date 2024/06/22 20:20 + */ +@RestController +@Tag(name = SwaggerTagConst.Support.MESSAGE) +public class MessageController extends SupportBaseController { + + @Resource + private MessageService messageService; + + @Operation(summary = "分页查询我的消息 @luoyi") + @PostMapping("/message/queryMyMessage") + public ResponseDTO> query(@RequestBody @Valid MessageQueryForm queryForm) { + RequestUser user = SmartRequestUtil.getRequestUser(); + if(user == null){ + return ResponseDTO.userErrorParam("用户未登录"); + } + + queryForm.setSearchCount(false); + queryForm.setReceiverUserId(user.getUserId()); + queryForm.setReceiverUserType(user.getUserType().getValue()); + return ResponseDTO.ok(messageService.query(queryForm)); + } + + @Operation(summary = "查询未读消息数量 @luoyi") + @GetMapping("/message/getUnreadCount") + public ResponseDTO getUnreadCount() { + RequestUser user = SmartRequestUtil.getRequestUser(); + if(user == null){ + return ResponseDTO.userErrorParam("用户未登录"); + } + return ResponseDTO.ok(messageService.getUnreadCount(user.getUserType(), user.getUserId())); + } + + @Operation(summary = "更新已读 @luoyi") + @GetMapping("/message/read/{messageId}") + public ResponseDTO updateReadFlag(@PathVariable Long messageId) { + RequestUser user = SmartRequestUtil.getRequestUser(); + if(user == null){ + return ResponseDTO.userErrorParam("用户未登录"); + } + + messageService.updateReadFlag(messageId, user.getUserType(), user.getUserId()); + return ResponseDTO.ok(); + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/dao/MessageDao.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/dao/MessageDao.java new file mode 100644 index 0000000..01389e0 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/dao/MessageDao.java @@ -0,0 +1,45 @@ +package net.lab1024.sa.base.module.support.message.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.base.module.support.message.domain.MessageEntity; +import net.lab1024.sa.base.module.support.message.domain.MessageQueryForm; +import net.lab1024.sa.base.module.support.message.domain.MessageVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 消息 接受者类型枚举 + * + * @author luoyi + * @date 2024/06/22 20:20 + */ +@Mapper +public interface MessageDao extends BaseMapper { + + /** + * 分页查询消息 + * + */ + List query(Page page, @Param("query") MessageQueryForm queryForm); + + /** + * 更新已读状态 + */ + Integer updateReadFlag(@Param("messageId") Long messageId, + @Param("receiverUserType") Integer receiverUserType, + @Param("receiverUserId") Long receiverUserId, + @Param("readFlag") Boolean readFlag); + + /** + * 查询未读消息数 + */ + Long getUnreadCount( @Param("receiverUserType") Integer receiverUserType, + @Param("receiverUserId") Long receiverUserId); + + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/domain/MessageEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/domain/MessageEntity.java new file mode 100644 index 0000000..673ed69 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/domain/MessageEntity.java @@ -0,0 +1,70 @@ +package net.lab1024.sa.base.module.support.message.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import net.lab1024.sa.base.module.support.message.constant.MessageTypeEnum; + +import java.time.LocalDateTime; + +/** + * 消息实体 + * + * @author luoyi + * @date 2024/06/22 20:20 + */ +@Data +@TableName("t_message") +public class MessageEntity { + + @TableId(type = IdType.AUTO) + private Long messageId; + + /** + * 消息类型 + * + * @see MessageTypeEnum + */ + private Integer messageType; + /** + * 接收者类型 + * + * @see net.lab1024.sa.base.common.enumeration.UserTypeEnum + */ + private Integer receiverUserType; + + /** + * 接收者id + */ + private Long receiverUserId; + + /** + * 相关业务id + */ + private String dataId; + + /** + * 消息标题 + */ + private String title; + + /** + * 消息内容 + */ + private String content; + + /** + * 是否已读 + */ + private Boolean readFlag; + + /** + * 已读时间 + */ + private LocalDateTime readTime; + + private LocalDateTime updateTime; + + private LocalDateTime createTime; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/domain/MessageQueryForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/domain/MessageQueryForm.java new file mode 100644 index 0000000..fe80d70 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/domain/MessageQueryForm.java @@ -0,0 +1,44 @@ +package net.lab1024.sa.base.module.support.message.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; +import net.lab1024.sa.base.module.support.message.constant.MessageTypeEnum; +import org.hibernate.validator.constraints.Length; + +import java.time.LocalDate; + +/** + * 消息查询form + * + * @author luoyi + * @date 2024/06/22 20:20 + */ +@Data +public class MessageQueryForm extends PageParam { + + @Schema(description = "搜索词") + @Length(max = 50, message = "搜索词最多50字符") + private String searchWord; + + @SchemaEnum(value = MessageTypeEnum.class) + @CheckEnum(value = MessageTypeEnum.class, message = "消息类型") + private Integer messageType; + + @Schema(description = "是否已读") + private Boolean readFlag; + + @Schema(description = "查询开始时间") + private LocalDate startDate; + + @Schema(description = "查询结束时间") + private LocalDate endDate; + + @Schema(description = "接收人") + private Long receiverUserId; + + @Schema(description = "接收人类型") + private Integer receiverUserType; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/domain/MessageSendForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/domain/MessageSendForm.java new file mode 100644 index 0000000..d62d216 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/domain/MessageSendForm.java @@ -0,0 +1,44 @@ +package net.lab1024.sa.base.module.support.message.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import net.lab1024.sa.base.common.enumeration.UserTypeEnum; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.module.support.message.constant.MessageTypeEnum; +/** + * 消息发送form + * + * @author luoyi + * @date 2024/06/22 20:20 + */ +@Data +public class MessageSendForm { + + @SchemaEnum(value = MessageTypeEnum.class, desc = "消息类型") + @NotNull(message = "消息类型不能为空") + private Integer messageType; + + @SchemaEnum(value = UserTypeEnum.class, desc = "接收者类型") + @NotNull(message = "接收者类型不能为空") + private Integer receiverUserType; + + @Schema(description = "接收者id") + @NotNull(message = "接收者id不能为空") + private Long receiverUserId; + + @Schema(description = "标题") + @NotBlank(message = "标题") + private String title; + + @Schema(description = "内容") + @NotBlank(message = "内容") + private String content; + + /** + * 相关业务id | 可选 + */ + private Object dataId; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/domain/MessageTemplateSendForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/domain/MessageTemplateSendForm.java new file mode 100644 index 0000000..9f70ac6 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/domain/MessageTemplateSendForm.java @@ -0,0 +1,41 @@ +package net.lab1024.sa.base.module.support.message.domain; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import net.lab1024.sa.base.common.enumeration.UserTypeEnum; +import net.lab1024.sa.base.module.support.message.constant.MessageTemplateEnum; + +import java.util.Map; + +/** + * 消息发送form + * + * @author luoyi + * @date 2024/06/22 20:20 + */ +@Data +public class MessageTemplateSendForm { + + @NotNull(message = "消息子类型不能为空") + private MessageTemplateEnum messageTemplateEnum; + + @NotNull(message = "接收者类型不能为空") + private UserTypeEnum receiverUserType; + + @NotNull(message = "接收者id不能为空") + private Long receiverUserId; + + /** + * 相关业务id | 可选 + * 用于跳转具体业务 + */ + private Object dataId; + + /** + * 消息参数 | 可选 + * 例:订单号:【{orderId}】{time}所提交的对账单被作废,请核实信息重新提交~ + * {orderId} {time} 就是消息的参数变量 + * 发送消息时 需要在map中放入k->orderId k->time + */ + private Map contentParam; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/domain/MessageVO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/domain/MessageVO.java new file mode 100644 index 0000000..eb7366f --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/domain/MessageVO.java @@ -0,0 +1,48 @@ +package net.lab1024.sa.base.module.support.message.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.enumeration.UserTypeEnum; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.module.support.message.constant.MessageTypeEnum; + +import java.time.LocalDateTime; + +/** + * 消息 + * + * @author luoyi + * @date 2024/06/22 20:20 + */ +@Data +public class MessageVO { + + private Long messageId; + + @SchemaEnum(value = MessageTypeEnum.class) + private Integer messageType; + + @SchemaEnum(value = UserTypeEnum.class) + private Integer receiverUserType; + + @Schema(description = "接收者id") + private Long receiverUserId; + + @Schema(description = "相关业务id") + private String dataId; + + @Schema(description = "消息标题") + private String title; + + @Schema(description = "消息内容") + private String content; + + @Schema(description = "是否已读") + private Boolean readFlag; + + @Schema(description = "已读时间") + private LocalDateTime readTime; + + @Schema(description = "创建时间") + private LocalDateTime createTime; +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/service/MessageManager.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/service/MessageManager.java new file mode 100644 index 0000000..e1e2419 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/service/MessageManager.java @@ -0,0 +1,18 @@ +package net.lab1024.sa.base.module.support.message.service; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import net.lab1024.sa.base.module.support.message.dao.MessageDao; +import net.lab1024.sa.base.module.support.message.domain.MessageEntity; +import org.springframework.stereotype.Service; + +/** + * 消息manager + * + * @author luoyi + * @date 2024/06/22 20:20 + */ +@Service +public class MessageManager extends ServiceImpl { + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/service/MessageService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/service/MessageService.java new file mode 100644 index 0000000..d380c19 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/service/MessageService.java @@ -0,0 +1,118 @@ +package net.lab1024.sa.base.module.support.message.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.google.common.collect.Lists; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.enumeration.UserTypeEnum; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.module.support.message.constant.MessageTemplateEnum; +import net.lab1024.sa.base.module.support.message.dao.MessageDao; +import net.lab1024.sa.base.module.support.message.domain.*; +import org.apache.commons.text.StringSubstitutor; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author luoyi + * @date 2024/6/27 12:14 上午 + */ +@Service +public class MessageService { + + @Resource + private MessageDao messageDao; + + @Resource + private MessageManager messageManager; + + /** + * 分页查询 消息 + */ + public PageResult query(MessageQueryForm queryForm) { + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List messageVOList = messageDao.query(page, queryForm); + return SmartPageUtil.convert2PageResult(page, messageVOList); + } + + /** + * 查询未读消息数量 + */ + public Long getUnreadCount(UserTypeEnum userType, Long userId) { + return messageDao.getUnreadCount(userType.getValue(), userId); + } + + /** + * 更新已读状态 + */ + public void updateReadFlag(Long messageId, UserTypeEnum userType, Long receiverUserId) { + messageDao.updateReadFlag(messageId, userType.getValue(), receiverUserId, true); + } + + + /** + * 发送【模板消息】 + */ + public void sendTemplateMessage(MessageTemplateSendForm... sendTemplateForms) { + List sendFormList = Lists.newArrayList(); + for (MessageTemplateSendForm sendTemplateForm : sendTemplateForms) { + MessageTemplateEnum msgTemplateTypeEnum = sendTemplateForm.getMessageTemplateEnum(); + StringSubstitutor stringSubstitutor = new StringSubstitutor(sendTemplateForm.getContentParam()); + String content = stringSubstitutor.replace(msgTemplateTypeEnum.getContent()); + + MessageSendForm messageSendForm = new MessageSendForm(); + messageSendForm.setMessageType(msgTemplateTypeEnum.getMessageTypeEnum().getValue()); + messageSendForm.setReceiverUserType(sendTemplateForm.getReceiverUserType().getValue()); + messageSendForm.setReceiverUserId(sendTemplateForm.getReceiverUserId()); + messageSendForm.setTitle(msgTemplateTypeEnum.getDesc()); + messageSendForm.setContent(content); + messageSendForm.setDataId(sendTemplateForm.getDataId()); + sendFormList.add(messageSendForm); + + } + this.sendMessage(sendFormList); + } + + /** + * 发送消息 + */ + public void sendMessage(MessageSendForm... sendForms) { + this.sendMessage(Lists.newArrayList(sendForms)); + } + + /** + * 批量发送通知消息 + */ + public void sendMessage(List sendList) { + for (MessageSendForm sendDTO : sendList) { + String verify = SmartBeanUtil.verify(sendDTO); + if (null != verify) { + throw new RuntimeException("send msg error: " + verify); + } + } + List messageEntityList = sendList.stream().map(e -> { + MessageEntity messageEntity = new MessageEntity(); + messageEntity.setMessageType(e.getMessageType()); + messageEntity.setReceiverUserType(e.getReceiverUserType()); + messageEntity.setReceiverUserId(e.getReceiverUserId()); + messageEntity.setDataId(String.valueOf(e.getDataId())); + messageEntity.setTitle(e.getTitle()); + messageEntity.setContent(e.getContent()); + return messageEntity; + }).collect(Collectors.toList()); + messageManager.saveBatch(messageEntityList); + } + + // 删除消息 + public ResponseDTO delete(Long messageId) { + if(messageId == null){ + return ResponseDTO.userErrorParam(); + } + messageDao.deleteById(messageId); + return ResponseDTO.ok(); + } +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/OperateLogDao.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/OperateLogDao.java new file mode 100644 index 0000000..cdf520a --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/OperateLogDao.java @@ -0,0 +1,41 @@ +package net.lab1024.sa.base.module.support.operatelog; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.base.module.support.operatelog.domain.OperateLogEntity; +import net.lab1024.sa.base.module.support.operatelog.domain.OperateLogQueryForm; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 操作日志 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2021-12-08 20:48:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface OperateLogDao extends BaseMapper { + + /** + * 分页查询 + * @param page + * @param queryForm + * @return UserOperateLogEntity + */ + List queryByPage(Page page, @Param("query") OperateLogQueryForm queryForm); + + + /** + * 批量删除 + * + * @param idList + * @return + */ + void deleteByIds(@Param("idList") List idList); +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/OperateLogService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/OperateLogService.java new file mode 100644 index 0000000..47c0644 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/OperateLogService.java @@ -0,0 +1,57 @@ +package net.lab1024.sa.base.module.support.operatelog; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.code.UserErrorCode; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.module.support.operatelog.domain.OperateLogEntity; +import net.lab1024.sa.base.module.support.operatelog.domain.OperateLogQueryForm; +import net.lab1024.sa.base.module.support.operatelog.domain.OperateLogVO; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 操作日志 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2021-12-08 20:48:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class OperateLogService { + + @Resource + private OperateLogDao operateLogDao; + + /** + * @author 罗伊 + * @description 分页查询 + */ + public ResponseDTO> queryByPage(OperateLogQueryForm queryForm) { + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List logEntityList = operateLogDao.queryByPage(page, queryForm); + PageResult pageResult = SmartPageUtil.convert2PageResult(page, logEntityList, OperateLogVO.class); + return ResponseDTO.ok(pageResult); + } + + + /** + * 查询详情 + * @param operateLogId + * @return + */ + public ResponseDTO detail(Long operateLogId) { + OperateLogEntity operateLogEntity = operateLogDao.selectById(operateLogId); + if(operateLogEntity == null){ + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST); + } + OperateLogVO operateLogVO = SmartBeanUtil.copy(operateLogEntity, OperateLogVO.class); + return ResponseDTO.ok(operateLogVO); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/annotation/OperateLog.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/annotation/OperateLog.java new file mode 100644 index 0000000..a13be6d --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/annotation/OperateLog.java @@ -0,0 +1,19 @@ +package net.lab1024.sa.base.module.support.operatelog.annotation; + +import java.lang.annotation.*; + +/** + * 用户操作日志 注解 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2021-12-08 20:48:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +@Documented +public @interface OperateLog { + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/core/OperateLogAspect.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/core/OperateLogAspect.java new file mode 100644 index 0000000..19349e7 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/core/OperateLogAspect.java @@ -0,0 +1,277 @@ +package net.lab1024.sa.base.module.support.operatelog.core; + +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.constant.StringConst; +import net.lab1024.sa.base.common.domain.RequestUser; +import net.lab1024.sa.base.common.util.SmartIpUtil; +import net.lab1024.sa.base.common.util.SmartRequestUtil; +import net.lab1024.sa.base.module.support.operatelog.OperateLogDao; +import net.lab1024.sa.base.module.support.operatelog.annotation.OperateLog; +import net.lab1024.sa.base.module.support.operatelog.domain.OperateLogEntity; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.Signature; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.AfterThrowing; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.boot.context.properties.bind.BindResult; +import org.springframework.context.ApplicationContext; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.servlet.ModelAndView; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * 操作日志记录处理,对所有OperateLog注解的Controller进行操作日志监控 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2021-12-08 20:48:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@Aspect +public abstract class OperateLogAspect { + + private static final String POINT_CUT = "@within(net.lab1024.sa.base.module.support.operatelog.annotation.OperateLog) || @annotation(net.lab1024.sa.base.module.support.operatelog.annotation.OperateLog)"; + + @Resource + private ApplicationContext applicationContext; + /** + * 线程池 + */ + private ThreadPoolTaskExecutor taskExecutor; + + public abstract OperateLogConfig getOperateLogConfig(); + + public OperateLogAspect() { + this.initThread(); + } + + @Pointcut(POINT_CUT) + public void logPointCut() { + } + + @AfterReturning(pointcut = "logPointCut()") + public void doAfterReturning(JoinPoint joinPoint) { + handleLog(joinPoint, null); + } + + @AfterThrowing(value = "logPointCut()", throwing = "e") + public void doAfterThrowing(JoinPoint joinPoint, Exception e) { + handleLog(joinPoint, e); + } + + /** + * 初始化线程池 + */ + private void initThread() { + OperateLogConfig config = getOperateLogConfig(); + int corePoolSize = Runtime.getRuntime().availableProcessors(); + if (null != config.getCorePoolSize()) { + corePoolSize = config.getCorePoolSize(); + } + taskExecutor = new ThreadPoolTaskExecutor(); + //线程初始化 + taskExecutor.initialize(); + // 设置核心线程数 + taskExecutor.setCorePoolSize(corePoolSize); + // 设置最大线程数 + taskExecutor.setMaxPoolSize(corePoolSize * 2); + // 设置队列容量 + taskExecutor.setQueueCapacity(1000); + // 设置线程活跃时间(秒) + taskExecutor.setKeepAliveSeconds(60); + // 设置默认线程名称 + taskExecutor.setThreadNamePrefix("smart-operate-log"); + // 设置拒绝策略 + taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + // 等待所有任务结束后再关闭线程池 + taskExecutor.setWaitForTasksToCompleteOnShutdown(true); + } + + protected void handleLog(final JoinPoint joinPoint, final Exception e) { + try { + OperateLog operateLog = this.getAnnotationLog(joinPoint); + if (operateLog == null) { + return; + } + this.submitLog(joinPoint, e); + } catch (Exception exp) { + log.error("保存操作日志异常:{}", exp.getMessage()); + exp.printStackTrace(); + } + } + + private OperateLog getAnnotationLog(JoinPoint joinPoint) throws Exception { + Signature signature = joinPoint.getSignature(); + MethodSignature methodSignature = (MethodSignature) signature; + Method method = methodSignature.getMethod(); + OperateLog classAnnotation = AnnotationUtils.findAnnotation(method.getDeclaringClass(), OperateLog.class); + if (classAnnotation != null) { + return classAnnotation; + } + OperateLog methodAnnotation = AnnotationUtils.findAnnotation(method, OperateLog.class); + return methodAnnotation; + } + + /** + * swagger tag + * + * @param joinPoint + * @return + * @throws Exception + */ + private Tag getApi(JoinPoint joinPoint) { + Signature signature = joinPoint.getSignature(); + MethodSignature methodSignature = (MethodSignature) signature; + Method method = methodSignature.getMethod(); + Tag classAnnotation = AnnotationUtils.findAnnotation(method.getDeclaringClass(), Tag.class); + if (method != null) { + return classAnnotation; + } + return null; + } + + /** + * swagger ApiOperation + * + * @param joinPoint + * @return + * @throws Exception + */ + private Operation getApiOperation(JoinPoint joinPoint) { + Signature signature = joinPoint.getSignature(); + MethodSignature methodSignature = (MethodSignature) signature; + Method method = methodSignature.getMethod(); + + if (method != null) { + return method.getAnnotation(Operation.class); + } + return null; + } + + /** + * 提交存储操作日志 + * + * @param joinPoint + * @param e + * @throws Exception + */ + private void submitLog(final JoinPoint joinPoint, final Throwable e) throws Exception { + HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + //设置用户信息 + RequestUser user = SmartRequestUtil.getRequestUser(); + if (user == null) { + return; + } + + Object[] args = joinPoint.getArgs(); + String params = buildParamString(args); + String className = joinPoint.getTarget().getClass().getName(); + String methodName = joinPoint.getSignature().getName(); + String operateMethod = className + "." + methodName; + String failReason = null; + Boolean successFlag = true; + if (e != null) { + successFlag = false; + failReason = getExceptionString(e); + } + + OperateLogEntity operateLogEntity = + OperateLogEntity.builder() + .operateUserId(user.getUserId()) + .operateUserType(user.getUserType().getValue()) + .operateUserName(user.getUserName()) + .url(request.getRequestURI()) + .method(operateMethod) + .param(params) + .ip(user.getIp()) + .ipRegion(SmartIpUtil.getRegion(user.getIp())) + .userAgent(user.getUserAgent()) + .failReason(failReason) + .successFlag(successFlag).build(); + Operation apiOperation = this.getApiOperation(joinPoint); + if (apiOperation != null) { + operateLogEntity.setContent(apiOperation.summary()); + } + Tag api = this.getApi(joinPoint); + if (api != null) { + String name = api.name(); + operateLogEntity.setModule(StrUtil.join(",", name)); + } + taskExecutor.execute(() -> { + this.saveLog(operateLogEntity); + }); + } + + private String buildParamString(Object[] args) { + if (args == null || args.length == 0) { + return StringConst.EMPTY; + } + + List filterArgs = new ArrayList<>(); + for (Object arg : args) { + if (arg == null) { + continue; + } + if (arg instanceof HttpServletRequest + || arg instanceof HttpServletResponse + || arg instanceof ModelAndView + || arg instanceof MultipartFile + || arg instanceof BindResult) { + continue; + } + filterArgs.add(arg); + } + return JSON.toJSONString(filterArgs); + } + + + private String getExceptionString(Throwable e) { + StringWriter sw = new StringWriter(); + try (PrintWriter pw = new PrintWriter(sw);) { + e.printStackTrace(pw); + } + return sw.toString(); + } + + /** + * 保存操作日志 + * + * @param operateLogEntity + * @return + */ + private Boolean saveLog(OperateLogEntity operateLogEntity) { + OperateLogConfig operateLogConfig = getOperateLogConfig(); + if (operateLogConfig.getSaveFunction() == null) { + BaseMapper mapper = applicationContext.getBean(OperateLogDao.class); + if (mapper == null) { + return false; + } + mapper.insert(operateLogEntity); + return true; + } + return operateLogConfig.getSaveFunction().apply(operateLogEntity); + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/core/OperateLogConfig.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/core/OperateLogConfig.java new file mode 100644 index 0000000..0e8727c --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/core/OperateLogConfig.java @@ -0,0 +1,38 @@ +package net.lab1024.sa.base.module.support.operatelog.core; + +import lombok.Builder; +import lombok.Data; +import net.lab1024.sa.base.module.support.operatelog.domain.OperateLogEntity; + +import java.util.function.Function; + +/** + * 配置 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2021-12-08 20:48:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@Builder +public class OperateLogConfig { + + /** + * 操作日志存储方法 + */ + private Function saveFunction; + + /** + * 核心线程数 + */ + private Integer corePoolSize; + + /** + * 队列大小 + */ + private Integer queueCapacity; + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/domain/OperateLogEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/domain/OperateLogEntity.java new file mode 100644 index 0000000..4f83b1d --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/domain/OperateLogEntity.java @@ -0,0 +1,111 @@ +package net.lab1024.sa.base.module.support.operatelog.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +/** + * 操作记录 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2021-12-08 20:48:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@TableName("t_operate_log") +public class OperateLogEntity { + + /** + * 主键id + */ + @TableId(type = IdType.AUTO) + private Long operateLogId; + + /** + * 操作人id + */ + private Long operateUserId; + + /** + * 用户类型 + */ + private Integer operateUserType; + + /** + * 操作人名称 + */ + private String operateUserName; + /** + * 操作模块 + */ + private String module; + + /** + * 操作内容 + */ + private String content; + + /** + * 请求路径 + */ + private String url; + + /** + * 请求方法 + */ + private String method; + + /** + * 请求参数 + */ + private String param; + + /** + * 客户ip + */ + private String ip; + + /** + * 客户ip地区 + */ + private String ipRegion; + + /** + * user-agent + */ + private String userAgent; + + /** + * 请求结果 0失败 1成功 + */ + private Boolean successFlag; + + /** + * 失败原因 + */ + private String failReason; + + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/domain/OperateLogQueryForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/domain/OperateLogQueryForm.java new file mode 100644 index 0000000..5e9bc70 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/domain/OperateLogQueryForm.java @@ -0,0 +1,44 @@ +package net.lab1024.sa.base.module.support.operatelog.domain; + +import net.lab1024.sa.base.common.domain.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 操作日志查询 表单 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2021-12-08 20:48:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class OperateLogQueryForm extends PageParam { + + @Schema(description = "用户ID") + private Long operateUserId; + + @Schema(description = "用户类型") + private Integer operateUserType; + + @Schema(description = "关键字:模块、操作内容") + private String keywords; + + @Schema(description = "请求关键字:请求地址、请求方法、请求参数") + private String requestKeywords; + + @Schema(description = "开始日期") + private String startDate; + + @Schema(description = "结束日期") + private String endDate; + + + @Schema(description = "用户名称") + private String userName; + + @Schema(description = "请求结果 false失败 true成功") + private Boolean successFlag; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/domain/OperateLogVO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/domain/OperateLogVO.java new file mode 100644 index 0000000..9cf3529 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/domain/OperateLogVO.java @@ -0,0 +1,74 @@ +package net.lab1024.sa.base.module.support.operatelog.domain; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.enumeration.UserTypeEnum; +import net.lab1024.sa.base.common.swagger.SchemaEnum; + +import java.time.LocalDateTime; + +/** + * 操作日志信息 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2021-12-08 20:48:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class OperateLogVO { + + @Schema(description = "主键") + private Long operateLogId; + + @Schema(description = "用户id") + private Long operateUserId; + + @SchemaEnum(value = UserTypeEnum.class, desc = "用户类型") + private Integer operateUserType; + + @Schema(description = "用户名称") + private String operateUserName; + + @Schema(description = "操作模块") + private String module; + + @Schema(description = "操作内容") + private String content; + + @Schema(description = "请求路径") + private String url; + + @Schema(description = "请求方法") + private String method; + + @Schema(description = "请求参数") + private String param; + + @Schema(description = "客户ip") + private String ip; + + @Schema(description = "客户ip地区") + private String ipRegion; + + @Schema(description = "user-agent") + private String userAgent; + + @Schema(description = "请求结果 0失败 1成功") + private Boolean successFlag; + + @Schema(description = "失败原因") + private String failReason; + + @Schema(description = "更新时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime updateTime; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime createTime; + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/redis/RedisService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/redis/RedisService.java new file mode 100644 index 0000000..a45f812 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/redis/RedisService.java @@ -0,0 +1,224 @@ +package net.lab1024.sa.base.module.support.redis; + +import com.alibaba.fastjson.JSON; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.domain.SystemEnvironment; +import net.lab1024.sa.base.common.enumeration.SystemEnvironmentEnum; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.constant.RedisKeyConst; +import org.slf4j.Logger; +import org.springframework.data.redis.core.*; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.temporal.ChronoUnit; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * redis 一顿操作 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2020/8/25 21:57 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class RedisService { + + private static final Logger log = org.slf4j.LoggerFactory.getLogger(RedisService.class); + + @Resource + private StringRedisTemplate stringRedisTemplate; + + @Resource + private RedisTemplate redisTemplate; + + @Resource + private ValueOperations redisValueOperations; + + @Resource + private HashOperations redisHashOperations; + + @Resource + private ListOperations redisListOperations; + + @Resource + private SetOperations redisSetOperations; + + @Resource + private SystemEnvironment systemEnvironment; + + + /** + * 生成redis key + * @param prefix + * @param key + * @return + */ + public String generateRedisKey(String prefix, String key) { + SystemEnvironmentEnum currentEnvironment = systemEnvironment.getCurrentEnvironment(); + return systemEnvironment.getProjectName() + RedisKeyConst.SEPARATOR + currentEnvironment.getValue() + RedisKeyConst.SEPARATOR + prefix + key; + } + + /** + * redis key 解析成真实的内容 + * @param redisKey + * @return + */ + public static String redisKeyParse(String redisKey) { + if(SmartStringUtil.isBlank(redisKey)){ + return ""; + } + int index = redisKey.lastIndexOf(RedisKeyConst.SEPARATOR); + if(index < 1){ + return redisKey; + } + return redisKey.substring(index); + } + + public boolean getLock(String key, long expire) { + return redisValueOperations.setIfAbsent(key, String.valueOf(System.currentTimeMillis()), expire, TimeUnit.MILLISECONDS); + } + + public void unLock(String key) { + redisValueOperations.getOperations().delete(key); + } + + /** + * 指定缓存失效时间 + * + * @param key 键 + * @param time 时间(秒) + * @return + */ + public boolean expire(String key, long time) { + return redisTemplate.expire(key, time, TimeUnit.SECONDS); + } + + /** + * 获取当天剩余的秒数 + * + * @return + */ + public static long currentDaySecond() { + return ChronoUnit.SECONDS.between(LocalDateTime.now(), LocalDateTime.of(LocalDate.now(), LocalTime.MAX)); + } + + /** + * 根据key 获取过期时间 + * + * @param key 键 不能为null + * @return 时间(秒) 返回0代表为永久有效 + */ + public long getExpire(String key) { + return redisTemplate.getExpire(key, TimeUnit.SECONDS); + } + + /** + * 判断key是否存在 + * + * @param key 键 + * @return true 存在 false不存在 + */ + public boolean hasKey(String key) { + return redisTemplate.hasKey(key); + } + + /** + * 删除缓存 + * + * @param key 可以传一个值 或多个 + */ + @SuppressWarnings("unchecked") + public void delete(String... key) { + if (key != null && key.length > 0) { + if (key.length == 1) { + redisTemplate.delete(key[0]); + } else { + redisTemplate.delete((Collection) CollectionUtils.arrayToList(key)); + } + } + } + + /** + * 删除缓存 + * + * @param keyList + */ + public void delete(List keyList) { + if (CollectionUtils.isEmpty(keyList)) { + return; + } + redisTemplate.delete(keyList); + } + + //============================String============================= + + /** + * 普通缓存获取 + * + * @param key 键 + * @return 值 + */ + public String get(String key) { + return key == null ? null : redisValueOperations.get(key); + } + + public T getObject(String key, Class clazz) { + Object json = this.get(key); + if (json == null) { + return null; + } + T obj = JSON.parseObject(json.toString(), clazz); + return obj; + } + + + /** + * 普通缓存放入 + */ + public void set(String key, String value) { + redisValueOperations.set(key, value); + } + public void set(Object key, Object value) { + String jsonString = JSON.toJSONString(value); + redisValueOperations.set(key.toString(), jsonString); + } + + /** + * 普通缓存放入 + */ + public void set(String key, String value, long second) { + redisValueOperations.set(key, value, second, TimeUnit.SECONDS); + } + + /** + * 普通缓存放入并设置时间 + */ + public void set(Object key, Object value, long second) { + String jsonString = JSON.toJSONString(value); + if (second > 0) { + redisValueOperations.set(key.toString(), jsonString, second, TimeUnit.SECONDS); + } else { + set(key.toString(), jsonString); + } + } + + //============================ map ============================= + + + public void mset(String key, String hashKey, Object value) { + redisHashOperations.put(key, hashKey, value); + } + + public Object mget(String key, String hashKey) { + return redisHashOperations.get(key, hashKey); + } + +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/redis/RedissonPasswordConfigurationCustomizer.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/redis/RedissonPasswordConfigurationCustomizer.java new file mode 100644 index 0000000..3781ca8 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/redis/RedissonPasswordConfigurationCustomizer.java @@ -0,0 +1,34 @@ +package net.lab1024.sa.base.module.support.redis; + +import net.lab1024.sa.base.common.util.SmartStringUtil; +import org.redisson.config.Config; +import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer; +import org.springframework.stereotype.Component; + +/** + * + * redission对于password 为空处理有问题,重新设置下 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2024/7/16 01:04:18 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 ,Since 2012 + */ + +@Component +public class RedissonPasswordConfigurationCustomizer implements RedissonAutoConfigurationCustomizer { + @Override + public void customize(Config configuration) { + if (configuration.isSingleConfig() && SmartStringUtil.isEmpty(configuration.useSingleServer().getPassword())) { + configuration.useSingleServer().setPassword(null); + } + + if (configuration.isClusterConfig() && SmartStringUtil.isEmpty(configuration.useClusterServers().getPassword())) { + configuration.useClusterServers().setPassword(null); + } + if (configuration.isSentinelConfig() && SmartStringUtil.isEmpty(configuration.useSentinelServers().getPassword())) { + configuration.useSentinelServers().setPassword(null); + } + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/redis/RedissonService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/redis/RedissonService.java new file mode 100644 index 0000000..aaab74a --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/redis/RedissonService.java @@ -0,0 +1,139 @@ +package net.lab1024.sa.base.module.support.redis; + +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.exception.BusinessException; +import org.redisson.api.RBucket; +import org.redisson.api.RIdGenerator; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.Duration; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +/** + * Redisson 业务 + * + * @author huke + * @date 2024/6/19 20:39 + */ +@Slf4j +@Service +public class RedissonService { + + @Autowired + private final RedissonClient redissonClient; + + public RedissonService(RedissonClient redissonClient) { + this.redissonClient = redissonClient; + } + + public RedissonClient getRedissonClient() { + return redissonClient; + } + + /** + * 获取锁 并 执行程序 + * + * @param lockKey + * @param waitTime 毫秒 + * @param lockTime 毫秒 + * @param supplier + */ + public T executeWithLock(String lockKey, long waitTime, long lockTime, Supplier supplier) { + // 获取锁 + RLock lock = this.tryLock(lockKey, waitTime, lockTime); + try { + return supplier.get(); + } finally { + // 释放锁 + if (lock.isHeldByCurrentThread()) { + lock.unlock(); + } + } + } + + /** + * 获取锁 并 执行程序 + * + * @param lockKey + * @param waitTime 毫秒 + * @param lockTime 毫秒 + * @param runnable + */ + public void executeWithLock(String lockKey, long waitTime, long lockTime, Runnable runnable) { + // 获取锁 + RLock lock = this.tryLock(lockKey, waitTime, lockTime); + try { + runnable.run(); + } finally { + // 释放锁 + if (lock.isHeldByCurrentThread()) { + lock.unlock(); + } + } + } + + /** + * 尝试获取锁 + * 最多等待 waitTime 毫秒 + * 获取锁成功后占用 lockTime 毫秒 + * ps:需要手动解锁 lock.unlock() + * + * @param lockKey + * @param waitTime 毫秒 + * @param lockTime 毫秒 + * @return + */ + public RLock tryLock(String lockKey, long waitTime, long lockTime) { + RLock lock = redissonClient.getLock(lockKey); + try { + boolean getLock = lock.tryLock(waitTime, lockTime, TimeUnit.MILLISECONDS); + if (getLock) { + return lock; + } + } catch (InterruptedException e) { + log.error("Redisson tryLock", e); + } + throw new BusinessException("业务繁忙,请稍后重试~"); + } + + /** + * 获取 id 生成器 + * nextId 可生成连续不重复的id + * + * @param key + * @return + */ + public RIdGenerator idGenerator(String key) { + return redissonClient.getIdGenerator(key); + } + + /** + * 存放任意数据类型 + * + * @param key + * @param v + * @param duration + * @param + */ + public void putObj(String key, T v, Duration duration) { + redissonClient.getBucket(key).set(v, duration); + } + + /** + * 获取任意数据类型 + * + * @param key + * @param clazz + * @param + * @return 如果没有找到则返回null + */ + public T getObj(String key, Class clazz) { + RBucket bucket = redissonClient.getBucket(key); + return bucket.get(); + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/ReloadCommand.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/ReloadCommand.java new file mode 100644 index 0000000..e848b05 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/ReloadCommand.java @@ -0,0 +1,56 @@ +package net.lab1024.sa.base.module.support.reload; + +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.module.support.reload.core.AbstractSmartReloadCommand; +import net.lab1024.sa.base.module.support.reload.core.domain.SmartReloadItem; +import net.lab1024.sa.base.module.support.reload.core.domain.SmartReloadResult; +import net.lab1024.sa.base.module.support.reload.dao.ReloadItemDao; +import net.lab1024.sa.base.module.support.reload.dao.ReloadResultDao; +import net.lab1024.sa.base.module.support.reload.domain.ReloadItemEntity; +import net.lab1024.sa.base.module.support.reload.domain.ReloadResultEntity; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * reload 操作 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2015-03-02 19:11:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Component +public class ReloadCommand extends AbstractSmartReloadCommand { + + @Resource + private ReloadItemDao reloadItemDao; + + @Resource + private ReloadResultDao reloadResultDao; + + /** + * 读取数据库中SmartReload项 + * + * @return List + */ + @Override + public List readReloadItem() { + List reloadItemEntityList = reloadItemDao.selectList(null); + return SmartBeanUtil.copyList(reloadItemEntityList, SmartReloadItem.class); + } + + + /** + * 保存reload结果 + * + * @param smartReloadResult + */ + @Override + public void handleReloadResult(SmartReloadResult smartReloadResult) { + ReloadResultEntity reloadResultEntity = SmartBeanUtil.copy(smartReloadResult, ReloadResultEntity.class); + reloadResultDao.insert(reloadResultEntity); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/ReloadService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/ReloadService.java new file mode 100644 index 0000000..98b8ae3 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/ReloadService.java @@ -0,0 +1,68 @@ +package net.lab1024.sa.base.module.support.reload; + +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.code.UserErrorCode; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.module.support.reload.dao.ReloadItemDao; +import net.lab1024.sa.base.module.support.reload.dao.ReloadResultDao; +import net.lab1024.sa.base.module.support.reload.domain.ReloadForm; +import net.lab1024.sa.base.module.support.reload.domain.ReloadItemEntity; +import net.lab1024.sa.base.module.support.reload.domain.ReloadItemVO; +import net.lab1024.sa.base.module.support.reload.domain.ReloadResultVO; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * reload (内存热加载、钩子等) + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2015-03-02 19:11:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class ReloadService { + + @Resource + private ReloadItemDao reloadItemDao; + + @Resource + private ReloadResultDao reloadResultDao; + + /** + * 查询 + * + * @return + */ + public ResponseDTO> query() { + List list = reloadItemDao.query(); + return ResponseDTO.ok(list); + } + + public ResponseDTO> queryReloadItemResult(String tag) { + List reloadResultList = reloadResultDao.query(tag); + return ResponseDTO.ok(reloadResultList); + } + + + /** + * 通过标签更新标识符 + * + * @param reloadForm + * @return + */ + public ResponseDTO updateByTag(ReloadForm reloadForm) { + ReloadItemEntity reloadItemEntity = reloadItemDao.selectById(reloadForm.getTag()); + if (null == reloadItemEntity) { + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST); + } + reloadItemEntity.setIdentification(reloadForm.getIdentification()); + reloadItemEntity.setUpdateTime(LocalDateTime.now()); + reloadItemEntity.setArgs(reloadForm.getArgs()); + reloadItemDao.updateById(reloadItemEntity); + return ResponseDTO.ok(); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/AbstractSmartReloadCommand.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/AbstractSmartReloadCommand.java new file mode 100644 index 0000000..a19aa06 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/AbstractSmartReloadCommand.java @@ -0,0 +1,96 @@ +package net.lab1024.sa.base.module.support.reload.core; + + +import net.lab1024.sa.base.module.support.reload.core.domain.SmartReloadItem; +import net.lab1024.sa.base.module.support.reload.core.domain.SmartReloadObject; +import net.lab1024.sa.base.module.support.reload.core.domain.SmartReloadResult; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 检测是否 Reload 的类 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2015-03-02 19:11:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public abstract class AbstractSmartReloadCommand { + + /** + * 当前ReloadItem的存储器 + */ + private ConcurrentHashMap tagIdentifierMap = new ConcurrentHashMap<>(); + + private SmartReloadManager smartReloadManager; + + /** + * @return + */ + public void setReloadManager(SmartReloadManager smartReloadManager) { + this.smartReloadManager = smartReloadManager; + } + + public void init() { + List smartReloadItems = this.readReloadItem(); + if (smartReloadItems != null) { + for (SmartReloadItem smartReloadItem : smartReloadItems) { + tagIdentifierMap.put(smartReloadItem.getTag(), smartReloadItem.getIdentification()); + } + } + } + + + /** + * 该方法返回一个List:
+ * ReloadItem对象的tagIdentify为:该tag的 状态(状态其实就是个字符串,如果该字符串跟上次有变化则进行reload操作)
+ * ReloadItem对象的args为: reload操作需要的参数

+ * + * @return List + */ + public abstract List readReloadItem(); + + /** + * 处理Reload结果 + * + * @param smartReloadResult + */ + public abstract void handleReloadResult(SmartReloadResult smartReloadResult); + + + /** + * 获取本地缓存tag标识 + * + * @return + */ + public ConcurrentHashMap getTagIdentifierMap() { + return tagIdentifierMap; + } + + /** + * 设置新的缓存标识 + * + * @param tag + * @param identification + */ + public void putIdentifierMap(String tag, String identification) { + tagIdentifierMap.put(tag, identification); + } + + + /** + * 获取重载对象 + * + * @return + */ + public SmartReloadObject reloadObject(String tag) { + if (this.smartReloadManager == null) { + return null; + } + Map reloadObjectMap = smartReloadManager.reloadObjectMap(); + return reloadObjectMap.get(tag); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/SmartReloadManager.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/SmartReloadManager.java new file mode 100644 index 0000000..8a3a180 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/SmartReloadManager.java @@ -0,0 +1,130 @@ +package net.lab1024.sa.base.module.support.reload.core; + + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.module.support.reload.core.annoation.SmartReload; +import net.lab1024.sa.base.module.support.reload.core.domain.SmartReloadObject; +import net.lab1024.sa.base.module.support.reload.core.thread.SmartReloadRunnable; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.stereotype.Service; +import org.springframework.util.ReflectionUtils; + +import java.lang.reflect.Method; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * SmartReloadManager 管理器 + *

+ * 可以在此类中添加 检测任务 以及注册 处理程序 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2015-03-02 19:11:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@Service +public class SmartReloadManager implements BeanPostProcessor { + + private static final String THREAD_NAME_PREFIX = "smart-reload"; + private static final int THREAD_COUNT = 1; + + @Value("${reload.interval-seconds}") + private Integer intervalSeconds; + + @Resource + private AbstractSmartReloadCommand reloadCommand; + + private final Map reloadObjectMap = new ConcurrentHashMap<>(); + + private ScheduledThreadPoolExecutor threadPoolExecutor; + + + @PostConstruct + public void init() { + if (threadPoolExecutor != null) { + return; + } + + this.threadPoolExecutor = new ScheduledThreadPoolExecutor(THREAD_COUNT, r -> { + Thread t = new Thread(r, THREAD_NAME_PREFIX); + if (!t.isDaemon()) { + t.setDaemon(true); + } + return t; + }); + + // 如果 reloadCommand 不存在,则不启动定时任务 + if (this.reloadCommand != null) { + this.threadPoolExecutor.scheduleWithFixedDelay(new SmartReloadRunnable(this.reloadCommand), 10, this.intervalSeconds, TimeUnit.SECONDS); + this.reloadCommand.setReloadManager(this); + } else { + log.warn("SmartReloadManager: AbstractSmartReloadCommand not found, reload feature disabled"); + } + } + + + @PreDestroy + public void shutdown() { + if (this.threadPoolExecutor != null) { + this.threadPoolExecutor.shutdownNow(); + this.threadPoolExecutor = null; + } + } + + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + Method[] methods = ReflectionUtils.getAllDeclaredMethods(bean.getClass()); + for (Method method : methods) { + SmartReload smartReload = method.getAnnotation(SmartReload.class); + if (smartReload == null) { + continue; + } + int paramCount = method.getParameterCount(); + if (paramCount > 1) { + log.error("<> register tag reload : " + smartReload.value() + " , param count cannot greater than one !"); + continue; + } + String reloadTag = smartReload.value(); + this.register(reloadTag, new SmartReloadObject(bean, method)); + } + return bean; + } + + /** + * 注册reload + * + * @param tag + * @param smartReloadObject + */ + private void register(String tag, SmartReloadObject smartReloadObject) { + if (reloadObjectMap.containsKey(tag)) { + log.error("<> register duplicated tag reload : " + tag + " , and it will be cover!"); + } + reloadObjectMap.put(tag, smartReloadObject); + } + + /** + * 获取重载对象 + * + * @return + */ + public Map reloadObjectMap() { + return this.reloadObjectMap; + } + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/annoation/SmartReload.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/annoation/SmartReload.java new file mode 100644 index 0000000..0dc3f5b --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/annoation/SmartReload.java @@ -0,0 +1,22 @@ +package net.lab1024.sa.base.module.support.reload.core.annoation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 定义 SmartReload 注解 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2015-03-02 19:11:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface SmartReload { + + String value(); +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/domain/SmartReloadItem.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/domain/SmartReloadItem.java new file mode 100644 index 0000000..19dc6be --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/domain/SmartReloadItem.java @@ -0,0 +1,32 @@ +package net.lab1024.sa.base.module.support.reload.core.domain; + +import lombok.Data; + +/** + * reload项目 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2015-03-02 19:11:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class SmartReloadItem { + + /** + * 项名称 + */ + private String tag; + + /** + * 参数 + */ + private String args; + + /** + * 标识 + */ + private String identification; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/domain/SmartReloadObject.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/domain/SmartReloadObject.java new file mode 100644 index 0000000..c05efd2 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/domain/SmartReloadObject.java @@ -0,0 +1,32 @@ +package net.lab1024.sa.base.module.support.reload.core.domain; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.lang.reflect.Method; + +/** + * Reload 处理程序的实现方法,用于包装以注解 SmartReload 实现的处理类 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2015-03-02 19:11:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@AllArgsConstructor +public class SmartReloadObject { + + /** + * 方法对应的实例化对象 + */ + private Object reloadObject; + + /** + * 重新加载执行的方法 + */ + private Method method; + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/domain/SmartReloadResult.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/domain/SmartReloadResult.java new file mode 100644 index 0000000..2a38c02 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/domain/SmartReloadResult.java @@ -0,0 +1,43 @@ +package net.lab1024.sa.base.module.support.reload.core.domain; + +import lombok.Data; + +/** + * t_reload_result 表 实体类 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2015-03-02 19:11:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class SmartReloadResult { + + /** + * 项名称 + */ + private String tag; + + /** + * 参数 + */ + private String args; + + /** + * 标识 + */ + private String identification; + + /** + * 处理结果 + */ + private boolean result; + + /** + * 异常说明 + */ + private String exception; + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/thread/SmartReloadRunnable.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/thread/SmartReloadRunnable.java new file mode 100644 index 0000000..88cb1a4 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/thread/SmartReloadRunnable.java @@ -0,0 +1,121 @@ +package net.lab1024.sa.base.module.support.reload.core.thread; + +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.module.support.reload.core.AbstractSmartReloadCommand; +import net.lab1024.sa.base.module.support.reload.core.SmartReloadManager; +import net.lab1024.sa.base.module.support.reload.core.domain.SmartReloadItem; +import net.lab1024.sa.base.module.support.reload.core.domain.SmartReloadObject; +import net.lab1024.sa.base.module.support.reload.core.domain.SmartReloadResult; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Method; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +/** + * reload 线程 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2015-03-02 19:11:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +public class SmartReloadRunnable implements Runnable { + + private AbstractSmartReloadCommand abstractSmartReloadCommand; + + private boolean isInit = false; + + public SmartReloadRunnable(AbstractSmartReloadCommand abstractSmartReloadCommand) { + this.abstractSmartReloadCommand = abstractSmartReloadCommand; + } + + @Override + public void run() { + try { + this.doTask(); + } catch (Throwable e) { + log.error("", e); + } + } + + /** + * 检测Identifier变化,执行reload + */ + private void doTask() { + if (!isInit) { + this.abstractSmartReloadCommand.init(); + isInit = true; + return; + } + + List smartReloadItemList = this.abstractSmartReloadCommand.readReloadItem(); + ConcurrentHashMap tagIdentifierMap = this.abstractSmartReloadCommand.getTagIdentifierMap(); + for (SmartReloadItem smartReloadItem : smartReloadItemList) { + String tag = smartReloadItem.getTag(); + String tagIdentifier = smartReloadItem.getIdentification(); + String preTagChangeIdentifier = tagIdentifierMap.get(tag); + // 数据不一致 + if (preTagChangeIdentifier == null || !preTagChangeIdentifier.equals(tagIdentifier)) { + this.abstractSmartReloadCommand.putIdentifierMap(tag, tagIdentifier); + // 执行重新加载此项的动作 + SmartReloadResult smartReloadResult = this.doReload(smartReloadItem); + this.abstractSmartReloadCommand.handleReloadResult(smartReloadResult); + } + } + } + + /** + * 方法调用 + * + * @param smartReloadItem + * @return + */ + private SmartReloadResult doReload(SmartReloadItem smartReloadItem) { + SmartReloadResult result = new SmartReloadResult(); + SmartReloadObject smartReloadObject = this.abstractSmartReloadCommand.reloadObject(smartReloadItem.getTag()); + try { + if (smartReloadObject == null) { + result.setResult(false); + result.setException("不能从系统中找到对应的tag:" + smartReloadItem.getTag()); + return result; + } + + Method method = smartReloadObject.getMethod(); + if (method == null) { + result.setResult(false); + result.setException("reload方法不存在"); + return result; + } + + result.setTag(smartReloadItem.getTag()); + result.setArgs(smartReloadItem.getArgs()); + result.setIdentification(smartReloadItem.getIdentification()); + result.setResult(true); + int paramCount = method.getParameterCount(); + if (paramCount > 1) { + result.setResult(false); + result.setException("reload方法" + method.getName() + "参数太多"); + return result; + } + + if (paramCount == 0) { + method.invoke(smartReloadObject.getReloadObject()); + } else { + method.invoke(smartReloadObject.getReloadObject(), smartReloadItem.getArgs()); + } + } catch (Throwable throwable) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + throwable.printStackTrace(pw); + + result.setResult(false); + result.setException(throwable.toString()); + } + return result; + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/dao/ReloadItemDao.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/dao/ReloadItemDao.java new file mode 100644 index 0000000..30fe8f8 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/dao/ReloadItemDao.java @@ -0,0 +1,24 @@ +package net.lab1024.sa.base.module.support.reload.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import net.lab1024.sa.base.module.support.reload.domain.ReloadItemEntity; +import net.lab1024.sa.base.module.support.reload.domain.ReloadItemVO; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * t_reload_item 数据表dao + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2015-03-02 19:11:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface ReloadItemDao extends BaseMapper { + + List query(); +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/dao/ReloadResultDao.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/dao/ReloadResultDao.java new file mode 100644 index 0000000..93cb07a --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/dao/ReloadResultDao.java @@ -0,0 +1,25 @@ +package net.lab1024.sa.base.module.support.reload.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import net.lab1024.sa.base.module.support.reload.domain.ReloadResultEntity; +import net.lab1024.sa.base.module.support.reload.domain.ReloadResultVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * t_reload_result 数据表dao + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2015-03-02 19:11:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface ReloadResultDao extends BaseMapper { + + List query(@Param("tag") String tag); +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/domain/ReloadForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/domain/ReloadForm.java new file mode 100644 index 0000000..c704fc2 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/domain/ReloadForm.java @@ -0,0 +1,30 @@ +package net.lab1024.sa.base.module.support.reload.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +/** + * reload (内存热加载、钩子等) + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2015-03-02 19:11:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class ReloadForm { + + @Schema(description = "标签") + @NotBlank(message = "标签不能为空") + private String tag; + + @Schema(description = "状态标识") + @NotBlank(message = "状态标识不能为空") + private String identification; + + @Schema(description = "参数") + private String args; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/domain/ReloadItemEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/domain/ReloadItemEntity.java new file mode 100644 index 0000000..9c24af9 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/domain/ReloadItemEntity.java @@ -0,0 +1,50 @@ +package net.lab1024.sa.base.module.support.reload.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * t_reload_item 数据表 实体类 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2015-03-02 19:11:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName("t_reload_item") +public class ReloadItemEntity { + + /** + * 加载项标签 + */ + @TableId(type = IdType.INPUT) + private String tag; + + /** + * 参数 + */ + private String args; + + /** + * 运行标识 + */ + private String identification; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/domain/ReloadItemVO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/domain/ReloadItemVO.java new file mode 100644 index 0000000..a9c81e4 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/domain/ReloadItemVO.java @@ -0,0 +1,36 @@ +package net.lab1024.sa.base.module.support.reload.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * reload (内存热加载、钩子等) + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2015-03-02 19:11:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class ReloadItemVO { + + @Schema(description = "加载项标签") + private String tag; + + @Schema(description = "参数") + private String args; + + @Schema(description = "运行标识") + private String identification; + + @Schema(description = "更新时间") + private LocalDateTime updateTime; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/domain/ReloadResultEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/domain/ReloadResultEntity.java new file mode 100644 index 0000000..7516e6c --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/domain/ReloadResultEntity.java @@ -0,0 +1,56 @@ +package net.lab1024.sa.base.module.support.reload.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * reload结果
+ * t_reload_result 数据表 实体类 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2015-03-02 19:11:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName("t_reload_result") +public class ReloadResultEntity { + + /** + * 加载项标签 + */ + @TableId(type= IdType.NONE) + private String tag; + + /** + * 运行标识 + */ + private String identification; + + /** + * 参数 + */ + private String args; + + /** + * 运行结果 + */ + private Boolean result; + + /** + * 异常 + */ + private String exception; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/domain/ReloadResultVO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/domain/ReloadResultVO.java new file mode 100644 index 0000000..da63994 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/domain/ReloadResultVO.java @@ -0,0 +1,34 @@ +package net.lab1024.sa.base.module.support.reload.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * reload结果 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2015-03-02 19:11:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class ReloadResultVO { + + @Schema(description = "加载项标签") + private String tag; + + @Schema(description = "参数") + private String args; + + @Schema(description = "运行结果") + private Boolean result; + + @Schema(description = "异常") + private String exception; + + @Schema(description = "创建时间") + private LocalDateTime createTime; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/repeatsubmit/RepeatSubmitAspect.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/repeatsubmit/RepeatSubmitAspect.java new file mode 100644 index 0000000..723ecd7 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/repeatsubmit/RepeatSubmitAspect.java @@ -0,0 +1,78 @@ +package net.lab1024.sa.base.module.support.repeatsubmit; + +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.code.UserErrorCode; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.module.support.repeatsubmit.annoation.RepeatSubmit; +import net.lab1024.sa.base.module.support.repeatsubmit.ticket.AbstractRepeatSubmitTicket; +import org.apache.commons.lang3.StringUtils; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.lang.reflect.Method; + +/** + * 重复提交 aop切口 + * + * @Author 1024创新实验室: 胡克 + * @Date 2020-11-25 20:56:58 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Aspect +@Slf4j +public class RepeatSubmitAspect { + + private final AbstractRepeatSubmitTicket repeatSubmitTicket; + + /** + * 获取凭证信息 + */ + public RepeatSubmitAspect(AbstractRepeatSubmitTicket repeatSubmitTicket) { + this.repeatSubmitTicket = repeatSubmitTicket; + } + + /** + * 定义切入点 + */ + @Around("@annotation(net.lab1024.sa.base.module.support.repeatsubmit.annoation.RepeatSubmit)") + public Object around(ProceedingJoinPoint point) throws Throwable { + + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + String ticketToken = attributes.getRequest().getServletPath(); + String ticket = this.repeatSubmitTicket.getTicket(ticketToken); + if (StringUtils.isEmpty(ticket)) { + return point.proceed(); + } + + Method method = ((MethodSignature) point.getSignature()).getMethod(); + RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class); + int limit = annotation.value(); + + // 获取上一次请求时间 + Long lastRequestTime = this.repeatSubmitTicket.getTicketTimestamp(ticket); + // 校验是否限制时间内重复提交 + if (lastRequestTime != null && System.currentTimeMillis() < lastRequestTime + limit) { + return ResponseDTO.error(UserErrorCode.REPEAT_SUBMIT); + } + + // 执行 + Object obj = null; + try { + // 给 ticket 设置在执行中 + this.repeatSubmitTicket.putTicket(ticket); + // 执行 + obj = point.proceed(); + } catch (Throwable throwable) { + log.error("", throwable); + throw throwable; + } + return obj; + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/repeatsubmit/annoation/RepeatSubmit.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/repeatsubmit/annoation/RepeatSubmit.java new file mode 100644 index 0000000..7469ba6 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/repeatsubmit/annoation/RepeatSubmit.java @@ -0,0 +1,27 @@ +package net.lab1024.sa.base.module.support.repeatsubmit.annoation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 标记 需要防止重复提交 的注解
+ * 单位:毫秒 + * + * @Author 1024创新实验室: 胡克 + * @Date 2020-11-25 20:56:58 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface RepeatSubmit { + + /** + * 重复提交间隔时间/毫秒 + */ + int value() default 300; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/repeatsubmit/ticket/AbstractRepeatSubmitTicket.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/repeatsubmit/ticket/AbstractRepeatSubmitTicket.java new file mode 100644 index 0000000..bc2f8e6 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/repeatsubmit/ticket/AbstractRepeatSubmitTicket.java @@ -0,0 +1,42 @@ +package net.lab1024.sa.base.module.support.repeatsubmit.ticket; + +import java.util.function.Function; + +/** + * 凭证(用于校验重复提交的东西) + * + * @Author 1024创新实验室: 罗伊 + * @Date 2020-11-25 20:56:58 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public abstract class AbstractRepeatSubmitTicket { + + private final Function ticketFunction; + + + public AbstractRepeatSubmitTicket(Function ticketFunction) { + this.ticketFunction = ticketFunction; + } + + + /** + * 获取凭证 + */ + public String getTicket(String ticketToken) { + return this.ticketFunction.apply(ticketToken); + } + + /** + * 获取凭证 时间戳 + */ + public abstract Long getTicketTimestamp(String ticket); + + + /** + * 设置本次请求时间 + */ + public abstract void putTicket(String ticket); + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/repeatsubmit/ticket/RepeatSubmitCaffeineTicket.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/repeatsubmit/ticket/RepeatSubmitCaffeineTicket.java new file mode 100644 index 0000000..a2070a3 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/repeatsubmit/ticket/RepeatSubmitCaffeineTicket.java @@ -0,0 +1,45 @@ +package net.lab1024.sa.base.module.support.repeatsubmit.ticket; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import net.lab1024.sa.base.module.support.repeatsubmit.annoation.RepeatSubmit; + +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +/** + * 凭证(内存实现) + * + * @Author 1024创新实验室: 罗伊 + * @Date 2020-11-25 20:56:58 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class RepeatSubmitCaffeineTicket extends AbstractRepeatSubmitTicket { + + /** + * 限制缓存最大数量 超过后先放入的会自动移除 + * 默认缓存时间 + * 初始大小为:100万 + */ + private static final Cache cache = Caffeine.newBuilder() + .maximumSize(100 * 10000) + .expireAfterWrite(300 * 1000L, TimeUnit.MILLISECONDS).build(); + + + public RepeatSubmitCaffeineTicket(Function ticketFunction) { + super(ticketFunction); + } + + @Override + public Long getTicketTimestamp(String ticket) { + return cache.getIfPresent(ticket); + } + + + @Override + public void putTicket(String ticket) { + cache.put(ticket, System.currentTimeMillis()); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/repeatsubmit/ticket/RepeatSubmitRedisTicket.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/repeatsubmit/ticket/RepeatSubmitRedisTicket.java new file mode 100644 index 0000000..cb1404d --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/repeatsubmit/ticket/RepeatSubmitRedisTicket.java @@ -0,0 +1,40 @@ +package net.lab1024.sa.base.module.support.repeatsubmit.ticket; + +import net.lab1024.sa.base.module.support.repeatsubmit.annoation.RepeatSubmit; +import org.springframework.data.redis.core.ValueOperations; + +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +/** + * 凭证(redis实现) + * + * @Author 1024创新实验室: 罗伊 + * @Date 2020-11-25 20:56:58 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class RepeatSubmitRedisTicket extends AbstractRepeatSubmitTicket { + + private final ValueOperations redisValueOperations; + + public RepeatSubmitRedisTicket(ValueOperations redisValueOperations, + Function ticketFunction) { + super(ticketFunction); + this.redisValueOperations = redisValueOperations; + } + + @Override + public Long getTicketTimestamp(String ticket) { + String ticketLastTime = redisValueOperations.get(ticket); + return ticketLastTime == null ? null : Long.valueOf(ticketLastTime); + } + + @Override + public void putTicket(String ticket) { + redisValueOperations.getOperations().delete(ticket); + this.getTicketTimestamp(ticket); + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/dao/LoginFailDao.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/dao/LoginFailDao.java new file mode 100644 index 0000000..587dd43 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/dao/LoginFailDao.java @@ -0,0 +1,52 @@ +package net.lab1024.sa.base.module.support.securityprotect.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.base.module.support.securityprotect.domain.LoginFailEntity; +import net.lab1024.sa.base.module.support.securityprotect.domain.LoginFailQueryForm; +import net.lab1024.sa.base.module.support.securityprotect.domain.LoginFailVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 登录失败 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022/07/22 19:46:23 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface LoginFailDao extends BaseMapper { + + /** + * 根据用户id和类型查询 + * + * @param userId + * @param userType + * @return + */ + LoginFailEntity selectByUserIdAndUserType(@Param("userId") Long userId, @Param("userType") Integer userType); + + /** + * 根据用户id和类型查询 进行删除 + * + * @param userId + * @param userType + * @return + */ + void deleteByUserIdAndUserType(@Param("userId") Long userId, @Param("userType") Integer userType); + + /** + * 分页 查询 + * + * @param page + * @param queryForm + * @return + */ + List queryPage(Page page, @Param("queryForm") LoginFailQueryForm queryForm); +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/dao/PasswordLogDao.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/dao/PasswordLogDao.java new file mode 100644 index 0000000..77dae29 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/dao/PasswordLogDao.java @@ -0,0 +1,33 @@ +package net.lab1024.sa.base.module.support.securityprotect.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import net.lab1024.sa.base.module.support.securityprotect.domain.PasswordLogEntity; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Mapper +public interface PasswordLogDao extends BaseMapper { + + /** + * 查询最后一次修改密码记录 + * + * @param userType + * @param userId + * @return + */ + PasswordLogEntity selectLastByUserTypeAndUserId(@Param("userType") Integer userType, @Param("userId") Long userId); + + + /** + * 查询最近几次修改后的密码 + * + * @param userType + * @param userId + * @return + */ + List selectOldPassword(@Param("userType") Integer userType, @Param("userId") Long userId, @Param("limit") int limit); + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/domain/Level3ProtectConfigForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/domain/Level3ProtectConfigForm.java new file mode 100644 index 0000000..8333493 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/domain/Level3ProtectConfigForm.java @@ -0,0 +1,57 @@ +package net.lab1024.sa.base.module.support.securityprotect.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 三级等保相关配置 + * + * @Author 1024创新实验室-创始人兼主任:卓大 + * @Date 2024/7/30 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 ,Since 2012 + */ + +@Data +public class Level3ProtectConfigForm { + + @Schema(description = "连续登录失败次数则锁定") + @NotNull(message = "连续登录失败次数则锁定 不能为空") + private Integer loginFailMaxTimes; + + @Schema(description = "连续登录失败锁定时间(单位:分钟)") + @NotNull(message = "连续登录失败锁定时间(单位:分钟) 不能为空") + private Integer loginFailLockMinutes; + + @Schema(description = "最低活跃时间(单位:分钟)") + @NotNull(message = "最低活跃时间(单位:分钟) 不能为空") + private Integer loginActiveTimeoutMinutes; + + @Schema(description = "开启双因子登录") + @NotNull(message = "开启双因子登录 不能为空") + private Boolean twoFactorLoginEnabled; + + @Schema(description = "密码复杂度 是否开启,默认:开启") + @NotNull(message = "密码复杂度 是否开启 不能为空") + private Boolean passwordComplexityEnabled; + + @Schema(description = "定期修改密码时间间隔(默认:月)") + @NotNull(message = "定期修改密码时间间隔(默认:月) 不能为空") + private Integer regularChangePasswordMonths; + + @Schema(description = "定期修改密码不允许重复次数,默认:3次以内密码不能相同(默认:次)") + @NotNull(message = "定期修改密码不允许重复次数 不能为空") + private Integer regularChangePasswordNotAllowRepeatTimes; + + @Schema(description = "文件检测,默认:不开启") + @NotNull(message = "文件检测 是否开启 不能为空") + private Boolean fileDetectFlag; + + @Schema(description = "文件大小限制,单位 mb ,(默认:50 mb)") + @NotNull(message = "文件大小限制 不能为空") + private Long maxUploadFileSizeMb; + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/domain/LoginFailEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/domain/LoginFailEntity.java new file mode 100644 index 0000000..f1219b4 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/domain/LoginFailEntity.java @@ -0,0 +1,66 @@ +package net.lab1024.sa.base.module.support.securityprotect.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Builder; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 登录失败记录 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/10/11 19:29:18 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室,Since 2012 + */ + +@Data +@Builder +@TableName("t_login_fail") +public class LoginFailEntity { + + + @TableId(type = IdType.AUTO) + private Long loginFailId; + + /** + * 用户id + */ + private Long userId; + + /** + * 用户类型 + */ + private Integer userType; + + /** + * 登录名 + */ + private String loginName; + + /** + * 锁定状态 + */ + private Boolean lockFlag; + + /** + * 登录失败次数 + */ + private Integer loginFailCount; + + /** + * 连续登录失败锁定开始时间 + */ + private LocalDateTime loginLockBeginTime; + + + private LocalDateTime updateTime; + + private LocalDateTime createTime; + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/domain/LoginFailQueryForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/domain/LoginFailQueryForm.java new file mode 100644 index 0000000..41c048b --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/domain/LoginFailQueryForm.java @@ -0,0 +1,32 @@ +package net.lab1024.sa.base.module.support.securityprotect.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; + +import java.time.LocalDate; + +/** + * 登录失败 分页查询表单 + * + * @Author 1024创新实验室-主任-卓大 + * @Date 2023-10-17 18:02:37 + * @Copyright 1024创新实验室 + */ + +@Data +public class LoginFailQueryForm extends PageParam { + + @Schema(description = "登录名") + private String loginName; + + @Schema(description = "锁定状态") + private Boolean lockFlag; + + @Schema(description = "登录失败锁定时间") + private LocalDate loginLockBeginTimeBegin; + + @Schema(description = "登录失败锁定时间") + private LocalDate loginLockBeginTimeEnd; + +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/domain/LoginFailVO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/domain/LoginFailVO.java new file mode 100644 index 0000000..317ee74 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/domain/LoginFailVO.java @@ -0,0 +1,46 @@ +package net.lab1024.sa.base.module.support.securityprotect.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 登录失败 列表VO + * + * @Author 1024创新实验室-主任-卓大 + * @Date 2023-10-17 18:02:37 + * @Copyright 1024创新实验室 + */ + +@Data +public class LoginFailVO { + + private Long loginFailId; + + + @Schema(description = "用户id") + private Long userId; + + @Schema(description = "用户类型") + private Integer userType; + + @Schema(description = "登录名") + private String loginName; + + @Schema(description = "连续登录失败次数") + private Integer loginFailCount; + + @Schema(description = "锁定状态:1锁定,0未锁定") + private Integer lockFlag; + + @Schema(description = "连续登录失败锁定开始时间") + private LocalDateTime loginLockBeginTime; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "更新时间") + private LocalDateTime updateTime; + +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/domain/PasswordLogEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/domain/PasswordLogEntity.java new file mode 100644 index 0000000..93927dc --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/domain/PasswordLogEntity.java @@ -0,0 +1,43 @@ +package net.lab1024.sa.base.module.support.securityprotect.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * @author yandy + * @description: + * @date 2024/7/15 1:39 下午 + */ +@Data +@TableName("t_password_log") +public class PasswordLogEntity { + + /** + * 主键id + */ + @TableId(type = IdType.AUTO) + private Long id; + + private Integer userType; + + private Long userId; + + private String oldPassword; + + private String newPassword; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/Level3ProtectConfigService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/Level3ProtectConfigService.java new file mode 100644 index 0000000..00cb51e --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/Level3ProtectConfigService.java @@ -0,0 +1,211 @@ +package net.lab1024.sa.base.module.support.securityprotect.service; + +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.module.support.config.ConfigKeyEnum; +import net.lab1024.sa.base.module.support.config.ConfigService; +import net.lab1024.sa.base.module.support.securityprotect.domain.Level3ProtectConfigForm; +import org.springframework.stereotype.Service; + +/** + * 三级等保配置 + * + * @Author 1024创新实验室-创始人兼主任:卓大 + * @Date 2024/7/30 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 ,Since 2012 + */ + +@Service +@Slf4j +public class Level3ProtectConfigService { + + /** + * 开启双因子登录,默认:开启 + */ + private boolean twoFactorLoginEnabled = false; + + /** + * 连续登录失败次数则锁定,-1表示不受限制,可以一直尝试登录 + */ + private int loginFailMaxTimes = -1; + + /** + * 连续登录失败锁定时间(单位:秒),-1表示不锁定,建议锁定30分钟 + */ + private int loginFailLockSeconds = 1800; + + /** + * 最低活跃时间(单位:秒),超过此时间没有操作系统就会被冻结,默认-1 代表不限制,永不冻结; 默认 30分钟 + */ + private int loginActiveTimeoutSeconds = -1; + + /** + * 密码复杂度 是否开启,默认:开启 + */ + private boolean passwordComplexityEnabled = true; + + /** + * 定期修改密码时间间隔(默认:天),默认:建议90天更换密码 + */ + private int regularChangePasswordDays = 90; + + /** + * 定期修改密码不允许相同次数,默认:3次以内密码不能相同 + */ + private int regularChangePasswordNotAllowRepeatTimes = 3; + + /** + * 文件大小限制,单位 mb ,(默认:50 mb) + */ + private long maxUploadFileSizeMb = 50; + + /** + * 文件检测,默认:不开启 + */ + private boolean fileDetectFlag = false; + + + @Resource + private ConfigService configService; + + /** + * 文件检测,默认:不开启 + */ + public boolean isFileDetectFlag() { + return fileDetectFlag; + } + + /** + * 文件大小限制,单位 mb ,(默认:50 mb) + */ + public long getMaxUploadFileSizeMb() { + return maxUploadFileSizeMb; + } + + /** + * 连续登录失败次数则锁定,-1表示不受限制,可以一直尝试登录 + */ + public int getLoginFailMaxTimes() { + return loginFailMaxTimes; + } + + /** + * 连续登录失败锁定时间(单位:秒),-1表示不锁定,建议锁定30分钟 + */ + public int getLoginFailLockSeconds() { + return loginFailLockSeconds; + } + + /** + * 最低活跃时间(单位:秒),超过此时间没有操作系统就会被冻结,默认-1 代表不限制,永不冻结; 默认 30分钟 + */ + public int getLoginActiveTimeoutSeconds() { + return loginActiveTimeoutSeconds > 0 ? loginActiveTimeoutSeconds : -1; + } + + /** + * 定期修改密码时间间隔(默认:天),默认:建议90天更换密码 + */ + public int getRegularChangePasswordDays() { + return regularChangePasswordDays; + } + + /** + * 开启双因子登录,默认:开启 + */ + public boolean isTwoFactorLoginEnabled() { + return twoFactorLoginEnabled; + } + + /** + * 密码复杂度 是否开启,默认:开启 + */ + public boolean isPasswordComplexityEnabled() { + return passwordComplexityEnabled; + } + + /** + * 定期修改密码不允许相同次数,默认:3次以内密码不能相同 + */ + public int getRegularChangePasswordNotAllowRepeatTimes() { + return regularChangePasswordNotAllowRepeatTimes; + } + + @PostConstruct + void init() { + String configValue = configService.getConfigValue(ConfigKeyEnum.LEVEL3_PROTECT_CONFIG); + if (StrUtil.isEmpty(configValue)) { + throw new ExceptionInInitializerError("t_config 表 三级等保配置为空,请进行配置!"); + } + Level3ProtectConfigForm level3ProtectConfigForm = JSON.parseObject(configValue, Level3ProtectConfigForm.class); + setProp(level3ProtectConfigForm); + } + + /** + * 设置属性 + */ + private void setProp(Level3ProtectConfigForm configForm) { + + if (configForm.getFileDetectFlag() != null) { + this.fileDetectFlag = configForm.getFileDetectFlag(); + } + + if (configForm.getMaxUploadFileSizeMb() != null) { + this.maxUploadFileSizeMb = configForm.getMaxUploadFileSizeMb(); + } + + if (configForm.getLoginFailMaxTimes() != null) { + this.loginFailMaxTimes = configForm.getLoginFailMaxTimes(); + } + + if (configForm.getLoginFailLockMinutes() != null) { + this.loginFailLockSeconds = configForm.getLoginFailLockMinutes() * 60; + } + + if (configForm.getLoginActiveTimeoutMinutes() != null) { + this.loginActiveTimeoutSeconds = configForm.getLoginActiveTimeoutMinutes() * 60; + this.loginActiveTimeoutSeconds = loginActiveTimeoutSeconds > 0 ? loginActiveTimeoutSeconds : -1; + } + + if (configForm.getPasswordComplexityEnabled() != null) { + this.passwordComplexityEnabled = configForm.getPasswordComplexityEnabled(); + } + + if (configForm.getRegularChangePasswordMonths() != null) { + this.regularChangePasswordDays = configForm.getRegularChangePasswordMonths() * 30; + } + + if (configForm.getTwoFactorLoginEnabled() != null) { + this.twoFactorLoginEnabled = configForm.getTwoFactorLoginEnabled(); + } + + if (configForm.getRegularChangePasswordNotAllowRepeatTimes() != null) { + this.regularChangePasswordNotAllowRepeatTimes = configForm.getRegularChangePasswordNotAllowRepeatTimes(); + } + + // 设置 最低活跃时间(单位:秒) + if (this.loginActiveTimeoutSeconds > 0) { + StpUtil.getStpLogic().getConfigOrGlobal().setActiveTimeout(getLoginActiveTimeoutSeconds()); + } else { + StpUtil.getStpLogic().getConfigOrGlobal().setActiveTimeout(-1); + } + } + + /** + * 更新三级等保配置 + */ + public ResponseDTO updateLevel3Config(Level3ProtectConfigForm configForm) { + // 设置属性 + setProp(configForm); + // 保存数据库 + String configFormJsonString = JSON.toJSONString(configForm, true); + return configService.updateValueByKey(ConfigKeyEnum.LEVEL3_PROTECT_CONFIG, configFormJsonString); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/SecurityFileService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/SecurityFileService.java new file mode 100644 index 0000000..9ecfd75 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/SecurityFileService.java @@ -0,0 +1,120 @@ +package net.lab1024.sa.base.module.support.securityprotect.service; + +import lombok.extern.slf4j.Slf4j; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import org.apache.tika.config.TikaConfig; +import org.apache.tika.exception.TikaException; +import org.apache.tika.io.TikaInputStream; +import org.apache.tika.metadata.Metadata; +import org.apache.tika.metadata.TikaCoreProperties; +import org.apache.tika.mime.MediaType; +import org.apache.tika.mime.MimeTypes; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +/** + * 三级等保 文件 相关 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2024/08/22 19:25:59 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室,Since 2012 + */ + +@Service +@Slf4j +public class SecurityFileService { + + @Resource + private Level3ProtectConfigService level3ProtectConfigService; + + // 定义白名单MIME类型 + private static final List ALLOWED_MIME_TYPES = Arrays.asList( + "application/json", + "application/zip", + "application/x-7z-compressed", + "application/pdf", + "application/vnd.ms-excel", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "application/vnd.ms-powerpoint", + "application/vnd.openxmlformats-officedocument.presentationml.presentation", + "application/msword", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "application/vnd.ms-works", + "text/csv", + "audio/*", + "video/*", + // 图片类型 svg有安全隐患,所以不使用"image/*" + "image/jpeg", + "image/png", + "image/gif", + "image/bmp" + ); + + /** + * 检测文件安全类型 + */ + public ResponseDTO checkFile(MultipartFile file) { + + // 检验文件大小 + if (level3ProtectConfigService.getMaxUploadFileSizeMb() > 0) { + long maxSize = level3ProtectConfigService.getMaxUploadFileSizeMb() * 1024 * 1024; + if (file.getSize() > maxSize) { + return ResponseDTO.userErrorParam("上传文件最大为:" + level3ProtectConfigService.getMaxUploadFileSizeMb() + " mb"); + } + } + + // 文件类型安全检测 + if (level3ProtectConfigService.isFileDetectFlag()) { + String fileType = getFileMimeType(file); + if (ALLOWED_MIME_TYPES.stream() + .noneMatch(allowedType -> matchesMimeType(fileType, allowedType))) { + return ResponseDTO.userErrorParam("禁止上传此文件类型"); + } + } + + return ResponseDTO.ok(); + } + + /** + * 获取文件的 MIME 类型 + * + * @param file 要检查的文件 + * @return 文件的 MIME 类型 + */ + public static String getFileMimeType(MultipartFile file) { + try { + TikaConfig tika = new TikaConfig(); + Metadata metadata = new Metadata(); + metadata.set(TikaCoreProperties.RESOURCE_NAME_KEY, file.getOriginalFilename()); + TikaInputStream stream = TikaInputStream.get(file.getInputStream()); + MediaType mimetype = tika.getDetector().detect(stream, metadata); + return mimetype.toString(); + } catch (IOException | TikaException e) { + log.error(e.getMessage(), e); + return MimeTypes.OCTET_STREAM; + } + } + + /** + * 检查文件的 MIME 类型是否与指定的MIME 类型匹配(支持通配符) + * + * @param fileType 文件的 MIME 类型 + * @param mimetype MIME 类型(支持通配符) + * @return 是否匹配 + */ + private static boolean matchesMimeType(String fileType, String mimetype) { + if (mimetype.endsWith("/*")) { + String prefix = mimetype.substring(0, mimetype.length() - 1); + return fileType.startsWith(prefix); + } else { + return fileType.equalsIgnoreCase(mimetype); + } + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/SecurityLoginService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/SecurityLoginService.java new file mode 100644 index 0000000..c51eb0c --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/SecurityLoginService.java @@ -0,0 +1,175 @@ +package net.lab1024.sa.base.module.support.securityprotect.service; + +import cn.hutool.core.date.LocalDateTimeUtil; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.code.UserErrorCode; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.enumeration.UserTypeEnum; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.module.support.securityprotect.dao.LoginFailDao; +import net.lab1024.sa.base.module.support.securityprotect.domain.LoginFailEntity; +import net.lab1024.sa.base.module.support.securityprotect.domain.LoginFailQueryForm; +import net.lab1024.sa.base.module.support.securityprotect.domain.LoginFailVO; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 三级等保 登录 相关 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/10/11 19:25:59 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室,Since 2012 + */ + +@Service +public class SecurityLoginService { + + private static final String LOGIN_LOCK_MSG = "您已连续登录失败%s次,账号锁定%s分钟,解锁时间为:%s,请您耐心等待!"; + + private static final String LOGIN_FAIL_MSG = "登录名或密码错误!连续登录失败%s次,账号将锁定%s分钟!您还可以再尝试%s次!"; + + @Resource + private Level3ProtectConfigService level3ProtectConfigService; + + @Resource + private LoginFailDao loginFailDao; + + + /** + * 检查是否可以登录 + * + * @param userId + * @param userType + * @return + */ + public ResponseDTO checkLogin(Long userId, UserTypeEnum userType) { + + // 若登录最大失败次数小于1,无需校验 + if (level3ProtectConfigService.getLoginFailMaxTimes() < 1) { + return ResponseDTO.ok(); + } + + + LoginFailEntity loginFailEntity = loginFailDao.selectByUserIdAndUserType(userId, userType.getValue()); + if (loginFailEntity == null) { + return ResponseDTO.ok(); + } + + // 校验登录失败次数 + if (loginFailEntity.getLoginFailCount() < level3ProtectConfigService.getLoginFailMaxTimes()) { + return ResponseDTO.ok(loginFailEntity); + } + + // 校验是否锁定 + if (loginFailEntity.getLoginLockBeginTime() == null) { + return ResponseDTO.ok(loginFailEntity); + } + + // 校验锁定时长 + if (loginFailEntity.getLoginLockBeginTime().plusSeconds(level3ProtectConfigService.getLoginFailLockSeconds()).isBefore(LocalDateTime.now())) { + // 过了锁定时间 + return ResponseDTO.ok(loginFailEntity); + } + + LocalDateTime unlockTime = loginFailEntity.getLoginLockBeginTime().plusSeconds(level3ProtectConfigService.getLoginFailLockSeconds()); + return ResponseDTO.error(UserErrorCode.LOGIN_FAIL_LOCK, String.format(LOGIN_LOCK_MSG, loginFailEntity.getLoginFailCount(), level3ProtectConfigService.getLoginFailLockSeconds() / 60, LocalDateTimeUtil.formatNormal(unlockTime))); + } + + /** + * 登录失败后记录 + * + * @param userId + * @param userType + * @param loginFailEntity + */ + public String recordLoginFail(Long userId, UserTypeEnum userType, String loginName, LoginFailEntity loginFailEntity) { + + // 若登录最大失败次数小于1,无需记录 + if (level3ProtectConfigService.getLoginFailMaxTimes() < 1) { + return null; + } + + // 登录失败 + int loginFailCount = loginFailEntity == null ? 1 : loginFailEntity.getLoginFailCount() + 1; + boolean lockFlag = loginFailCount >= level3ProtectConfigService.getLoginFailMaxTimes(); + LocalDateTime lockBeginTime = lockFlag ? LocalDateTime.now() : null; + + if (loginFailEntity == null) { + loginFailEntity = LoginFailEntity.builder() + .userId(userId) + .userType(userType.getValue()) + .loginName(loginName) + .loginFailCount(loginFailCount) + .lockFlag(lockFlag) + .loginLockBeginTime(lockBeginTime) + .build(); + loginFailDao.insert(loginFailEntity); + } else { + loginFailEntity.setLoginLockBeginTime(lockBeginTime); + loginFailEntity.setLoginFailCount(loginFailCount); + loginFailEntity.setLockFlag(lockFlag); + loginFailEntity.setLoginName(loginName); + loginFailDao.updateById(loginFailEntity); + } + + // 提示信息 + if (lockFlag) { + LocalDateTime unlockTime = loginFailEntity.getLoginLockBeginTime().plusSeconds(level3ProtectConfigService.getLoginFailLockSeconds()); + return String.format(LOGIN_LOCK_MSG, loginFailEntity.getLoginFailCount(), level3ProtectConfigService.getLoginFailLockSeconds() / 60, LocalDateTimeUtil.formatNormal(unlockTime)); + } else { + return String.format(LOGIN_FAIL_MSG, level3ProtectConfigService.getLoginFailMaxTimes(), level3ProtectConfigService.getLoginFailLockSeconds() / 60, level3ProtectConfigService.getLoginFailMaxTimes() - loginFailEntity.getLoginFailCount()); + } + } + + /** + * 清除登录失败 + * + * @param userId + * @param userType + */ + public void removeLoginFail(Long userId, UserTypeEnum userType) { + + // 若登录最大失败次数小于1,无需校验 + if (level3ProtectConfigService.getLoginFailMaxTimes() < 1) { + return; + } + + loginFailDao.deleteByUserIdAndUserType(userId, userType.getValue()); + } + + /** + * 分页查询 + * + * @param queryForm + * @return + */ + public PageResult queryPage(LoginFailQueryForm queryForm) { + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List list = loginFailDao.queryPage(page, queryForm); + return SmartPageUtil.convert2PageResult(page, list); + } + + /** + * 批量删除 + * + * @param idList + * @return + */ + public ResponseDTO batchDelete(List idList) { + if (CollectionUtils.isEmpty(idList)) { + return ResponseDTO.ok(); + } + + loginFailDao.deleteBatchIds(idList); + return ResponseDTO.ok(); + } + +} +; \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/SecurityPasswordService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/SecurityPasswordService.java new file mode 100644 index 0000000..3cd8305 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/SecurityPasswordService.java @@ -0,0 +1,157 @@ +package net.lab1024.sa.base.module.support.securityprotect.service; + +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.domain.RequestUser; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.module.support.securityprotect.dao.PasswordLogDao; +import net.lab1024.sa.base.module.support.securityprotect.domain.PasswordLogEntity; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.lang3.RandomStringUtils; +import org.springframework.security.crypto.argon2.Argon2PasswordEncoder; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +/** + * 三级等保 密码 相关 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/10/11 19:25:59 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室,Since 2012 + */ + +@Service +public class SecurityPasswordService { + + /** + * 密码长度8-20位且包含大小写字母、数字、特殊符号三种及以上组合 + */ + public static final String PASSWORD_PATTERN = "^(?![a-zA-Z]+$)(?![A-Z0-9]+$)(?![A-Z\\W_!@#$%^&*`~()-+=]+$)(?![a-z0-9]+$)(?![a-z\\W_!@#$%^&*`~()-+=]+$)(?![0-9\\W_!@#$%^&*`~()-+=]+$)[a-zA-Z0-9\\W_!@#$%^&*`~()-+=]*$"; + + public static final String PASSWORD_FORMAT_MSG = "密码必须为长度8-20位且必须包含大小写字母、数字、特殊符号(如:@#$%^&*()_+-=)等三种字符"; + + private static final int PASSWORD_LENGTH = 8; + + + @Resource + private PasswordLogDao passwordLogDao; + + @Resource + private Level3ProtectConfigService level3ProtectConfigService; + + static Argon2PasswordEncoder ARGON2_PASSWORD_ENCODER = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8(); + + /** + * 校验密码复杂度 + */ + public ResponseDTO validatePasswordComplexity(String password) { + + if (SmartStringUtil.isEmpty(password)) { + return ResponseDTO.userErrorParam(PASSWORD_FORMAT_MSG); + } + + // 密码长度必须大于等于8位 + if (password.length() < PASSWORD_LENGTH) { + return ResponseDTO.userErrorParam(PASSWORD_FORMAT_MSG); + } + + // 无需校验 密码复杂度 + if (!level3ProtectConfigService.isPasswordComplexityEnabled()) { + return ResponseDTO.ok(); + } + + if (!password.matches(PASSWORD_PATTERN)) { + return ResponseDTO.userErrorParam(PASSWORD_FORMAT_MSG); + } + + return ResponseDTO.ok(); + } + + /** + * 校验密码重复次数 + */ + public ResponseDTO validatePasswordRepeatTimes(RequestUser requestUser, String newPassword) { + + // 密码重复次数小于1 无需校验 + if (level3ProtectConfigService.getRegularChangePasswordNotAllowRepeatTimes() < 1) { + return ResponseDTO.ok(); + } + + // 检查最近几次是否有重复密码 + List oldPasswords = passwordLogDao.selectOldPassword(requestUser.getUserType().getValue(), requestUser.getUserId(), level3ProtectConfigService.getRegularChangePasswordNotAllowRepeatTimes()); + boolean isDuplicate = oldPasswords.stream().anyMatch(oldPassword -> ARGON2_PASSWORD_ENCODER.matches(newPassword, oldPassword)); + if (isDuplicate) { + return ResponseDTO.userErrorParam(String.format("与前%d个历史密码重复,请换个密码!", level3ProtectConfigService.getRegularChangePasswordNotAllowRepeatTimes())); + } + + return ResponseDTO.ok(); + } + + /** + * 随机生成密码 + */ + public String randomPassword() { + // 未开启密码复杂度,则由8为数字构成 + if (!level3ProtectConfigService.isPasswordComplexityEnabled()) { + return RandomStringUtils.randomNumeric(PASSWORD_LENGTH); + } + + // 3位大写字母,2位数字,2位小写字母 + 1位特殊符号 + return RandomStringUtils.randomAlphabetic(3).toUpperCase() + + RandomStringUtils.randomNumeric(2) + + RandomStringUtils.randomAlphabetic(2).toLowerCase() + + (ThreadLocalRandom.current().nextBoolean() ? "#" : "@"); + } + + + /** + * 保存修改密码 + */ + public void saveUserChangePasswordLog(RequestUser requestUser, String newPassword, String oldPassword) { + + PasswordLogEntity passwordLogEntity = new PasswordLogEntity(); + passwordLogEntity.setNewPassword(newPassword); + passwordLogEntity.setOldPassword(oldPassword); + passwordLogEntity.setUserId(requestUser.getUserId()); + passwordLogEntity.setUserType(requestUser.getUserType().getValue()); + passwordLogDao.insert(passwordLogEntity); + } + + /** + * 检查是否需要修改密码 + */ + public boolean checkNeedChangePassword(Integer userType, Long userId) { + + if (level3ProtectConfigService.getRegularChangePasswordDays() < 1) { + return false; + } + + PasswordLogEntity passwordLogEntity = passwordLogDao.selectLastByUserTypeAndUserId(userType, userId); + if (passwordLogEntity == null) { + return false; + } + + LocalDateTime nextUpdateTime = passwordLogEntity.getCreateTime().plusDays(level3ProtectConfigService.getRegularChangePasswordDays()); + return nextUpdateTime.isBefore(LocalDateTime.now()); + } + + /** + * 获取 加密后 的密码 + */ + public static String getEncryptPwd(String password) { + return ARGON2_PASSWORD_ENCODER.encode(password); + } + + /** + * 校验密码是否匹配 + */ + public static Boolean matchesPwd(String password, String encodedPassword) { + return ARGON2_PASSWORD_ENCODER.matches(password, encodedPassword); + } + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/constant/SerialNumberIdEnum.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/constant/SerialNumberIdEnum.java new file mode 100644 index 0000000..6a12d41 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/constant/SerialNumberIdEnum.java @@ -0,0 +1,42 @@ +package net.lab1024.sa.base.module.support.serialnumber.constant; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 单据序列号 枚举 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-25 21:46:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@AllArgsConstructor +@Getter +public enum SerialNumberIdEnum implements BaseEnum { + + ORDER(1, "订单id"), + + CONTRACT(2, "合同id"), + + ; + + private final Integer serialNumberId; + + private final String desc; + + @Override + public Integer getValue() { + return serialNumberId; + } + + @Override + public String toString() { + return "SerialNumberIdEnum{" + + "serialNumberId=" + serialNumberId + + ", desc='" + desc + '\'' + + '}'; + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/constant/SerialNumberRuleTypeEnum.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/constant/SerialNumberRuleTypeEnum.java new file mode 100644 index 0000000..01f4f42 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/constant/SerialNumberRuleTypeEnum.java @@ -0,0 +1,44 @@ +package net.lab1024.sa.base.module.support.serialnumber.constant; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.lab1024.sa.base.common.constant.StringConst; +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 单据序列号 周期 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-25 21:46:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@AllArgsConstructor +@Getter +public enum SerialNumberRuleTypeEnum implements BaseEnum { + /** + * 没有周期 + */ + NONE(StringConst.EMPTY, "", "没有周期"), + /** + * 年周期 + */ + YEAR("[yyyy]", "\\[yyyy\\]", "年"), + /** + * 月周期 + */ + MONTH("[mm]", "\\[mm\\]", "年月"), + /** + * 日周期 + */ + DAY("[dd]", "\\[dd\\]", "年月日"); + + private final String value; + + private final String regex; + + private final String desc; + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/dao/SerialNumberDao.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/dao/SerialNumberDao.java new file mode 100644 index 0000000..6e3303b --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/dao/SerialNumberDao.java @@ -0,0 +1,40 @@ +package net.lab1024.sa.base.module.support.serialnumber.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberEntity; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; + +/** + * 单据序列号 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-25 21:46:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface SerialNumberDao extends BaseMapper { + + /** + * 排他锁查询 + * + * @param serialNumberId + * @return + */ + SerialNumberEntity selectForUpdate(@Param("serialNumberId") Integer serialNumberId); + + /** + * 更新上一次的 数值和时间 + * + * @param serialNumberId + * @param lastNumber + * @param lastTime + */ + void updateLastNumberAndTime(@Param("serialNumberId") Integer serialNumberId, @Param("lastNumber") Long lastNumber, @Param("lastTime") LocalDateTime lastTime); + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/dao/SerialNumberRecordDao.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/dao/SerialNumberRecordDao.java new file mode 100644 index 0000000..f363cd6 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/dao/SerialNumberRecordDao.java @@ -0,0 +1,54 @@ +package net.lab1024.sa.base.module.support.serialnumber.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberRecordEntity; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberRecordQueryForm; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.time.LocalDate; +import java.util.List; + +/** + * 单据序列号 生成的记录 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-25 21:46:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface SerialNumberRecordDao extends BaseMapper { + + /** + * 根据 id和日期 查询 记录id + * + * @param serialNumberId + * @param recordDate + * @return + */ + Long selectRecordIdBySerialNumberIdAndDate(@Param("serialNumberId") Integer serialNumberId, @Param("recordDate") String recordDate); + + /** + * 更新记录 + * + * @param serialNumberId + * @param recordDate + * @param lastNumber + * @param count + * @return + */ + Long updateRecord(@Param("serialNumberId") Integer serialNumberId, @Param("recordDate") LocalDate recordDate, @Param("lastNumber") Long lastNumber, @Param("count") int count); + + /** + * 分页查询记录 + * + * @param page + * @param queryForm + * @return + */ + List query(Page page, @Param("queryForm") SerialNumberRecordQueryForm queryForm); +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberEntity.java new file mode 100644 index 0000000..46ae303 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberEntity.java @@ -0,0 +1,79 @@ +package net.lab1024.sa.base.module.support.serialnumber.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import net.lab1024.sa.base.module.support.serialnumber.constant.SerialNumberIdEnum; +import net.lab1024.sa.base.module.support.serialnumber.constant.SerialNumberRuleTypeEnum; + +import java.time.LocalDateTime; + +/** + * 单据序列号 定义表 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-25 21:46:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName("t_serial_number") +public class SerialNumberEntity { + + /** + * 主键id + * + * @see SerialNumberIdEnum + */ + @TableId(type = IdType.INPUT) + private Integer serialNumberId; + + /** + * 业务 + */ + private String businessName; + + /** + * 格式 + */ + private String format; + + /** + * 生成规则 + * + * @see SerialNumberRuleTypeEnum + */ + private String ruleType; + + + /** + * 初始值 + */ + private Long initNumber; + + /** + * 步长随机数范围 + */ + private Integer stepRandomRange; + + /** + * 备注 + */ + private String remark; + + /** + * 上次产生的单号, 默认为空 + */ + private Long lastNumber; + + /** + * 上次产生的单号时间 + */ + private LocalDateTime lastTime; + + private LocalDateTime updateTime; + + private LocalDateTime createTime; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberGenerateForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberGenerateForm.java new file mode 100644 index 0000000..0308cfd --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberGenerateForm.java @@ -0,0 +1,27 @@ +package net.lab1024.sa.base.module.support.serialnumber.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 单据序列号 生成表单 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-25 21:46:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class SerialNumberGenerateForm { + + @Schema(description = "单号id") + @NotNull(message = "单号id不能为空") + private Integer serialNumberId; + + @Schema(description = "生成的数量") + @NotNull(message = "生成的数量") + private Integer count; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberGenerateResultBO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberGenerateResultBO.java new file mode 100644 index 0000000..6250b61 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberGenerateResultBO.java @@ -0,0 +1,52 @@ +package net.lab1024.sa.base.module.support.serialnumber.domain; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 单据序列号 生成结果 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-25 21:46:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class SerialNumberGenerateResultBO { + + /** + * 序号id + */ + private Integer serialNumberId; + + /** + * 是否重置的初始值 + */ + private Boolean isReset; + + /** + * 上次生成的数字 + */ + private Long lastNumber; + + /** + * 上次生成的时间 + */ + private LocalDateTime lastTime; + + /** + * 生成的 number 集合 + */ + private List numberList; + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberInfoBO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberInfoBO.java new file mode 100644 index 0000000..4bb3071 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberInfoBO.java @@ -0,0 +1,97 @@ +package net.lab1024.sa.base.module.support.serialnumber.domain; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import net.lab1024.sa.base.module.support.serialnumber.constant.SerialNumberIdEnum; +import net.lab1024.sa.base.module.support.serialnumber.constant.SerialNumberRuleTypeEnum; + +/** + * 单据序列号 信息 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-25 21:46:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class SerialNumberInfoBO { + + /** + * 主键id + * + * @see SerialNumberIdEnum + */ + private Integer serialNumberId; + + /** + * 业务 + */ + private String businessName; + + /** + * 格式 + */ + private String format; + + /** + * 生成规则 + * + * @see SerialNumberRuleTypeEnum + */ + private String ruleType; + + + /** + * 初始值 + */ + private Long initNumber; + + /** + * 步长随机数范围 + */ + private Integer stepRandomRange; + + /** + * 备注 + */ + private String remark; + + /** + * 规则枚举 + */ + private SerialNumberRuleTypeEnum serialNumberRuleTypeEnum; + + + /** + * 存在[nnnnnn]中 n 的数量 + */ + private Integer numberCount; + + /** + * [nnnnnn] 的格式(主要用于替换) + */ + private String numberFormat; + + /** + * 是否存在年份 + */ + private Boolean haveYearFlag; + + /** + * 是否存在月份 + */ + private Boolean haveMonthFlag; + + /** + * 是否存在 月 + */ + private Boolean haveDayFlag; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberLastGenerateBO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberLastGenerateBO.java new file mode 100644 index 0000000..f695df0 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberLastGenerateBO.java @@ -0,0 +1,47 @@ +package net.lab1024.sa.base.module.support.serialnumber.domain; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +/** + * 上次生成信息 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-25 21:46:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class SerialNumberLastGenerateBO { + + /** + * 序号id + */ + private Integer serialNumberId; + + /** + * 上次生成的数字 + */ + private Long lastNumber; + + /** + * 上次生成的时间 + */ + @JsonDeserialize(using = LocalDateTimeDeserializer.class) + @JsonSerialize(using = LocalDateTimeSerializer.class) + private LocalDateTime lastTime; + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberRecordEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberRecordEntity.java new file mode 100644 index 0000000..f7c3a7d --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberRecordEntity.java @@ -0,0 +1,60 @@ +package net.lab1024.sa.base.module.support.serialnumber.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +/** + * 单据序列号 表结构 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-25 21:46:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@TableName("t_serial_number_record") +public class SerialNumberRecordEntity { + + /** + * 单号id + */ + @TableId(type= IdType.NONE) + private Integer serialNumberId; + + /** + * 记录日期 + */ + private LocalDate recordDate; + + /** + * 最后更新值 + */ + private Long lastNumber; + + /** + * 上次生成时间 + */ + private LocalDateTime lastTime; + + /** + * 数量 + */ + private Long count; + + private LocalDateTime updateTime; + + private LocalDateTime createTime; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberRecordQueryForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberRecordQueryForm.java new file mode 100644 index 0000000..95041bb --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberRecordQueryForm.java @@ -0,0 +1,23 @@ +package net.lab1024.sa.base.module.support.serialnumber.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; + +/** + * 单据序列号 生成记录 查询 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-25 21:46:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class SerialNumberRecordQueryForm extends PageParam { + + @Schema(description = "单号id") + @NotNull(message = "单号id不能为空") + private Integer serialNumberId; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/service/SerialNumberBaseService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/service/SerialNumberBaseService.java new file mode 100644 index 0000000..6380f7e --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/service/SerialNumberBaseService.java @@ -0,0 +1,252 @@ +package net.lab1024.sa.base.module.support.serialnumber.service; + +import com.google.common.collect.Lists; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.exception.BusinessException; +import net.lab1024.sa.base.common.util.SmartEnumUtil; +import net.lab1024.sa.base.module.support.serialnumber.constant.SerialNumberIdEnum; +import net.lab1024.sa.base.module.support.serialnumber.constant.SerialNumberRuleTypeEnum; +import net.lab1024.sa.base.module.support.serialnumber.dao.SerialNumberDao; +import net.lab1024.sa.base.module.support.serialnumber.dao.SerialNumberRecordDao; +import net.lab1024.sa.base.module.support.serialnumber.domain.*; +import org.apache.commons.lang3.RandomUtils; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 单据序列号 基类 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-25 21:46:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public abstract class SerialNumberBaseService implements SerialNumberService { + + @Resource + protected SerialNumberRecordDao serialNumberRecordDao; + + @Resource + protected SerialNumberDao serialNumberDao; + + private ConcurrentHashMap serialNumberMap = new ConcurrentHashMap<>(); + + public abstract List generateSerialNumberList(SerialNumberInfoBO serialNumber, int count); + + @PostConstruct + void init() { + List serialNumberEntityList = serialNumberDao.selectList(null); + if (serialNumberEntityList == null) { + return; + } + for (SerialNumberEntity serialNumberEntity : serialNumberEntityList) { + SerialNumberRuleTypeEnum ruleTypeEnum = SmartEnumUtil.getEnumByName(serialNumberEntity.getRuleType().toUpperCase(), SerialNumberRuleTypeEnum.class); + if (ruleTypeEnum == null) { + throw new ExceptionInInitializerError("cannot find rule type , id : " + serialNumberEntity.getSerialNumberId()); + } + + String format = serialNumberEntity.getFormat(); + int startIndex = format.indexOf("[n"); + int endIndex = format.indexOf("n]"); + if (startIndex == -1 || endIndex == -1 || endIndex <= startIndex) { + throw new ExceptionInInitializerError("[nnn] 配置错误,请仔细查看 id : " + serialNumberEntity.getSerialNumberId()); + } + + String numberFormat = format.substring(startIndex + 1, endIndex + 1); + + if (serialNumberEntity.getStepRandomRange() < 1) { + throw new ExceptionInInitializerError("random step range must greater than 1 " + serialNumberEntity.getSerialNumberId()); + } + + SerialNumberInfoBO serialNumberInfoBO = SerialNumberInfoBO.builder() + .serialNumberId(serialNumberEntity.getSerialNumberId()) + .serialNumberRuleTypeEnum(ruleTypeEnum) + .initNumber(serialNumberEntity.getInitNumber()) + .format(serialNumberEntity.getFormat()) + .stepRandomRange(serialNumberEntity.getStepRandomRange()) + .haveDayFlag(format.contains(SerialNumberRuleTypeEnum.DAY.getValue())) + .haveMonthFlag(format.contains(SerialNumberRuleTypeEnum.MONTH.getValue())) + .haveYearFlag(format.contains(SerialNumberRuleTypeEnum.YEAR.getValue())) + .numberCount(endIndex - startIndex) + .numberFormat("\\[" + numberFormat + "\\]") + .build(); + + this.serialNumberMap.put(serialNumberEntity.getSerialNumberId(), serialNumberInfoBO); + } + + //初始化数据 + initLastGenerateData(serialNumberEntityList); + } + + /** + * 初始化上次生成的数据 + * + * @param serialNumberEntityList + */ + public abstract void initLastGenerateData(List serialNumberEntityList); + + @Override + public String generate(SerialNumberIdEnum serialNumberIdEnum) { + List generateList = this.generate(serialNumberIdEnum, 1); + if (generateList == null || generateList.isEmpty()) { + throw new BusinessException("cannot generate : " + serialNumberIdEnum.toString()); + } + return generateList.get(0); + } + + @Override + public List generate(SerialNumberIdEnum serialNumberIdEnum, int count) { + SerialNumberInfoBO serialNumberInfoBO = serialNumberMap.get(serialNumberIdEnum.getSerialNumberId()); + if (serialNumberInfoBO == null) { + throw new BusinessException("cannot found SerialNumberId : " + serialNumberIdEnum.toString()); + } + return this.generateSerialNumberList(serialNumberInfoBO, count); + } + + /** + * 循环生成 number 集合 + * + * @param lastGenerate + * @param serialNumberInfo + * @param count + * @return + */ + protected SerialNumberGenerateResultBO loopNumberList(SerialNumberLastGenerateBO lastGenerate, SerialNumberInfoBO serialNumberInfo, int count) { + Long lastNumber = lastGenerate.getLastNumber(); + boolean isReset = false; + if (isResetInitNumber(lastGenerate, serialNumberInfo)) { + lastNumber = serialNumberInfo.getInitNumber(); + isReset = true; + } + + ArrayList numberList = Lists.newArrayListWithCapacity(count); + for (int i = 0; i < count; i++) { + Integer stepRandomRange = serialNumberInfo.getStepRandomRange(); +// if (stepRandomRange > 1) { +// lastNumber = lastNumber + RandomUtils.nextInt(1, stepRandomRange + 1); +// } else { + lastNumber = lastNumber + 1; +// } + + numberList.add(lastNumber); + } + + return SerialNumberGenerateResultBO + .builder() + .serialNumberId(serialNumberInfo.getSerialNumberId()) + .lastNumber(lastNumber) + .lastTime(LocalDateTime.now()) + .numberList(numberList) + .isReset(isReset) + .build(); + } + + protected void saveRecord(SerialNumberGenerateResultBO resultBO) { + Long effectRows = serialNumberRecordDao.updateRecord(resultBO.getSerialNumberId(), + resultBO.getLastTime().toLocalDate(), + resultBO.getLastNumber(), + resultBO.getNumberList().size() + ); + + // 需要插入 + if (effectRows == null || effectRows == 0) { + SerialNumberRecordEntity recordEntity = SerialNumberRecordEntity.builder() + .serialNumberId(resultBO.getSerialNumberId()) + .recordDate(LocalDate.now()) + .lastTime(resultBO.getLastTime()) + .lastNumber(resultBO.getLastNumber()) + .count((long) resultBO.getNumberList().size()) + .build(); + serialNumberRecordDao.insert(recordEntity); + } + + } + + /** + * 若不在规则周期内,重制初始值 + * + * @return + */ + private boolean isResetInitNumber(SerialNumberLastGenerateBO lastGenerate, SerialNumberInfoBO serialNumberInfo) { + LocalDateTime lastTime = lastGenerate.getLastTime(); + if (lastTime == null) { + return true; + } + + SerialNumberRuleTypeEnum serialNumberRuleTypeEnum = serialNumberInfo.getSerialNumberRuleTypeEnum(); + int lastTimeYear = lastTime.getYear(); + int lastTimeMonth = lastTime.getMonthValue(); + int lastTimeDay = lastTime.getDayOfYear(); + + LocalDateTime now = LocalDateTime.now(); + + switch (serialNumberRuleTypeEnum) { + case YEAR: + return lastTimeYear != now.getYear(); + case MONTH: + return lastTimeYear != now.getYear() || lastTimeMonth != now.getMonthValue(); + case DAY: + return lastTimeYear != now.getYear() || lastTimeDay != now.getDayOfYear(); + default: + return false; + } + } + + /** + * 替换特殊rule,即替换[yyyy][mm][dd][nnn]等规则 + */ + protected List formatNumberList(SerialNumberGenerateResultBO generateResult, SerialNumberInfoBO serialNumberInfo) { + + /** + * 第一步:替换年、月、日 + */ + LocalDate lastTime = generateResult.getLastTime().toLocalDate(); + String year = String.valueOf(lastTime.getYear()); + String month = lastTime.getMonthValue() > 9 ? String.valueOf(lastTime.getMonthValue()) : "0" + lastTime.getMonthValue(); + String day = lastTime.getDayOfMonth() > 9 ? String.valueOf(lastTime.getDayOfMonth()) : "0" + lastTime.getDayOfMonth(); + + // 把年月日替换 + String format = serialNumberInfo.getFormat(); + + if (serialNumberInfo.getHaveYearFlag()) { + format = format.replaceAll(SerialNumberRuleTypeEnum.YEAR.getRegex(), year); + } + if (serialNumberInfo.getHaveMonthFlag()) { + format = format.replaceAll(SerialNumberRuleTypeEnum.MONTH.getRegex(), month); + } + if (serialNumberInfo.getHaveDayFlag()) { + format = format.replaceAll(SerialNumberRuleTypeEnum.DAY.getRegex(), day); + } + + + /** + * 第二步:替换数字 + */ + + List numberList = Lists.newArrayListWithCapacity(generateResult.getNumberList().size()); + for (Long number : generateResult.getNumberList()) { + StringBuilder numberStringBuilder = new StringBuilder(); + int currentNumberCount = String.valueOf(number).length(); + //数量不够,前面补0 + if (serialNumberInfo.getNumberCount() > currentNumberCount) { + int remain = serialNumberInfo.getNumberCount() - currentNumberCount; + for (int i = 0; i < remain; i++) { + numberStringBuilder.append(0); + } + } + numberStringBuilder.append(number); + //最终替换 + String finalNumber = format.replaceAll(serialNumberInfo.getNumberFormat(), numberStringBuilder.toString()); + numberList.add(finalNumber); + } + return numberList; + } + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/service/SerialNumberRecordService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/service/SerialNumberRecordService.java new file mode 100644 index 0000000..d102ea5 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/service/SerialNumberRecordService.java @@ -0,0 +1,34 @@ +package net.lab1024.sa.base.module.support.serialnumber.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.module.support.serialnumber.dao.SerialNumberRecordDao; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberRecordEntity; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberRecordQueryForm; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 单据序列号 记录 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-25 21:46:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class SerialNumberRecordService { + + @Resource + private SerialNumberRecordDao serialNumberRecordDao; + + public PageResult query(SerialNumberRecordQueryForm queryForm) { + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List recordList = serialNumberRecordDao.query(page, queryForm); + return SmartPageUtil.convert2PageResult(page, recordList); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/service/SerialNumberService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/service/SerialNumberService.java new file mode 100644 index 0000000..50d1dbe --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/service/SerialNumberService.java @@ -0,0 +1,36 @@ +package net.lab1024.sa.base.module.support.serialnumber.service; + +import net.lab1024.sa.base.module.support.serialnumber.constant.SerialNumberIdEnum; + +import java.util.List; + +/** + * 单据序列号 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-25 21:46:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public interface SerialNumberService { + + /** + * 生成 + * + * @param serialNumberIdEnum + * @return + */ + String generate(SerialNumberIdEnum serialNumberIdEnum); + + + /** + * 生成n个 + * + * @param serialNumberIdEnum + * @param count + * @return + */ + List generate(SerialNumberIdEnum serialNumberIdEnum, int count); + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/service/impl/SerialNumberInternService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/service/impl/SerialNumberInternService.java new file mode 100644 index 0000000..8a21ebc --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/service/impl/SerialNumberInternService.java @@ -0,0 +1,78 @@ +package net.lab1024.sa.base.module.support.serialnumber.service.impl; + +import com.google.common.collect.Interner; +import com.google.common.collect.Interners; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberEntity; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberGenerateResultBO; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberInfoBO; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberLastGenerateBO; +import net.lab1024.sa.base.module.support.serialnumber.service.SerialNumberBaseService; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 单据序列号 基于内存锁实现 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-25 21:46:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class SerialNumberInternService extends SerialNumberBaseService { + + /** + * 按照 serialNumberId 进行锁 + */ + private static final Interner POOL = Interners.newStrongInterner(); + + + private ConcurrentHashMap serialNumberLastGenerateMap = new ConcurrentHashMap<>(); + + @Override + public void initLastGenerateData(List serialNumberEntityList) { + if (serialNumberEntityList == null) { + return; + } + + for (SerialNumberEntity serialNumberEntity : serialNumberEntityList) { + SerialNumberLastGenerateBO lastGenerateBO = SerialNumberLastGenerateBO + .builder() + .serialNumberId(serialNumberEntity.getSerialNumberId()) + .lastNumber(serialNumberEntity.getLastNumber()) + .lastTime(serialNumberEntity.getLastTime()) + .build(); + serialNumberLastGenerateMap.put(serialNumberEntity.getSerialNumberId(), lastGenerateBO); + } + } + + @Override + public List generateSerialNumberList(SerialNumberInfoBO serialNumberInfo, int count) { + SerialNumberGenerateResultBO serialNumberGenerateResult = null; + synchronized (POOL.intern(serialNumberInfo.getSerialNumberId())) { + + // 获取上次的生成结果 + SerialNumberLastGenerateBO lastGenerateBO = serialNumberLastGenerateMap.get(serialNumberInfo.getSerialNumberId()); + + // 生成 + serialNumberGenerateResult = super.loopNumberList(lastGenerateBO, serialNumberInfo, count); + + // 将生成信息保存的内存和数据库 + lastGenerateBO.setLastNumber(serialNumberGenerateResult.getLastNumber()); + lastGenerateBO.setLastTime(serialNumberGenerateResult.getLastTime()); + serialNumberDao.updateLastNumberAndTime(serialNumberInfo.getSerialNumberId(), + serialNumberGenerateResult.getLastNumber(), + serialNumberGenerateResult.getLastTime()); + + // 把生成过程保存到数据库里 + super.saveRecord(serialNumberGenerateResult); + } + + return formatNumberList(serialNumberGenerateResult, serialNumberInfo); + } + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/service/impl/SerialNumberMysqlService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/service/impl/SerialNumberMysqlService.java new file mode 100644 index 0000000..0d13dc7 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/service/impl/SerialNumberMysqlService.java @@ -0,0 +1,61 @@ +package net.lab1024.sa.base.module.support.serialnumber.service.impl; + +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.exception.BusinessException; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberEntity; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberGenerateResultBO; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberInfoBO; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberLastGenerateBO; +import net.lab1024.sa.base.module.support.serialnumber.service.SerialNumberBaseService; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/** + * 单据序列号 基于mysql锁实现 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-25 21:46:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +public class SerialNumberMysqlService extends SerialNumberBaseService { + + @Override + @Transactional(rollbackFor = Throwable.class) + public List generateSerialNumberList(SerialNumberInfoBO serialNumberInfo, int count) { + // // 获取上次的生成结果 + SerialNumberEntity serialNumberEntity = serialNumberDao.selectForUpdate(serialNumberInfo.getSerialNumberId()); + if (serialNumberEntity == null) { + throw new BusinessException("cannot found SerialNumberId 数据库不存在:" + serialNumberInfo.getSerialNumberId()); + } + SerialNumberLastGenerateBO lastGenerateBO = SerialNumberLastGenerateBO + .builder() + .lastNumber(serialNumberEntity.getLastNumber()) + .lastTime(serialNumberEntity.getLastTime()) + .serialNumberId(serialNumberEntity.getSerialNumberId()) + .build(); + + // 生成 + SerialNumberGenerateResultBO serialNumberGenerateResult = super.loopNumberList(lastGenerateBO, serialNumberInfo, count); + + // 将生成信息保存的内存和数据库 + lastGenerateBO.setLastNumber(serialNumberGenerateResult.getLastNumber()); + lastGenerateBO.setLastTime(serialNumberGenerateResult.getLastTime()); + serialNumberDao.updateLastNumberAndTime(serialNumberInfo.getSerialNumberId(), + serialNumberGenerateResult.getLastNumber(), + serialNumberGenerateResult.getLastTime()); + + // 把生成过程保存到数据库里 + super.saveRecord(serialNumberGenerateResult); + + return formatNumberList(serialNumberGenerateResult, serialNumberInfo); + } + + @Override + public void initLastGenerateData(List serialNumberEntityList) { + + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/service/impl/SerialNumberRedisService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/service/impl/SerialNumberRedisService.java new file mode 100644 index 0000000..571e0d9 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/service/impl/SerialNumberRedisService.java @@ -0,0 +1,108 @@ +package net.lab1024.sa.base.module.support.serialnumber.service.impl; + +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.exception.BusinessException; +import net.lab1024.sa.base.constant.RedisKeyConst; +import net.lab1024.sa.base.module.support.redis.RedisService; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberEntity; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberGenerateResultBO; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberInfoBO; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberLastGenerateBO; +import net.lab1024.sa.base.module.support.serialnumber.service.SerialNumberBaseService; + +import java.util.List; + +/** + * 单据序列号 基于redis锁实现 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-25 21:46:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +public class SerialNumberRedisService extends SerialNumberBaseService { + + private static final int MAX_GET_LOCK_COUNT = 5; + + private static final long SLEEP_MILLISECONDS = 200L; + + @Resource + private RedisService redisService; + + @Override + public void initLastGenerateData(List serialNumberEntityList) { + if (serialNumberEntityList == null) { + return; + } + + //删除之前的 + redisService.delete(RedisKeyConst.Support.SERIAL_NUMBER_LAST_INFO); + + for (SerialNumberEntity serialNumberEntity : serialNumberEntityList) { + SerialNumberLastGenerateBO lastGenerateBO = SerialNumberLastGenerateBO + .builder() + .serialNumberId(serialNumberEntity.getSerialNumberId()) + .lastNumber(serialNumberEntity.getLastNumber()) + .lastTime(serialNumberEntity.getLastTime()) + .build(); + + redisService.mset(RedisKeyConst.Support.SERIAL_NUMBER_LAST_INFO, + String.valueOf(serialNumberEntity.getSerialNumberId()), + lastGenerateBO + ); + } + } + + @Override + public List generateSerialNumberList(SerialNumberInfoBO serialNumberInfo, int count) { + SerialNumberGenerateResultBO serialNumberGenerateResult = null; + String lockKey = RedisKeyConst.Support.SERIAL_NUMBER + serialNumberInfo.getSerialNumberId(); + try { + boolean lock = false; + for (int i = 0; i < MAX_GET_LOCK_COUNT; i++) { + try { + lock = redisService.getLock(lockKey, 60 * 1000L); + if (lock) { + break; + } + Thread.sleep(SLEEP_MILLISECONDS); + } catch (Throwable e) { + log.error(e.getMessage(), e); + } + } + if (!lock) { + throw new BusinessException("SerialNumber 尝试5次,未能生成单号"); + } + // 获取上次的生成结果 + SerialNumberLastGenerateBO lastGenerateBO = (SerialNumberLastGenerateBO) redisService.mget( + RedisKeyConst.Support.SERIAL_NUMBER_LAST_INFO, + String.valueOf(serialNumberInfo.getSerialNumberId())); + + // 生成 + serialNumberGenerateResult = super.loopNumberList(lastGenerateBO, serialNumberInfo, count); + + // 将生成信息保存的内存和数据库 + lastGenerateBO.setLastNumber(serialNumberGenerateResult.getLastNumber()); + lastGenerateBO.setLastTime(serialNumberGenerateResult.getLastTime()); + serialNumberDao.updateLastNumberAndTime(serialNumberInfo.getSerialNumberId(), + serialNumberGenerateResult.getLastNumber(), + serialNumberGenerateResult.getLastTime()); + + redisService.mset(RedisKeyConst.Support.SERIAL_NUMBER_LAST_INFO, + String.valueOf(serialNumberInfo.getSerialNumberId()), lastGenerateBO); + + // 把生成过程保存到数据库里 + super.saveRecord(serialNumberGenerateResult); + } catch (Throwable e) { + log.error(e.getMessage(), e); + throw e; + } finally { + redisService.unLock(lockKey); + } + + return formatNumberList(serialNumberGenerateResult, serialNumberInfo); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/table/TableColumnController.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/table/TableColumnController.java new file mode 100644 index 0000000..66b3dc5 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/table/TableColumnController.java @@ -0,0 +1,50 @@ +package net.lab1024.sa.base.module.support.table; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import net.lab1024.sa.base.common.controller.SupportBaseController; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartRequestUtil; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import net.lab1024.sa.base.module.support.repeatsubmit.annoation.RepeatSubmit; +import net.lab1024.sa.base.module.support.table.domain.TableColumnUpdateForm; +import org.springframework.web.bind.annotation.*; + +/** + * 表格自定义列(前端用户自定义表格列,并保存到数据库里) + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-12 22:52:21 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@RestController +@Tag(name = SwaggerTagConst.Support.TABLE_COLUMN) +public class TableColumnController extends SupportBaseController { + + @Resource + private TableColumnService tableColumnService; + + @Operation(summary = "修改表格列 @author 卓大") + @PostMapping("/tableColumn/update") + @RepeatSubmit + public ResponseDTO updateTableColumn(@RequestBody @Valid TableColumnUpdateForm updateForm) { + return tableColumnService.updateTableColumns(SmartRequestUtil.getRequestUser(), updateForm); + } + + @Operation(summary = "恢复默认(删除) @author 卓大") + @GetMapping("/tableColumn/delete/{tableId}") + @RepeatSubmit + public ResponseDTO deleteTableColumn(@PathVariable Integer tableId) { + return tableColumnService.deleteTableColumn(SmartRequestUtil.getRequestUser(), tableId); + } + + @Operation(summary = "查询表格列 @author 卓大") + @GetMapping("/tableColumn/getColumns/{tableId}") + public ResponseDTO getColumns(@PathVariable Integer tableId) { + return ResponseDTO.ok(tableColumnService.getTableColumns(SmartRequestUtil.getRequestUser(), tableId)); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/table/TableColumnDao.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/table/TableColumnDao.java new file mode 100644 index 0000000..6dbe22f --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/table/TableColumnDao.java @@ -0,0 +1,23 @@ +package net.lab1024.sa.base.module.support.table; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import net.lab1024.sa.base.module.support.table.domain.TableColumnEntity; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 表格自定义列(前端用户自定义表格列,并保存到数据库里) + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-12 22:52:21 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface TableColumnDao extends BaseMapper { + + TableColumnEntity selectByUserIdAndTableId(@Param("userId") Long userId, @Param("userType") Integer userType, @Param("tableId") Integer tableId); + + void deleteTableColumn(@Param("userId") Long userId, @Param("userType") Integer userType, @Param("tableId") Integer tableId); +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/table/TableColumnService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/table/TableColumnService.java new file mode 100644 index 0000000..871e1aa --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/table/TableColumnService.java @@ -0,0 +1,72 @@ +package net.lab1024.sa.base.module.support.table; + +import com.alibaba.fastjson.JSONArray; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.domain.RequestUser; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.module.support.table.domain.TableColumnEntity; +import net.lab1024.sa.base.module.support.table.domain.TableColumnUpdateForm; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; + +/** + * 表格自定义列(前端用户自定义表格列,并保存到数据库里) + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-12 22:52:21 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class TableColumnService { + + @Resource + private TableColumnDao tableColumnDao; + + /** + * 获取 - 表格列 + * + * @return + */ + public String getTableColumns(RequestUser requestUser, Integer tableId) { + TableColumnEntity tableColumnEntity = tableColumnDao.selectByUserIdAndTableId(requestUser.getUserId(), requestUser.getUserType().getValue(), tableId); + return tableColumnEntity == null ? null : tableColumnEntity.getColumns(); + } + + /** + * 更新表格列 + * + * @return + */ + public ResponseDTO updateTableColumns(RequestUser requestUser, TableColumnUpdateForm updateForm) { + if (CollectionUtils.isEmpty(updateForm.getColumnList())) { + return ResponseDTO.ok(); + } + Integer tableId = updateForm.getTableId(); + TableColumnEntity tableColumnEntity = tableColumnDao.selectByUserIdAndTableId(requestUser.getUserId(), requestUser.getUserType().getValue(), tableId); + if (tableColumnEntity == null) { + tableColumnEntity = new TableColumnEntity(); + tableColumnEntity.setTableId(tableId); + tableColumnEntity.setUserId(requestUser.getUserId()); + tableColumnEntity.setUserType(requestUser.getUserType().getValue()); + + tableColumnEntity.setColumns(JSONArray.toJSONString(updateForm.getColumnList())); + tableColumnDao.insert(tableColumnEntity); + } else { + tableColumnEntity.setColumns(JSONArray.toJSONString(updateForm.getColumnList())); + tableColumnDao.updateById(tableColumnEntity); + } + return ResponseDTO.ok(); + } + + /** + * 删除表格列 + * + * @return + */ + public ResponseDTO deleteTableColumn(RequestUser requestUser, Integer tableId) { + tableColumnDao.deleteTableColumn(requestUser.getUserId(), requestUser.getUserType().getValue(), tableId); + return ResponseDTO.ok(); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/table/domain/TableColumnEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/table/domain/TableColumnEntity.java new file mode 100644 index 0000000..b8b89cb --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/table/domain/TableColumnEntity.java @@ -0,0 +1,49 @@ +package net.lab1024.sa.base.module.support.table.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 自定义表格列 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-12 22:52:21 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName("t_table_column") +public class TableColumnEntity { + + @TableId(type = IdType.AUTO) + private Long tableColumnId; + + /** + * 用户id + */ + private Long userId; + + /** + * 用户类型 + */ + private Integer userType; + + /** + * 表id + */ + private Integer tableId; + + /** + * 表列 + */ + private String columns; + + private LocalDateTime createTime; + + private LocalDateTime updateTime; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/table/domain/TableColumnItemForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/table/domain/TableColumnItemForm.java new file mode 100644 index 0000000..4cc96f0 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/table/domain/TableColumnItemForm.java @@ -0,0 +1,36 @@ +package net.lab1024.sa.base.module.support.table.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 自定义表格列 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-12 22:52:21 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class TableColumnItemForm { + + @NotEmpty(message = "列不能为空") + @Schema(description = "字段") + private String columnKey; + + @Schema(description = "宽度") + private Integer width; + + @NotNull(message = "显示不能为空") + @Schema(description = "是否显示") + private Boolean showFlag; + + @NotNull(message = "排序不能为空") + @Schema(description = "排序") + private Integer sort; + + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/table/domain/TableColumnUpdateForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/table/domain/TableColumnUpdateForm.java new file mode 100644 index 0000000..dce303f --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/table/domain/TableColumnUpdateForm.java @@ -0,0 +1,27 @@ +package net.lab1024.sa.base.module.support.table.domain; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +/** + * 自定义表格列 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-12 22:52:21 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class TableColumnUpdateForm { + + @NotNull(message = "表id不能为空") + private Integer tableId; + + @NotEmpty(message = "请上传列") + private List columnList; + +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/workbook/controller/WorkBookController.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/workbook/controller/WorkBookController.java new file mode 100644 index 0000000..56ba8f1 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/workbook/controller/WorkBookController.java @@ -0,0 +1,84 @@ +package net.lab1024.sa.base.module.support.workbook.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import net.lab1024.sa.base.common.controller.SupportBaseController; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.RequestUser; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartRequestUtil; +import net.lab1024.sa.base.common.util.SmartResponseUtil; +import net.lab1024.sa.base.module.support.file.domain.vo.FileDownloadVO; +import net.lab1024.sa.base.module.support.workbook.domain.form.WorkBookQueryForm; +import net.lab1024.sa.base.module.support.workbook.domain.vo.WorkBookVO; +import net.lab1024.sa.base.module.support.workbook.service.WorkBookService; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +/** + * 工作手册管理 + * + * @Author 1024 创新实验室 + * @Date 2026-03-24 + * @Copyright 1024 创新实验室(https://1024lab.net),Since 2012 + */ +@RestController +@RequestMapping("/workBook") +@Tag(name = "支持 - 工作手册") +public class WorkBookController extends SupportBaseController { + + @Resource + private WorkBookService workBookService; + + /** + * 上传工作手册 + */ + @Operation(summary = "上传工作手册") + @PostMapping("/upload") + public ResponseDTO upload(@RequestParam MultipartFile file, @RequestParam Integer folderType) { + RequestUser requestUser = SmartRequestUtil.getRequestUser(); + return workBookService.uploadWorkBook(file, folderType, requestUser); + } + + /** + * 分页查询工作手册 + */ + @Operation(summary = "分页查询工作手册") + @PostMapping("/queryPage") + public ResponseDTO> queryPage(@RequestBody WorkBookQueryForm queryForm) { + PageResult pageResult = workBookService.queryPage(queryForm); + return ResponseDTO.ok(pageResult); + } + + /** + * 下载工作手册 + */ + @Operation(summary = "下载工作手册") + @GetMapping("/download") + public void download(@RequestParam String fileKey, HttpServletRequest request, HttpServletResponse response) throws Exception { + String userAgent = request.getHeader("User-Agent"); + ResponseDTO downloadFileResult = workBookService.getDownloadFile(fileKey, userAgent); + if (!downloadFileResult.getOk()) { + SmartResponseUtil.write(response, downloadFileResult); + return; + } + // 设置下载文件头 + FileDownloadVO fileDownloadVO = downloadFileResult.getData(); + SmartResponseUtil.setDownloadFileHeader(response, fileDownloadVO.getMetadata().getFileName(), fileDownloadVO.getMetadata().getFileSize()); + // 下载 + response.getOutputStream().write(fileDownloadVO.getData()); + } + + /** + * 删除工作手册 + */ + @Operation(summary = "删除工作手册") + @GetMapping("/delete/{workBookId}") + public ResponseDTO delete(@PathVariable Long workBookId) { + return workBookService.delete(workBookId); + } +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/workbook/dao/WorkBookDao.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/workbook/dao/WorkBookDao.java new file mode 100644 index 0000000..ba5f3a4 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/workbook/dao/WorkBookDao.java @@ -0,0 +1,39 @@ +package net.lab1024.sa.base.module.support.workbook.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.base.module.support.workbook.domain.entity.WorkBookEntity; +import net.lab1024.sa.base.module.support.workbook.domain.form.WorkBookQueryForm; +import net.lab1024.sa.base.module.support.workbook.domain.vo.WorkBookVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 工作手册 Dao + * + * @Author 1024 创新实验室 + * @Date 2026-03-24 + * @Copyright 1024 创新实验室(https://1024lab.net),Since 2012 + */ +@Mapper +public interface WorkBookDao extends BaseMapper { + + /** + * 分页查询工作手册 + * + * @param page + * @param queryForm + * @return + */ + List queryPage(Page page, @Param("queryForm") WorkBookQueryForm queryForm); + + /** + * 根据 fileKey 查询 + * + * @param fileKey + * @return + */ + WorkBookVO getByFileKey(@Param("fileKey") String fileKey); +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/workbook/domain/entity/WorkBookEntity.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/workbook/domain/entity/WorkBookEntity.java new file mode 100644 index 0000000..6e7f63d --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/workbook/domain/entity/WorkBookEntity.java @@ -0,0 +1,75 @@ +package net.lab1024.sa.base.module.support.workbook.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 工作手册实体类 + * + * @Author 1024 创新实验室 + * @Date 2026-03-24 + * @Copyright 1024 创新实验室(https://1024lab.net),Since 2012 + */ +@Data +@TableName("t_work_book") +public class WorkBookEntity { + + /** + * 主键 id + */ + @TableId(type = IdType.AUTO) + private Long workBookId; + + /** + * 文件 ID + */ + private Long fileId; + + /** + * 手册类型 (1 常用文书 2 法律文书 3 相关规定 4 其他) + */ + private Integer folderType; + + /** + * 文件名称 + */ + private String fileName; + + /** + * 文件大小 (字节) + */ + private Long fileSize; + + /** + * 文件 key,用于下载 + */ + private String fileKey; + + /** + * 文件类型 (扩展名) + */ + private String fileType; + + /** + * 创建人 ID + */ + private Long creatorId; + + /** + * 创建人类型 + */ + private Integer creatorUserType; + + /** + * 创建人姓名 + */ + private String creatorName; + + private LocalDateTime updateTime; + + private LocalDateTime createTime; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/workbook/domain/form/WorkBookQueryForm.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/workbook/domain/form/WorkBookQueryForm.java new file mode 100644 index 0000000..34a985e --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/workbook/domain/form/WorkBookQueryForm.java @@ -0,0 +1,49 @@ +package net.lab1024.sa.base.module.support.workbook.domain.form; + +import lombok.Data; + +/** + * 工作手册查询表单 + * + * @Author 1024 创新实验室 + * @Date 2026-03-24 + * @Copyright 1024 创新实验室(https://1024lab.net),Since 2012 + */ +@Data +public class WorkBookQueryForm { + + /** + * 手册类型 + */ + private Integer folderType; + + /** + * 文件名称 + */ + private String fileName; + + /** + * 上传人 + */ + private String creatorName; + + /** + * 上传时间开始 + */ + private String createTimeBegin; + + /** + * 上传时间结束 + */ + private String createTimeEnd; + + /** + * 页码 + */ + private Integer pageNum = 1; + + /** + * 每页数量 + */ + private Integer pageSize = 10; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/workbook/domain/vo/WorkBookVO.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/workbook/domain/vo/WorkBookVO.java new file mode 100644 index 0000000..d6cac9a --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/workbook/domain/vo/WorkBookVO.java @@ -0,0 +1,84 @@ +package net.lab1024.sa.base.module.support.workbook.domain.vo; + +import lombok.Data; + +/** + * 工作手册 VO + * + * @Author 1024 创新实验室 + * @Date 2026-03-24 + * @Copyright 1024 创新实验室(https://1024lab.net),Since 2012 + */ +@Data +public class WorkBookVO { + + /** + * 主键 id + */ + private Long workBookId; + + /** + * 文件 ID + */ + private Long fileId; + + /** + * 手册类型 + */ + private Integer folderType; + + /** + * 手册类型描述 + */ + private String folderTypeDesc; + + /** + * 文件名称 + */ + private String fileName; + + /** + * 文件大小 (字节) + */ + private Long fileSize; + + /** + * 文件大小描述 + */ + private String fileSizeDesc; + + /** + * 文件 key,用于下载 + */ + private String fileKey; + + /** + * 文件类型 (扩展名) + */ + private String fileType; + + /** + * 创建人 ID + */ + private Long creatorId; + + /** + * 创建人类型 + */ + private Integer creatorUserType; + + /** + * 创建人姓名 + */ + private String creatorName; + + /** + * 更新时间 + */ + private String updateTime; + + /** + * 创建时间 + */ + private String createTime; +} diff --git a/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/workbook/service/WorkBookService.java b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/workbook/service/WorkBookService.java new file mode 100644 index 0000000..c58fefb --- /dev/null +++ b/smart-flow-api/sa-base/src/main/java/net/lab1024/sa/base/module/support/workbook/service/WorkBookService.java @@ -0,0 +1,199 @@ +package net.lab1024.sa.base.module.support.workbook.service; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.code.UserErrorCode; +import net.lab1024.sa.base.common.constant.StringConst; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.RequestUser; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartEnumUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.module.support.file.constant.FileFolderTypeEnum; +import net.lab1024.sa.base.module.support.file.domain.vo.FileDownloadVO; +import net.lab1024.sa.base.module.support.file.domain.vo.FileUploadVO; +import net.lab1024.sa.base.module.support.file.service.IFileStorageService; +import net.lab1024.sa.base.module.support.redis.RedisService; +import net.lab1024.sa.base.module.support.securityprotect.service.SecurityFileService; +import net.lab1024.sa.base.module.support.workbook.dao.WorkBookDao; +import net.lab1024.sa.base.module.support.workbook.domain.entity.WorkBookEntity; +import net.lab1024.sa.base.module.support.workbook.domain.form.WorkBookQueryForm; +import net.lab1024.sa.base.module.support.workbook.domain.vo.WorkBookVO; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +/** + * 工作手册服务 + * + * @Author 1024 创新实验室 + * @Date 2026-03-24 + * @Copyright 1024 创新实验室(https://1024lab.net),Since 2012 + */ +@Service +public class WorkBookService { + + /** + * 文件名最大长度 + */ + private static final int FILE_NAME_MAX_LENGTH = 100; + + @Resource + private IFileStorageService fileStorageService; + + @Resource + private WorkBookDao workBookDao; + + /** + * 上传工作手册 + * + * @param file + * @param folderType 手册类型 + * @return + */ + public ResponseDTO uploadWorkBook(MultipartFile file, Integer folderType, RequestUser requestUser) { + + // 进行上传 + ResponseDTO response = fileStorageService.upload(file, FileFolderTypeEnum.WORK_BOOK.getFolder()); + if (!response.getOk()) { + return ResponseDTO.error(response); + } + + // 上传成功,保存到数据库 + FileUploadVO uploadVO = response.getData(); + WorkBookEntity workBookEntity = new WorkBookEntity(); + workBookEntity.setFolderType(folderType); + workBookEntity.setFileName(file.getOriginalFilename()); + workBookEntity.setFileSize(file.getSize()); + workBookEntity.setFileKey(uploadVO.getFileKey()); + workBookEntity.setFileType(uploadVO.getFileType()); + workBookEntity.setCreatorId(requestUser == null ? null : requestUser.getUserId()); + workBookEntity.setCreatorName(requestUser == null ? null : requestUser.getUserName()); + workBookEntity.setCreatorUserType(requestUser == null ? null : requestUser.getUserType().getValue()); + workBookDao.insert(workBookEntity); + + // 返回结果 + WorkBookVO resultVO = convertToVO(workBookEntity); + return ResponseDTO.ok(resultVO); + } + + /** + * 分页查询 + */ + public PageResult queryPage(WorkBookQueryForm queryForm) { + Page page = new Page<>(queryForm.getPageNum(), queryForm.getPageSize()); + java.util.List list = workBookDao.queryPage(page, queryForm); + + // 转换数据 + for (WorkBookVO vo : list) { + convertVO(vo); + } + + return SmartPageUtil.convert2PageResult(page, list); + } + + /** + * 根据文件 key 获取下载信息 + */ + public ResponseDTO getDownloadFile(String fileKey, String userAgent) { + WorkBookVO workBookVO = workBookDao.getByFileKey(fileKey); + if (workBookVO == null) { + return ResponseDTO.userErrorParam("文件不存在"); + } + + // 获取下载文件 + ResponseDTO download = fileStorageService.download(fileKey); + if (download.getOk()) { + download.getData().getMetadata().setFileName(workBookVO.getFileName()); + } + return download; + } + + /** + * 删除工作手册 + * + * @param workBookId + * @return + */ + public ResponseDTO delete(Long workBookId) { + WorkBookEntity workBookEntity = workBookDao.selectById(workBookId); + if (workBookEntity == null) { + return ResponseDTO.userErrorParam("工作手册不存在"); + } + + // 删除文件记录 + workBookDao.deleteById(workBookId); + + // TODO: 是否需要从文件存储中删除文件? + // fileStorageService.delete(workBookEntity.getFileKey()); + + return ResponseDTO.ok(); + } + + /** + * 转换为 VO + */ + private WorkBookVO convertToVO(WorkBookEntity entity) { + WorkBookVO vo = new WorkBookVO(); + vo.setWorkBookId(entity.getWorkBookId()); + vo.setFileId(entity.getFileId()); + vo.setFolderType(entity.getFolderType()); + vo.setFileName(entity.getFileName()); + vo.setFileSize(entity.getFileSize()); + vo.setFileKey(entity.getFileKey()); + vo.setFileType(entity.getFileType()); + vo.setCreatorId(entity.getCreatorId()); + vo.setCreatorUserType(entity.getCreatorUserType()); + vo.setCreatorName(entity.getCreatorName()); + vo.setCreateTime(entity.getCreateTime() != null ? entity.getCreateTime().toString() : null); + vo.setUpdateTime(entity.getUpdateTime() != null ? entity.getUpdateTime().toString() : null); + return vo; + } + + /** + * 转换 VO 数据,添加描述信息 + */ + private void convertVO(WorkBookVO vo) { + if (vo == null) { + return; + } + + // 设置手册类型描述 + FileFolderTypeEnum folderTypeEnum = SmartEnumUtil.getEnumByValue(vo.getFolderType(), FileFolderTypeEnum.class); + if (folderTypeEnum != null) { + vo.setFolderTypeDesc(folderTypeEnum.getDesc()); + } + + // 设置文件大小描述 + Long fileSize = vo.getFileSize(); + if (fileSize != null) { + String fileSizeDesc = formatFileSize(fileSize); + vo.setFileSizeDesc(fileSizeDesc); + } + } + + /** + * 格式化文件大小 + */ + private String formatFileSize(Long size) { + if (size == null || size == 0) { + return ""; + } + double num = 1024.0; + if (size < num) { + return size + "B"; + } + if (size < Math.pow(num, 2)) { + return String.format("%.2fKB", size / num); + } + if (size < Math.pow(num, 3)) { + return String.format("%.2fMB", size / Math.pow(num, 2)); + } + if (size < Math.pow(num, 4)) { + return String.format("%.2fG", size / Math.pow(num, 3)); + } + return String.format("%.2fT", size / Math.pow(num, 4)); + } +} diff --git a/smart-flow-api/sa-base/src/main/resources/META-INF/spring.factories b/smart-flow-api/sa-base/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..7dc64b8 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.env.EnvironmentPostProcessor=\ + net.lab1024.sa.base.config.YamlProcessor \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/resources/banner.txt b/smart-flow-api/sa-base/src/main/resources/banner.txt new file mode 100644 index 0000000..8fea0a4 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/banner.txt @@ -0,0 +1,19 @@ + ${AnsiColor.BRIGHT_GREEN} + + / ____| | | /\ | | (_) +| (___ _ __ ___ __ _ _ __| |_ / \ __| |_ __ ___ _ _ __ + \___ \| '_ ` _ \ / _` | '__| __| / /\ \ / _` | '_ ` _ \| | '_ \ + ____) | | | | | | (_| | | | |_ / ____ \ (_| | | | | | | | | | | +|_____/|_| |_| |_|\__,_|_| \__/_/ \_\__,_|_| |_| |_|_|_| |_| + +保持谦逊 保持学习 ! +热爱代码 热爱生活 ! +永远年轻 永远前行 ! + +SmartFlow v3.X ,作者:1024创新实验室 @copyright:【 1024lab 】 + +SmartAdmin 文档地址:https://smartadmin.vip + +1024创新实验室:https://www.1024lab.net + +${AnsiColor.DEFAULT} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/resources/code-generator-template/java/constant/enum.java.vm b/smart-flow-api/sa-base/src/main/resources/code-generator-template/java/constant/enum.java.vm new file mode 100644 index 0000000..87c2c64 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/code-generator-template/java/constant/enum.java.vm @@ -0,0 +1,24 @@ +package ${packageName}; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * ${enumDesc} + * + * @Author ${basic.backendAuthor} + * @Date ${basic.backendDate} + * @Copyright ${basic.copyright} + */ + +@AllArgsConstructor +@Getter +public enum ${enumName} implements BaseEnum { + + ; + + private final ${enumJavaType} value; + + private final String desc; +} diff --git a/smart-flow-api/sa-base/src/main/resources/code-generator-template/java/controller/Controller.java.vm b/smart-flow-api/sa-base/src/main/resources/code-generator-template/java/controller/Controller.java.vm new file mode 100644 index 0000000..3d0657b --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/code-generator-template/java/controller/Controller.java.vm @@ -0,0 +1,74 @@ +package ${packageName}; + +#foreach ($importClass in $importPackageList) +$importClass +#end +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.domain.PageResult; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import cn.dev33.satoken.annotation.SaCheckPermission; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; + +/** + * ${basic.description} Controller + * + * @Author ${basic.backendAuthor} + * @Date ${basic.backendDate} + * @Copyright ${basic.copyright} + */ + +@RestController +@Tag(name = "${basic.description}") +public class ${name.upperCamel}Controller { + + @Resource + private ${name.upperCamel}Service ${name.lowerCamel}Service; + + @Operation(summary = "分页查询 @author ${basic.backendAuthor}") + @PostMapping("/${name.lowerCamel}/queryPage") + @SaCheckPermission("${name.lowerCamel}:query") + public ResponseDTO> queryPage(@RequestBody @Valid ${name.upperCamel}QueryForm queryForm) { + return ResponseDTO.ok(${name.lowerCamel}Service.queryPage(queryForm)); + } + +#if($insertAndUpdate.isSupportInsertAndUpdate) + @Operation(summary = "添加 @author ${basic.backendAuthor}") + @PostMapping("/${name.lowerCamel}/add") + @SaCheckPermission("${name.lowerCamel}:add") + public ResponseDTO add(@RequestBody @Valid ${name.upperCamel}AddForm addForm) { + return ${name.lowerCamel}Service.add(addForm); + } + + @Operation(summary = "更新 @author ${basic.backendAuthor}") + @PostMapping("/${name.lowerCamel}/update") + @SaCheckPermission("${name.lowerCamel}:update") + public ResponseDTO update(@RequestBody @Valid ${name.upperCamel}UpdateForm updateForm) { + return ${name.lowerCamel}Service.update(updateForm); + } +#end + +#if($deleteInfo.isSupportDelete) + #if($deleteInfo.deleteEnum == "Batch" || $deleteInfo.deleteEnum == "SingleAndBatch") + @Operation(summary = "批量删除 @author ${basic.backendAuthor}") + @PostMapping("/${name.lowerCamel}/batchDelete") + @SaCheckPermission("${name.lowerCamel}:delete") + public ResponseDTO batchDelete(@RequestBody ValidateList<${primaryKeyJavaType}> idList) { + return ${name.lowerCamel}Service.batchDelete(idList); + } + #end + + #if($deleteInfo.deleteEnum == "Single" || $deleteInfo.deleteEnum == "SingleAndBatch") + @Operation(summary = "单个删除 @author ${basic.backendAuthor}") + @GetMapping("/${name.lowerCamel}/delete/{${primaryKeyFieldName}}") + @SaCheckPermission("${name.lowerCamel}:delete") + public ResponseDTO batchDelete(@PathVariable ${primaryKeyJavaType} ${primaryKeyFieldName}) { + return ${name.lowerCamel}Service.delete(${primaryKeyFieldName}); + } + #end +#end +} diff --git a/smart-flow-api/sa-base/src/main/resources/code-generator-template/java/dao/Dao.java.vm b/smart-flow-api/sa-base/src/main/resources/code-generator-template/java/dao/Dao.java.vm new file mode 100644 index 0000000..374c374 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/code-generator-template/java/dao/Dao.java.vm @@ -0,0 +1,51 @@ +package ${packageName}; + +#foreach ($importClass in $importPackageList) +$importClass +#end +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +/** + * ${basic.description} Dao + * + * @Author ${basic.backendAuthor} + * @Date ${basic.backendDate} + * @Copyright ${basic.copyright} + */ + +@Mapper +public interface ${name.upperCamel}Dao extends BaseMapper<${name.upperCamel}Entity> { + + /** + * 分页 查询 + * + * @param page + * @param queryForm + * @return + */ + List<${name.upperCamel}VO> queryPage(Page page, @Param("queryForm") ${name.upperCamel}QueryForm queryForm); + +#if($deleteInfo.isSupportDelete) +### 假删除 +#if(!${deleteInfo.isPhysicallyDeleted}) +#if($deleteInfo.deleteEnum == "Single" || $deleteInfo.deleteEnum == "SingleAndBatch") + /** + * 更新删除状态 + */ + long updateDeleted(@Param("${primaryKeyFieldName}")${primaryKeyJavaType} ${primaryKeyFieldName},@Param("deletedFlag")boolean deletedFlag); + +#end +#if($deleteInfo.deleteEnum == "Batch" || $deleteInfo.deleteEnum == "SingleAndBatch") + /** + * 批量更新删除状态 + */ + void batchUpdateDeleted(@Param("idList")List<${primaryKeyJavaType}> idList,@Param("deletedFlag")boolean deletedFlag); + +#end +#end +#end +} diff --git a/smart-flow-api/sa-base/src/main/resources/code-generator-template/java/domain/entity/Entity.java.vm b/smart-flow-api/sa-base/src/main/resources/code-generator-template/java/domain/entity/Entity.java.vm new file mode 100644 index 0000000..04230b3 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/code-generator-template/java/domain/entity/Entity.java.vm @@ -0,0 +1,38 @@ +package ${basic.javaPackageName}.domain.entity; + +#foreach ($importClass in $importPackageList) +$importClass +#end + +/** + * ${basic.description} 实体类 + * + * @Author ${basic.backendAuthor} + * @Date ${basic.backendDate} + * @Copyright ${basic.copyright} + */ + +@Data +@TableName("${tableName}") +public class ${name.upperCamel}Entity { +#foreach ($field in $fields) + + /** + * $field.label + */ + #if($field.primaryKeyFlag && $field.autoIncreaseFlag) + @TableId(type = IdType.AUTO) + #end + #if($field.primaryKeyFlag && !$field.autoIncreaseFlag) + @TableId + #end + #if($field.columnName == "create_time") + @TableField(fill = FieldFill.INSERT) + #end + #if($field.columnName == "update_time") + @TableField(fill = FieldFill.INSERT_UPDATE) + #end + private $field.javaType $field.fieldName; +#end + +} diff --git a/smart-flow-api/sa-base/src/main/resources/code-generator-template/java/domain/form/AddForm.java.vm b/smart-flow-api/sa-base/src/main/resources/code-generator-template/java/domain/form/AddForm.java.vm new file mode 100644 index 0000000..d1e8581 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/code-generator-template/java/domain/form/AddForm.java.vm @@ -0,0 +1,30 @@ +package ${packageName}; + +#foreach ($importClass in $importPackageList) +$importClass +#end + +/** + * ${basic.description} 新建表单 + * + * @Author ${basic.backendAuthor} + * @Date ${basic.backendDate} + * @Copyright ${basic.copyright} + */ + +@Data +public class ${name.upperCamel}AddForm { +#foreach ($field in $fields) + +#if($field.isEnum) + ${field.apiModelProperty} + ${field.checkEnum} + private $field.javaType $field.fieldName; +#end +#if(!$field.isEnum) + ${field.apiModelProperty}$!{field.notEmpty}$!{field.dict}$!{field.file} + private $field.javaType $field.fieldName; +#end +#end + +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/resources/code-generator-template/java/domain/form/QueryForm.java.vm b/smart-flow-api/sa-base/src/main/resources/code-generator-template/java/domain/form/QueryForm.java.vm new file mode 100644 index 0000000..69a247e --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/code-generator-template/java/domain/form/QueryForm.java.vm @@ -0,0 +1,39 @@ +package ${packageName}; + +import net.lab1024.sa.base.common.domain.PageParam; +#foreach ($importClass in $importPackageList) +$importClass +#end + +/** + * ${basic.description} 分页查询表单 + * + * @Author ${basic.backendAuthor} + * @Date ${basic.backendDate} + * @Copyright ${basic.copyright} + */ + +@Data +@EqualsAndHashCode(callSuper = false) +public class ${name.upperCamel}QueryForm extends PageParam { +#foreach ($field in $fields) + +#if($field.isEnum) + ${field.apiModelProperty} + ${field.checkEnum} + private $field.javaType $field.fieldName; +#end +#if(!$field.isEnum && $field.queryTypeEnum != "DateRange") + ${field.apiModelProperty}$!{field.dict} + private $field.javaType $field.fieldName; +#end +#if(!$field.isEnum && $field.queryTypeEnum == "DateRange") + ${field.apiModelProperty} + private $field.javaType ${field.fieldName}Begin; + + ${field.apiModelProperty} + private $field.javaType ${field.fieldName}End; +#end +#end + +} diff --git a/smart-flow-api/sa-base/src/main/resources/code-generator-template/java/domain/form/UpdateForm.java.vm b/smart-flow-api/sa-base/src/main/resources/code-generator-template/java/domain/form/UpdateForm.java.vm new file mode 100644 index 0000000..15cbe9a --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/code-generator-template/java/domain/form/UpdateForm.java.vm @@ -0,0 +1,30 @@ +package ${packageName}; + +#foreach ($importClass in $importPackageList) +$importClass +#end + +/** + * ${basic.description} 更新表单 + * + * @Author ${basic.backendAuthor} + * @Date ${basic.backendDate} + * @Copyright ${basic.copyright} + */ + +@Data +public class ${name.upperCamel}UpdateForm { +#foreach ($field in $fields) + +#if($field.isEnum) + ${field.apiModelProperty} + ${field.checkEnum} + private $field.javaType $field.fieldName; +#end +#if(!$field.isEnum) + ${field.apiModelProperty}$!{field.notEmpty}$!{field.dict}$!{field.file} + private $field.javaType $field.fieldName; +#end +#end + +} \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/resources/code-generator-template/java/domain/vo/VO.java.vm b/smart-flow-api/sa-base/src/main/resources/code-generator-template/java/domain/vo/VO.java.vm new file mode 100644 index 0000000..76ebd3d --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/code-generator-template/java/domain/vo/VO.java.vm @@ -0,0 +1,24 @@ +package ${packageName}; + +#foreach ($importClass in $importPackageList) +$importClass +#end + +/** + * ${basic.description} 列表VO + * + * @Author ${basic.backendAuthor} + * @Date ${basic.backendDate} + * @Copyright ${basic.copyright} + */ + +@Data +public class ${name.upperCamel}VO { + +#foreach ($field in $fields) + + ${field.apiModelProperty}$!{field.notEmpty}$!{field.file} + private $field.javaType $field.fieldName; +#end + +} diff --git a/smart-flow-api/sa-base/src/main/resources/code-generator-template/java/manager/Manager.java.vm b/smart-flow-api/sa-base/src/main/resources/code-generator-template/java/manager/Manager.java.vm new file mode 100644 index 0000000..52811ca --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/code-generator-template/java/manager/Manager.java.vm @@ -0,0 +1,21 @@ +package ${packageName}; + +#foreach ($importClass in $importPackageList) +$importClass +#end + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + * ${basic.description} Manager + * + * @Author ${basic.backendAuthor} + * @Date ${basic.backendDate} + * @Copyright ${basic.copyright} + */ +@Service +public class ${name.upperCamel}Manager extends ServiceImpl<${name.upperCamel}Dao, ${name.upperCamel}Entity> { + + +} diff --git a/smart-flow-api/sa-base/src/main/resources/code-generator-template/java/mapper/Mapper.xml.vm b/smart-flow-api/sa-base/src/main/resources/code-generator-template/java/mapper/Mapper.xml.vm new file mode 100644 index 0000000..7a82712 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/code-generator-template/java/mapper/Mapper.xml.vm @@ -0,0 +1,76 @@ + + + + + + + #foreach ($field in $fields) + ${tableName}.${field.columnName}#if($foreach.hasNext),#end + #end + + + + + +#if($deleteInfo.isSupportDelete) +### 假删除 +#if(!${deleteInfo.isPhysicallyDeleted}) +#if($deleteInfo.deleteEnum == "Batch" || $deleteInfo.deleteEnum == "SingleAndBatch") + + update ${tableName} set deleted_flag = #{deletedFlag} + where ${primaryKeyColumnName} in + + #{item} + + +#end +#if($deleteInfo.deleteEnum == "Single" || $deleteInfo.deleteEnum == "SingleAndBatch") + + + update ${tableName} set deleted_flag = #{deletedFlag} + where ${primaryKeyColumnName} = #{${primaryKeyFieldName}} + +#end +#end +#end + + diff --git a/smart-flow-api/sa-base/src/main/resources/code-generator-template/java/service/Service.java.vm b/smart-flow-api/sa-base/src/main/resources/code-generator-template/java/service/Service.java.vm new file mode 100644 index 0000000..e21bb2f --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/code-generator-template/java/service/Service.java.vm @@ -0,0 +1,100 @@ +package ${packageName}; + +#foreach ($importClass in $importPackageList) +$importClass +#end +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.domain.PageResult; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; + +import jakarta.annotation.Resource; + +/** + * ${basic.description} Service + * + * @Author ${basic.backendAuthor} + * @Date ${basic.backendDate} + * @Copyright ${basic.copyright} + */ + +@Service +public class ${name.upperCamel}Service { + + @Resource + private ${name.upperCamel}Dao ${name.lowerCamel}Dao; + + /** + * 分页查询 + */ + public PageResult<${name.upperCamel}VO> queryPage(${name.upperCamel}QueryForm queryForm) { + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List<${name.upperCamel}VO> list = ${name.lowerCamel}Dao.queryPage(page, queryForm); + return SmartPageUtil.convert2PageResult(page, list); + } + +#if($insertAndUpdate.isSupportInsertAndUpdate) + /** + * 添加 + */ + public ResponseDTO add(${name.upperCamel}AddForm addForm) { + ${name.upperCamel}Entity ${name.lowerCamel}Entity = SmartBeanUtil.copy(addForm, ${name.upperCamel}Entity.class); + ${name.lowerCamel}Dao.insert(${name.lowerCamel}Entity); + return ResponseDTO.ok(); + } + + /** + * 更新 + * + */ + public ResponseDTO update(${name.upperCamel}UpdateForm updateForm) { + ${name.upperCamel}Entity ${name.lowerCamel}Entity = SmartBeanUtil.copy(updateForm, ${name.upperCamel}Entity.class); + ${name.lowerCamel}Dao.updateById(${name.lowerCamel}Entity); + return ResponseDTO.ok(); + } +#end + +#if($deleteInfo.isSupportDelete) + #if($deleteInfo.deleteEnum == "Batch" || $deleteInfo.deleteEnum == "SingleAndBatch") + /** + * 批量删除 + */ + public ResponseDTO batchDelete(List<${primaryKeyJavaType}> idList) { + if (CollectionUtils.isEmpty(idList)){ + return ResponseDTO.ok(); + } + +### 真删除 or 假删除 +#if(!${deleteInfo.isPhysicallyDeleted}) + ${name.lowerCamel}Dao.batchUpdateDeleted(idList, true); +#else + ${name.lowerCamel}Dao.deleteBatchIds(idList); +#end + return ResponseDTO.ok(); + } + #end + + #if($deleteInfo.deleteEnum == "Single" || $deleteInfo.deleteEnum == "SingleAndBatch") + /** + * 单个删除 + */ + public ResponseDTO delete(${primaryKeyJavaType} ${primaryKeyFieldName}) { + if (null == ${primaryKeyFieldName}){ + return ResponseDTO.ok(); + } + +### 真删除 or 假删除 +#if(!${deleteInfo.isPhysicallyDeleted}) + ${name.lowerCamel}Dao.updateDeleted(${primaryKeyFieldName}, true); +#end +#if(${deleteInfo.isPhysicallyDeleted}) + ${name.lowerCamel}Dao.deleteById(${primaryKeyFieldName}); +#end + return ResponseDTO.ok(); + } + #end +#end +} diff --git a/smart-flow-api/sa-base/src/main/resources/code-generator-template/java/sql/Menu.sql.vm b/smart-flow-api/sa-base/src/main/resources/code-generator-template/java/sql/Menu.sql.vm new file mode 100644 index 0000000..4f066e3 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/code-generator-template/java/sql/Menu.sql.vm @@ -0,0 +1,22 @@ +# 默认是按前端工程文件的 /views/business 文件夹的路径作为前端组件路径,如果你没把生成的 .vue 前端代码放在 /views/business 下, +# 那就根据自己实际情况修改下面 SQL 的 path,component 字段值,避免执行 SQL 后菜单无法访问。 +# 如果你一切都是按照默认,那么下面的 SQL 基本不用改 + +INSERT INTO t_menu ( menu_name, menu_type, parent_id, path, component, frame_flag, cache_flag, visible_flag, disabled_flag, perms_type, create_user_id ) +VALUES ( '${basic.description}', 2, 0, '/${name.lowerHyphenCamel}/list', '/business/${name.lowerHyphenCamel}/${name.lowerHyphenCamel}-list.vue', false, false, true, false, 1, 1 ); + +# 按菜单名称查询该菜单的 menu_id 作为按钮权限的 父菜单ID 与 功能点关联菜单ID +SET @parent_id = NULL; +SELECT t_menu.menu_id INTO @parent_id FROM t_menu WHERE t_menu.menu_name = '${basic.description}'; + +INSERT INTO t_menu ( menu_name, menu_type, parent_id, frame_flag, cache_flag, visible_flag, disabled_flag, perms_type, api_perms, web_perms, context_menu_id, create_user_id ) +VALUES ( '查询', 3, @parent_id, false, false, true, false, 1, '${name.lowerCamel}:query', '${name.lowerCamel}:query', @parent_id, 1 ); + +INSERT INTO t_menu ( menu_name, menu_type, parent_id, frame_flag, cache_flag, visible_flag, disabled_flag, perms_type, api_perms, web_perms, context_menu_id, create_user_id ) +VALUES ( '添加', 3, @parent_id, false, false, true, false, 1, '${name.lowerCamel}:add', '${name.lowerCamel}:add', @parent_id, 1 ); + +INSERT INTO t_menu ( menu_name, menu_type, parent_id, frame_flag, cache_flag, visible_flag, disabled_flag, perms_type, api_perms, web_perms, context_menu_id, create_user_id ) +VALUES ( '更新', 3, @parent_id, false, false, true, false, 1, '${name.lowerCamel}:update', '${name.lowerCamel}:update', @parent_id, 1 ); + +INSERT INTO t_menu ( menu_name, menu_type, parent_id, frame_flag, cache_flag, visible_flag, disabled_flag, perms_type, api_perms, web_perms, context_menu_id, create_user_id ) +VALUES ( '删除', 3, @parent_id, false, false, true, false, 1, '${name.lowerCamel}:delete', '${name.lowerCamel}:delete', @parent_id, 1 ); diff --git a/smart-flow-api/sa-base/src/main/resources/code-generator-template/js/api.js.vm b/smart-flow-api/sa-base/src/main/resources/code-generator-template/js/api.js.vm new file mode 100644 index 0000000..b249d0c --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/code-generator-template/js/api.js.vm @@ -0,0 +1,78 @@ +/** + * ${basic.description} api 封装 + * + * @Author: ${basic.frontAuthor} + * @Date: ${basic.frontDate} + * @Copyright ${basic.copyright} + */ +import { postRequest, getRequest } from '/@/lib/axios'; + +export const ${name.lowerCamel}Api = { + + /** + * 分页查询 @author ${basic.frontAuthor} + */ + queryPage : (param) => { + return postRequest('/${name.lowerCamel}/queryPage', param); + }, + + /** + * 增加 @author ${basic.frontAuthor} + */ + add: (param) => { + return postRequest('/${name.lowerCamel}/add', param); + }, + + /** + * 修改 @author ${basic.frontAuthor} + */ + update: (param) => { + return postRequest('/${name.lowerCamel}/update', param); + }, +## ------------------ 详情 ------------------ + +#if($deleteInfo.isSupportDetail) + /** + * 获取详情 @author ${basic.frontAuthor} + */ + getDetail: (id) => { + return getRequest(`/${name.lowerCamel}/getDetail/\${id}`); + }, +#end + +## ------------------ 删除 ------------------ +#if($deleteInfo.isSupportDelete) + #if($deleteInfo.deleteEnum == 'Single') + /** + * 删除 @author ${basic.frontAuthor} + */ + delete: (id) => { + return getRequest(`/${name.lowerCamel}/delete/${id}`); + }, + #end + #if($deleteInfo.deleteEnum == 'Batch') + /** + * 批量删除 @author ${basic.frontAuthor} + */ + batchDelete: (idList) => { + return postRequest('/${name.lowerCamel}/batchDelete', idList); + }, + #end + #if($deleteInfo.deleteEnum == 'SingleAndBatch') + /** + * 删除 @author ${basic.frontAuthor} + */ + delete: (id) => { + return getRequest(`/${name.lowerCamel}/delete/${id}`); + }, + + /** + * 批量删除 @author ${basic.frontAuthor} + */ + batchDelete: (idList) => { + return postRequest('/${name.lowerCamel}/batchDelete', idList); + }, + #end +#end + +}; diff --git a/smart-flow-api/sa-base/src/main/resources/code-generator-template/js/const.js.vm b/smart-flow-api/sa-base/src/main/resources/code-generator-template/js/const.js.vm new file mode 100644 index 0000000..30ca06a --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/code-generator-template/js/const.js.vm @@ -0,0 +1,23 @@ +/** + * ${basic.description} 枚举 + * + * @Author: ${basic.frontAuthor} + * @Date: ${basic.frontDate} + * @Copyright ${basic.copyright} + */ + +#foreach ($enum in $enumList) + +/** + * $enum.columnComment + */ +export const $enum.upperUnderscoreEnum = { + +} +#end + +export default { +#foreach ($enum in $enumList) + $enum.upperUnderscoreEnum, +#end +}; \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/resources/code-generator-template/js/form.vue.vm b/smart-flow-api/sa-base/src/main/resources/code-generator-template/js/form.vue.vm new file mode 100644 index 0000000..6291a44 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/code-generator-template/js/form.vue.vm @@ -0,0 +1,239 @@ + + + diff --git a/smart-flow-api/sa-base/src/main/resources/code-generator-template/js/list.vue.vm b/smart-flow-api/sa-base/src/main/resources/code-generator-template/js/list.vue.vm new file mode 100644 index 0000000..e336227 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/code-generator-template/js/list.vue.vm @@ -0,0 +1,340 @@ + + + diff --git a/smart-flow-api/sa-base/src/main/resources/code-generator-template/tools.xml b/smart-flow-api/sa-base/src/main/resources/code-generator-template/tools.xml new file mode 100644 index 0000000..bcfc8fe --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/code-generator-template/tools.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/smart-flow-api/sa-base/src/main/resources/dev/sa-base.yaml b/smart-flow-api/sa-base/src/main/resources/dev/sa-base.yaml new file mode 100644 index 0000000..f45ad80 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/dev/sa-base.yaml @@ -0,0 +1,192 @@ +spring: + # 数据库连接信息 + datasource: + url: jdbc:kingbase8://8.162.3.203:54321/kingbase?currentSchema=public,sys_catalog&clientEncoding=utf8&socketTimeout=300000&loginTimeout=3000 + username: dcms_dev + password: sdy@2025#dc$ks + driver-class-name: com.kingbase8.Driver + initial-size: 2 + min-idle: 2 + max-active: 10 + max-wait: 60000 + time-between-eviction-runs-millis: 60000 + min-evictable-idle-time-millis: 300000 + filters: stat + druid: + username: druid + password: 1024 + login: + enabled: false + method: + pointcut: net.lab1024.sa..*Service.* + + # redis 连接池配置信息 + data: + redis: + database: 1 + host: 8.162.3.203 + port: 6379 + password: sdy@2025#dc + timeout: 10000ms + lettuce: + pool: + max-active: 5 + min-idle: 1 + max-idle: 3 + max-wait: 30000ms + + # 邮件,置以SSL的方式发送, 这个需要使用这种方式并且端口是465 + mail: + host: smtp.163.com + port: 465 + username: lab1024@163.com + password: LAB1024LAB + properties: + mail: + smtp: + auth: true + ssl: + enable: true + socketFactory: + class: com.sun.mail.util.MailSSLSocketFactory + fallback: false + debug: false + + # json序列化相关配置 + jackson: + serialization: + write-enums-using-to-string: true + write-dates-as-timestamps: false + deserialization: + read-enums-using-to-string: true + fail-on-unknown-properties: false + default-property-inclusion: always + date-format: yyyy-MM-dd HH:mm:ss + time-zone: GMT+8 + + # 缓存实现类型 + cache: + type: caffeine + + # 文件上传大小配置 + servlet: + multipart: + max-file-size: 50MB + max-request-size: 50MB + +# tomcat 配置,主要用于 配置 访问日志(便于将来排查错误) +server: + tomcat: + basedir: ${project.log-directory}/tomcat-logs + accesslog: + enabled: true + max-days: 7 + pattern: "%t %{X-Forwarded-For}i %a %r %s (%D ms) %I (%B byte)" + +# 文件上传 配置 +file: + storage: + mode: local + local: + upload-path: ../upload/ #文件上传目录 + url-prefix: /upload/ #使用相对路径,避免混合内容问题 + cloud: + region: oss-cn-hangzhou + endpoint: oss-cn-hangzhou.aliyuncs.com + bucket-name: 1024lab-smart-admin + access-key: + secret-key: + url-prefix: https://${file.storage.cloud.bucket-name}.${file.storage.cloud.endpoint}/ + private-url-expire-seconds: 3600 + +# open api配置 +springdoc: + swagger-ui: + enabled: true # 开关 + doc-expansion: none #关闭展开 + tags-sorter: alpha + server-base-url: + api-docs: + enabled: true # 开关 +knife4j: + enable: true + basic: + enable: false + username: api # Basic认证用户名 + password: 1024 # Basic认证密码 + +# RestTemplate 请求配置 +http: + pool: + max-total: 20 + connect-timeout: 50000 + read-timeout: 50000 + write-timeout: 50000 + keep-alive: 300000 + +# 跨域配置 +access-control-allow-origin: '*' + +# 心跳配置 +heart-beat: + interval-seconds: 300 + +# 热加载配置 +reload: + interval-seconds: 300 + +# sa-token 配置 +sa-token: + # token 名称(同时也是 cookie 名称) + token-name: Authorization + # token 前缀 例如:Bearer + token-prefix: Bearer + # token 有效期(单位:秒) 默认30天(2592000秒),-1 代表永久有效 + timeout: 2592000 + # token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结 + active-timeout: -1 + # 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录) + is-concurrent: false + # 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)(jwt模式下恒false) + is-share: false + # token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)(jwt模式下无用) + token-style: simple-uuid + # 是否打开自动续签 (如果此值为true,框架会在每次直接或间接调用 getLoginId() 时进行一次过期检查与续签操作) + auto-renew: true + # 是否输出操作日志 + is-log: true + # 日志等级(trace、debug、info、warn、error、fatal) + log-level: debug + # 启动时的字符画打印 + is-print: false + # 是否从cookie读取token + is-read-cookie: false + +# SmartJob 定时任务配置(不需要可以直接删除以下配置,详细文档请看:https://www.xxxxxx.com) +smart: + job: + enabled: true + # 任务初始化延迟 默认30秒 可选 + init-delay: 10 + # 定时任务执行线程池数量 默认2 可选 + core-pool-size: 2 + # 数据库配置检测-开关 默认开启 可选(作用是固定间隔读取数据库配置更新任务,关闭后只能重启服务或通过接口修改定时任务,建议开启) + db-refresh-enabled: true + # 数据库配置检测-执行间隔 默认120秒 可选 + db-refresh-interval: 60 +--- # warm-flow工作流配置 +warm-flow: + # 是否开启工作流,默认true + enabled: true + # 是否开启设计器ui + ui: true + # 默认Authorization,如果有多个token,用逗号分隔 + token-name: ${sa-token.token-name} + # 流程状态对应的三元色 + chart-status-color: + ## 未办理 + - 62,62,62 + ## 待办理 + - 255,205,23 + ## 已办理 + - 157,255,0 \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/resources/ip2region.xdb b/smart-flow-api/sa-base/src/main/resources/ip2region.xdb new file mode 100644 index 0000000..9f6502b Binary files /dev/null and b/smart-flow-api/sa-base/src/main/resources/ip2region.xdb differ diff --git a/smart-flow-api/sa-base/src/main/resources/mapper/rights/PoliceEventMapper.xml b/smart-flow-api/sa-base/src/main/resources/mapper/rights/PoliceEventMapper.xml new file mode 100644 index 0000000..c7c90b9 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/mapper/rights/PoliceEventMapper.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + diff --git a/smart-flow-api/sa-base/src/main/resources/mapper/support/ChangeLogMapper.xml b/smart-flow-api/sa-base/src/main/resources/mapper/support/ChangeLogMapper.xml new file mode 100644 index 0000000..9d9cdc6 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/mapper/support/ChangeLogMapper.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/resources/mapper/support/CodeGeneratorMapper.xml b/smart-flow-api/sa-base/src/main/resources/mapper/support/CodeGeneratorMapper.xml new file mode 100644 index 0000000..5c26474 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/mapper/support/CodeGeneratorMapper.xml @@ -0,0 +1,34 @@ + + + + + + + + + + diff --git a/smart-flow-api/sa-base/src/main/resources/mapper/support/ConfigMapper.xml b/smart-flow-api/sa-base/src/main/resources/mapper/support/ConfigMapper.xml new file mode 100644 index 0000000..88882fe --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/mapper/support/ConfigMapper.xml @@ -0,0 +1,22 @@ + + + + + + + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/resources/mapper/support/DataTracerMapper.xml b/smart-flow-api/sa-base/src/main/resources/mapper/support/DataTracerMapper.xml new file mode 100644 index 0000000..a713935 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/mapper/support/DataTracerMapper.xml @@ -0,0 +1,31 @@ + + + + + + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/resources/mapper/support/DictDataMapper.xml b/smart-flow-api/sa-base/src/main/resources/mapper/support/DictDataMapper.xml new file mode 100644 index 0000000..0a3ae5c --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/mapper/support/DictDataMapper.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + diff --git a/smart-flow-api/sa-base/src/main/resources/mapper/support/DictMapper.xml b/smart-flow-api/sa-base/src/main/resources/mapper/support/DictMapper.xml new file mode 100644 index 0000000..ef58108 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/mapper/support/DictMapper.xml @@ -0,0 +1,45 @@ + + + + + + + t_dict.dict_id, + t_dict.dict_name, + t_dict.dict_code, + t_dict.remark, + t_dict.disabled_flag, + t_dict.create_time, + t_dict.update_time + + + + + + + + + diff --git a/smart-flow-api/sa-base/src/main/resources/mapper/support/FeedbackMapper.xml b/smart-flow-api/sa-base/src/main/resources/mapper/support/FeedbackMapper.xml new file mode 100644 index 0000000..1cc90cb --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/mapper/support/FeedbackMapper.xml @@ -0,0 +1,26 @@ + + + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/resources/mapper/support/FileMapper.xml b/smart-flow-api/sa-base/src/main/resources/mapper/support/FileMapper.xml new file mode 100644 index 0000000..e3cdad7 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/mapper/support/FileMapper.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/resources/mapper/support/HeartBeatRecordMapper.xml b/smart-flow-api/sa-base/src/main/resources/mapper/support/HeartBeatRecordMapper.xml new file mode 100644 index 0000000..ce7100e --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/mapper/support/HeartBeatRecordMapper.xml @@ -0,0 +1,37 @@ + + + + + + + update t_heart_beat_record + set heart_beat_time = #{heartBeatTime} + + heart_beat_record_id = #{id} + + + + + + + + diff --git a/smart-flow-api/sa-base/src/main/resources/mapper/support/HelpDocDao.xml b/smart-flow-api/sa-base/src/main/resources/mapper/support/HelpDocDao.xml new file mode 100644 index 0000000..b55e7e2 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/mapper/support/HelpDocDao.xml @@ -0,0 +1,130 @@ + + + + + + + + + + + + update t_help_doc + set page_view_count = page_view_count + #{pageViewCountIncrease}, + user_view_count = user_view_count + #{userViewCountIncrease} + where help_doc_id = #{helpDocId} + + + + + + + + + + insert into t_help_doc_relation + (relation_id, relation_name, help_doc_id) + values + + ( #{item.relationId} ,#{item.relationName}, #{helpDocId} ) + + + + + delete + from t_help_doc_relation + where help_doc_id = #{helpDocId} + + + + + + + + insert into t_help_doc_view_record (help_doc_id, user_id,user_name, first_ip, first_user_agent, page_view_count) + values (#{helpDocId}, #{userId},#{userName}, #{ip}, #{userAgent}, #{pageViewCount}) + + + update t_help_doc_view_record + set page_view_count = page_view_count + 1, + last_ip = #{ip}, + last_user_agent = #{userAgent} + where help_doc_id = #{helpDocId} + and user_id = #{userId} + + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/resources/mapper/support/LoginFailMapper.xml b/smart-flow-api/sa-base/src/main/resources/mapper/support/LoginFailMapper.xml new file mode 100644 index 0000000..e41b9c6 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/mapper/support/LoginFailMapper.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + delete + from t_login_fail + where user_id = #{userId} + and user_type = #{userType} + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/resources/mapper/support/LoginLogMapper.xml b/smart-flow-api/sa-base/src/main/resources/mapper/support/LoginLogMapper.xml new file mode 100644 index 0000000..b2b88ed --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/mapper/support/LoginLogMapper.xml @@ -0,0 +1,47 @@ + + + + + + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/resources/mapper/support/MessageMapper.xml b/smart-flow-api/sa-base/src/main/resources/mapper/support/MessageMapper.xml new file mode 100644 index 0000000..2a3764c --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/mapper/support/MessageMapper.xml @@ -0,0 +1,56 @@ + + + + + + + UPDATE t_message + SET read_flag = #{readFlag}, + read_time = now() + WHERE message_id = #{messageId} + AND receiver_user_type = #{receiverUserType} + AND receiver_user_id = #{receiverUserId} + AND read_flag != #{readFlag} + + + + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/resources/mapper/support/OperateLogMapper.xml b/smart-flow-api/sa-base/src/main/resources/mapper/support/OperateLogMapper.xml new file mode 100644 index 0000000..d00adff --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/mapper/support/OperateLogMapper.xml @@ -0,0 +1,45 @@ + + + + + + + + delete from t_operate_log where id in + + #{item} + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/resources/mapper/support/PasswordLogMapper.xml b/smart-flow-api/sa-base/src/main/resources/mapper/support/PasswordLogMapper.xml new file mode 100644 index 0000000..84ef798 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/mapper/support/PasswordLogMapper.xml @@ -0,0 +1,28 @@ + + + + + + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/resources/mapper/support/ReloadItemMapper.xml b/smart-flow-api/sa-base/src/main/resources/mapper/support/ReloadItemMapper.xml new file mode 100644 index 0000000..40fea4c --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/mapper/support/ReloadItemMapper.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/resources/mapper/support/ReloadResultMapper.xml b/smart-flow-api/sa-base/src/main/resources/mapper/support/ReloadResultMapper.xml new file mode 100644 index 0000000..99a4fb9 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/mapper/support/ReloadResultMapper.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/resources/mapper/support/SerialNumberMapper.xml b/smart-flow-api/sa-base/src/main/resources/mapper/support/SerialNumberMapper.xml new file mode 100644 index 0000000..eba23ed --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/mapper/support/SerialNumberMapper.xml @@ -0,0 +1,21 @@ + + + + + + update t_serial_number + set + last_number = #{lastNumber}, + last_time = #{lastTime} + where + serial_number_id = #{serialNumberId} + + + + + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/resources/mapper/support/SerialNumberRecordMapper.xml b/smart-flow-api/sa-base/src/main/resources/mapper/support/SerialNumberRecordMapper.xml new file mode 100644 index 0000000..b59fc53 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/mapper/support/SerialNumberRecordMapper.xml @@ -0,0 +1,32 @@ + + + + + + update t_serial_number_record + set last_number = #{lastNumber}, + count = count + #{count} + where + serial_number_id = #{serialNumberId} + and + record_date = #{recordDate} + + + + + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/resources/mapper/support/SmartJobLogMapper.xml b/smart-flow-api/sa-base/src/main/resources/mapper/support/SmartJobLogMapper.xml new file mode 100644 index 0000000..48727dd --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/mapper/support/SmartJobLogMapper.xml @@ -0,0 +1,35 @@ + + + + + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/resources/mapper/support/SmartJobMapper.xml b/smart-flow-api/sa-base/src/main/resources/mapper/support/SmartJobMapper.xml new file mode 100644 index 0000000..bd1ad56 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/mapper/support/SmartJobMapper.xml @@ -0,0 +1,43 @@ + + + + + + update t_smart_job + set deleted_flag = #{deletedFlag} + where job_id = #{jobId} + + + + + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/resources/mapper/support/TableColumnMapper.xml b/smart-flow-api/sa-base/src/main/resources/mapper/support/TableColumnMapper.xml new file mode 100644 index 0000000..1f11c60 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/mapper/support/TableColumnMapper.xml @@ -0,0 +1,18 @@ + + + + + delete + from t_table_column + where user_id = #{userId} + and table_id = #{tableId} + + + + \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/resources/mapper/support/WorkBookMapper.xml b/smart-flow-api/sa-base/src/main/resources/mapper/support/WorkBookMapper.xml new file mode 100644 index 0000000..81c7cc0 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/mapper/support/WorkBookMapper.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + diff --git a/smart-flow-api/sa-base/src/main/resources/pre/sa-base.yaml b/smart-flow-api/sa-base/src/main/resources/pre/sa-base.yaml new file mode 100644 index 0000000..f6bfb40 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/pre/sa-base.yaml @@ -0,0 +1,191 @@ +spring: + # 数据库连接信息 + datasource: + url: jdbc:kingbase8://8.162.3.203:54321/kingbase?currentSchema=public,sys_catalog&clientEncoding=utf8&socketTimeout=300000&loginTimeout=3000 + username: dcms_dev + password: sdy@2025#dc$ks + driver-class-name: com.kingbase8.Driver + initial-size: 2 + min-idle: 2 + max-active: 10 + max-wait: 60000 + time-between-eviction-runs-millis: 60000 + min-evictable-idle-time-millis: 300000 + filters: stat + druid: + username: druid + password: 1024 + login: + enabled: false + method: + pointcut: net.lab1024.sa..*Service.* + + # redis 连接池配置信息 + data: + redis: + database: 10 + host: 8.162.3.203 + port: 6379 + password: sdy@2025#dc + lettuce: + pool: + max-active: 5 + min-idle: 1 + max-idle: 3 + max-wait: 30000ms + + # 邮件,置以SSL的方式发送, 这个需要使用这种方式并且端口是465 + mail: + host: smtp.163.com + port: 465 + username: lab1024@163.com + password: LAB1024LAB + properties: + mail: + smtp: + auth: true + ssl: + enable: true + socketFactory: + class: com.sun.mail.util.MailSSLSocketFactory + fallback: false + debug: false + + # json序列化相关配置 + jackson: + serialization: + write-enums-using-to-string: true + write-dates-as-timestamps: false + deserialization: + read-enums-using-to-string: true + fail-on-unknown-properties: false + default-property-inclusion: always + date-format: yyyy-MM-dd HH:mm:ss + time-zone: GMT+8 + + # 缓存实现类型 + cache: + type: caffeine + + # 文件上传大小配置 + servlet: + multipart: + max-file-size: 50MB + max-request-size: 50MB + +# tomcat 配置,主要用于 配置 访问日志(便于将来排查错误) +server: + tomcat: + basedir: ${project.log-directory}/tomcat-logs + connection-timeout: -1 + accesslog: + enabled: true + max-days: 7 + pattern: "%t %{X-Forwarded-For}i %a %r %s (%D ms) %I (%B byte)" + +# 文件上传 配置 +file: + storage: + mode: local + local: + upload-path: /home/smart_admin_v3/upload/ #文件上传目录 + url-prefix: /upload/ #使用相对路径,避免混合内容问题 + cloud: + region: oss-cn-hangzhou + endpoint: oss-cn-hangzhou.aliyuncs.com + bucket-name: 1024lab-smart-admin + access-key: + secret-key: + url-prefix: https://${file.storage.cloud.bucket-name}.${file.storage.cloud.endpoint}/ + private-url-expire-seconds: 3600 + +# open api配置 +springdoc: + swagger-ui: + enabled: true # 开关 + doc-expansion: none #关闭展开 + tags-sorter: alpha + server-base-url: + api-docs: + enabled: true # 开关 +knife4j: + enable: true + basic: + enable: false + username: api # Basic认证用户名 + password: 1024 # Basic认证密码 + +# RestTemplate 请求配置 +http: + pool: + max-total: 20 + connect-timeout: 50000 + read-timeout: 50000 + write-timeout: 50000 + keep-alive: 300000 + +# 跨域配置 +access-control-allow-origin: '*' + +# 心跳配置 +heart-beat: + interval-seconds: 300 + +# 热加载配置 +reload: + interval-seconds: 300 + +# sa-token 配置 +sa-token: + # token 名称(同时也是 cookie 名称) + token-name: Authorization + # token 前缀 例如:Bear + token-prefix: Bearer + # token 有效期(单位:秒) 默认30天(2592000秒),-1 代表永久有效 + timeout: 2592000 + # token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结 + active-timeout: -1 + # 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录) + is-concurrent: false + # 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)(jwt模式下恒false) + is-share: false + # token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)(jwt模式下无用) + token-style: simple-uuid + # 是否打开自动续签 (如果此值为true,框架会在每次直接或间接调用 getLoginId() 时进行一次过期检查与续签操作) + auto-renew: true + # 是否输出操作日志 + is-log: true + # 日志等级(trace、debug、info、warn、error、fatal) + log-level: debug + # 启动时的字符画打印 + is-print: false + # 是否从cookie读取token + is-read-cookie: false + +# SmartJob 定时任务配置(不需要可以直接删除以下配置,详细文档请看:https://www.xxxxxx.com) +smart: + job: + enabled: true + # 任务初始化延迟 默认30秒 可选 + init-delay: 10 + # 定时任务执行线程池数量 默认2 可选 + core-pool-size: 2 + # 数据库配置检测-开关 默认开启 可选(作用是固定间隔读取数据库配置更新任务,关闭后只能重启服务或通过接口修改定时任务,建议开启) + db-refresh-enabled: true + # 数据库配置检测-执行间隔 默认120秒 可选 + db-refresh-interval: 60 +warm-flow: + # 是否开启工作流,默认true + enabled: true + # 是否开启设计器ui + ui: true + # 默认Authorization,如果有多个token,用逗号分隔 + token-name: ${sa-token.token-name} + # 流程状态对应的三元色 + chart-status-color: + ## 未办理 + - 62,62,62 + ## 待办理 + - 255,205,23 + ## 已办理 + - 157,255,0 \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/resources/prod/sa-base.yaml b/smart-flow-api/sa-base/src/main/resources/prod/sa-base.yaml new file mode 100644 index 0000000..f899c87 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/prod/sa-base.yaml @@ -0,0 +1,190 @@ +spring: + # 数据库连接信息 + datasource: + url: jdbc:kingbase8://53.1.211.188:54321/kingbase?currentSchema=mjwq,sys_catalog&clientEncoding=utf8&socketTimeout=300000&loginTimeout=3000 + username: szdcpt + password: szdcpt + driver-class-name: com.kingbase8.Driver + initial-size: 10 + min-idle: 10 + max-active: 100 + max-wait: 60000 + time-between-eviction-runs-millis: 60000 + min-evictable-idle-time-millis: 300000 + filters: stat + druid: + username: druid + password: 1024lab + login: + enabled: false + method: + pointcut: net.lab1024.sa..*Service.* + + # redis 连接池配置信息 + data: + redis: + database: 1 + host: 127.0.0.1 + port: 6379 + password: + timeout: 10000ms + lettuce: + pool: + max-active: 100 + min-idle: 10 + max-idle: 50 + max-wait: 30000ms + # 邮件,置以SSL的方式发送, 这个需要使用这种方式并且端口是465 + mail: + host: smtp.163.com + port: 465 + username: lab1024@163.com + password: LAB1024LAB + properties: + mail: + smtp: + auth: true + ssl: + enable: true + socketFactory: + class: com.sun.mail.util.MailSSLSocketFactory + fallback: false + debug: false + + # json序列化相关配置 + jackson: + serialization: + write-enums-using-to-string: true + write-dates-as-timestamps: false + deserialization: + read-enums-using-to-string: true + fail-on-unknown-properties: false + default-property-inclusion: always + date-format: yyyy-MM-dd HH:mm:ss + time-zone: GMT+8 + + # 缓存实现类型 + cache: + type: caffeine + + # 文件上传大小配置 + servlet: + multipart: + max-file-size: 50MB + max-request-size: 50MB + +# tomcat 配置,主要用于 配置 访问日志(便于将来排查错误) +server: + tomcat: + basedir: ${project.log-directory}/tomcat-logs + accesslog: + enabled: true + max-days: 30 + pattern: "%t %{X-Forwarded-For}i %a %r %s (%D ms) %I (%B byte)" + + +# 文件上传 配置 +file: + storage: + mode: local + local: + upload-path: /home/smart_admin_v3/upload/ #文件上传目录 + url-prefix: /upload/ #使用相对路径,避免混合内容问题 + cloud: + region: oss-cn-hangzhou + endpoint: oss-cn-hangzhou.aliyuncs.com + bucket-name: 1024lab-smart-admin + access-key: + secret-key: + url-prefix: https://${file.storage.cloud.bucket-name}.${file.storage.cloud.endpoint}/ + private-url-expire-seconds: 3600 + + +# open api配置 +springdoc: + swagger-ui: + enabled: true # 开关 + doc-expansion: none #关闭展开 + tags-sorter: alpha + api-docs: + enabled: true # 开关 +knife4j: + enable: true + basic: + enable: true + username: api # Basic认证用户名 + password: 1024 # Basic认证密码 + +# RestTemplate 请求配置 +http: + pool: + max-total: 100 + connect-timeout: 50000 + read-timeout: 50000 + write-timeout: 50000 + keep-alive: 300000 + +# 心跳配置 +heart-beat: + interval-seconds: 60 + +# 热加载配置 +reload: + interval-seconds: 60 + +# sa-token 配置 +sa-token: + # token 名称(同时也是 cookie 名称) + token-name: Authorization + # token 前缀 例如:Bear + token-prefix: Bearer + # token 有效期(单位:秒) 默认30天(2592000秒),-1 代表永久有效 + timeout: 2592000 + # token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结 + active-timeout: -1 + # 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录) + is-concurrent: false + # 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)(jwt模式下恒false) + is-share: false + # token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)(jwt模式下无用) + token-style: simple-uuid + # 是否打开自动续签 (如果此值为true,框架会在每次直接或间接调用 getLoginId() 时进行一次过期检查与续签操作) + auto-renew: true + # 是否输出操作日志 + is-log: false + # 日志等级(trace、debug、info、warn、error、fatal) + log-level: warn + # 启动时的字符画打印 + is-print: false + # 是否从cookie读取token + is-read-cookie: false + +# SmartJob 定时任务配置(不需要可以直接删除以下配置,详细文档请看:https://www.xxxxxx.com) +smart: + job: + enabled: true + # 任务初始化延迟 默认30秒 可选 + init-delay: 10 + # 定时任务执行线程池数量 默认2 可选 + core-pool-size: 2 + # 数据库配置检测-开关 默认开启 可选(作用是固定间隔读取数据库配置更新任务,关闭后只能重启服务或通过接口修改定时任务,建议开启) + db-refresh-enabled: true + # 数据库配置检测-执行间隔 默认120秒 可选 + db-refresh-interval: 60 + +--- # warm-flow工作流配置 +warm-flow: + # 是否开启工作流,默认true + enabled: true + # 是否开启设计器ui + ui: true + # 默认Authorization,如果有多个token,用逗号分隔 + token-name: ${sa-token.token-name} + # 流程状态对应的三元色 + chart-status-color: + ## 未办理 + - 62,62,62 + ## 待办理 + - 255,205,23 + ## 已办理 + - 157,255,0 \ No newline at end of file diff --git a/smart-flow-api/sa-base/src/main/resources/test/sa-base.yaml b/smart-flow-api/sa-base/src/main/resources/test/sa-base.yaml new file mode 100644 index 0000000..e852d82 --- /dev/null +++ b/smart-flow-api/sa-base/src/main/resources/test/sa-base.yaml @@ -0,0 +1,192 @@ +spring: + # 数据库连接信息 + datasource: + url: jdbc:kingbase8://8.162.3.203:54321/kingbase?currentSchema=public,sys_catalog&clientEncoding=utf8&socketTimeout=300000&loginTimeout=3000 + username: dcms_dev + password: sdy@2025#dc$ks + driver-class-name: com.kingbase8.Driver + initial-size: 2 + min-idle: 2 + max-active: 10 + max-wait: 60000 + time-between-eviction-runs-millis: 60000 + min-evictable-idle-time-millis: 300000 + filters: stat + druid: + username: druid + password: 1024 + login: + enabled: false + method: + pointcut: net.lab1024.sa..*Service.* + + # redis 连接池配置信息 + data: + redis: + database: 10 + host: 8.162.3.203 + port: 6379 + password: sdy@2025#dc + timeout: 10000ms + lettuce: + pool: + max-active: 5 + min-idle: 1 + max-idle: 3 + max-wait: 30000ms + + # 邮件,置以SSL的方式发送, 这个需要使用这种方式并且端口是465 + mail: + host: smtp.163.com + port: 465 + username: lab1024@163.com + password: LAB1024LAB + properties: + mail: + smtp: + auth: true + ssl: + enable: true + socketFactory: + class: com.sun.mail.util.MailSSLSocketFactory + fallback: false + debug: false + + # json序列化相关配置 + jackson: + serialization: + write-enums-using-to-string: true + write-dates-as-timestamps: false + deserialization: + read-enums-using-to-string: true + fail-on-unknown-properties: false + default-property-inclusion: always + date-format: yyyy-MM-dd HH:mm:ss + time-zone: GMT+8 + + # 缓存实现类型 + cache: + type: caffeine + + # 文件上传大小配置 + servlet: + multipart: + max-file-size: 50MB + max-request-size: 50MB + +# tomcat 配置,主要用于 配置 访问日志(便于将来排查错误) +server: + tomcat: + basedir: ${project.log-directory}/tomcat-logs + accesslog: + enabled: true + max-days: 7 + pattern: "%t %{X-Forwarded-For}i %a %r %s (%D ms) %I (%B byte)" + +# 文件上传 配置 +file: + storage: + mode: local + local: + upload-path: ../upload/ #文件上传目录 + url-prefix: /upload/ #使用相对路径,避免混合内容问题 + cloud: + region: oss-cn-hangzhou + endpoint: oss-cn-hangzhou.aliyuncs.com + bucket-name: 1024lab-smart-admin + access-key: + secret-key: + url-prefix: https://${file.storage.cloud.bucket-name}.${file.storage.cloud.endpoint}/ + private-url-expire-seconds: 3600 + +# open api配置 +springdoc: + swagger-ui: + enabled: true # 开关 + doc-expansion: none #关闭展开 + tags-sorter: alpha + server-base-url: + api-docs: + enabled: true # 开关 +knife4j: + enable: true + basic: + enable: false + username: api # Basic认证用户名 + password: 1024 # Basic认证密码 + +# RestTemplate 请求配置 +http: + pool: + max-total: 20 + connect-timeout: 50000 + read-timeout: 50000 + write-timeout: 50000 + keep-alive: 300000 + +# 跨域配置 +access-control-allow-origin: '*' + +# 心跳配置 +heart-beat: + interval-seconds: 300 + +# 热加载配置 +reload: + interval-seconds: 300 + +# sa-token 配置 +sa-token: + # token 名称(同时也是 cookie 名称) + token-name: Authorization + # token 前缀 例如:Bearer + token-prefix: Bearer + # token 有效期(单位:秒) 默认30天(2592000秒),-1 代表永久有效 + timeout: 2592000 + # token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结 + active-timeout: -1 + # 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录) + is-concurrent: false + # 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)(jwt模式下恒false) + is-share: false + # token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)(jwt模式下无用) + token-style: simple-uuid + # 是否打开自动续签 (如果此值为true,框架会在每次直接或间接调用 getLoginId() 时进行一次过期检查与续签操作) + auto-renew: true + # 是否输出操作日志 + is-log: true + # 日志等级(trace、debug、info、warn、error、fatal) + log-level: debug + # 启动时的字符画打印 + is-print: false + # 是否从cookie读取token + is-read-cookie: false + +# SmartJob 定时任务配置(不需要可以直接删除以下配置,详细文档请看:https://www.xxxxxx.com) +smart: + job: + enabled: true + # 任务初始化延迟 默认30秒 可选 + init-delay: 10 + # 定时任务执行线程池数量 默认2 可选 + core-pool-size: 2 + # 数据库配置检测-开关 默认开启 可选(作用是固定间隔读取数据库配置更新任务,关闭后只能重启服务或通过接口修改定时任务,建议开启) + db-refresh-enabled: true + # 数据库配置检测-执行间隔 默认120秒 可选 + db-refresh-interval: 60 +--- # warm-flow工作流配置 +warm-flow: + # 是否开启工作流,默认true + enabled: true + # 是否开启设计器ui + ui: true + # 默认Authorization,如果有多个token,用逗号分隔 + token-name: ${sa-token.token-name} + # 流程状态对应的三元色 + chart-status-color: + ## 未办理 + - 62,62,62 + ## 待办理 + - 255,205,23 + ## 已办理 + - 157,255,0 \ No newline at end of file diff --git a/smart-flow-api/warm-flow/pom.xml b/smart-flow-api/warm-flow/pom.xml new file mode 100644 index 0000000..a797664 --- /dev/null +++ b/smart-flow-api/warm-flow/pom.xml @@ -0,0 +1,445 @@ + + + 4.0.0 + + org.dromara.warm + warm-flow + 1.8.4 + pom + + warm-flow + Warm-Flow is a flow + https://github.com/dromara/warm-flow + + + Github Issue + https://github.com/dromara/warm-flow/issues + + + + The Apache Software License, Version 2.0 + https://apache.org/licenses/LICENSE-2.0.txt + + + + https://github.com/dromara/warm-flow.git + https://github.com/dromara/warm-flow + + + + Warm-Flow + stupidkid + 290631660@qq.com + + + Warm-Flow + xiarigang + 272227118@qq.com + + + Warm-Flow + vanlin + vanlin@139.com + + + Warm-Flow + liangli + liangli_zsw@163.com + + + Warm-Flow + Zhen + w205782390@163.com + + + + + warm-flow-core + warm-flow-orm + warm-flow-plugin + + + + ${project.version} + + + 8 + 8 + UTF-8 + 1.8 + 17 + C:\dev\Amazon Corretto\jdk17.0.6_10\bin\ + + + 2.7.18 + 3.0.1 + 3.0.2 + + 8.0.33 + 11.2.0.4 + 4.0.3 + + + 3.2.139 + 4.0.8 + 2.13.5 + 2.0.43 + 2.9.0 + + + 2.3.2 + 3.5.15 + 3.5.12 + + + 4.13.2 + 1.7.36 + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring.boot.version} + pom + import + + + + + org.noear + solon + ${solon.version} + + + + org.noear + solon-data + ${solon.version} + + + + org.noear + solon-web-staticfiles + ${solon.version} + + + + org.noear + solon-web + ${solon.version} + + + + org.noear + solon-logging-logback + ${solon.version} + + + + + + org.mybatis + mybatis + ${mybatis.version} + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + ${mybatis-spring-boot} + + + + org.noear + mybatis-solon-plugin + ${solon.version} + + + + com.baomidou + mybatis-plus-solon-plugin + ${mybatis-plus.version} + + + + + com.baomidou + mybatis-plus-jsqlparser + ${mybatis-plus.version} + + + + + com.baomidou + mybatis-plus-jsqlparser-4.9 + ${mybatis-plus.version} + + + + + + com.baomidou + mybatis-plus-extension + ${mybatis-plus.version} + + + + com.baomidou + mybatis-plus-boot-starter + ${mybatis-plus.version} + + + + org.noear + mybatis-plus-extension-solon-plugin + ${solon.version} + + + + + org.noear + snack3 + ${snack3.version} + + + + org.noear + snack4 + ${snack4.version} + + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + + com.alibaba.fastjson2 + fastjson2 + ${fastjson.version} + + + + com.google.code.gson + gson + ${gson.version} + + + + + org.slf4j + slf4j-api + ${slf4j-api.version} + + + + + + + + + + + org.dromara.warm + warm-flow-core + ${warm-flow} + + + + org.dromara.warm + warm-flow-mybatis-core + ${warm-flow} + + + + org.dromara.warm + warm-flow-mybatis-sb-starter + ${warm-flow} + + + + org.dromara.warm + warm-flow-mybatis-sb3-starter + ${warm-flow} + + + + org.dromara.warm + warm-flow-mybatis-solon-plugin + ${warm-flow} + + + + org.dromara.warm + warm-flow-mybatis-plus-core + ${warm-flow} + + + + org.dromara.warm + warm-flow-mybatis-plus-sb-starter + ${warm-flow} + + + + org.dromara.warm + warm-flow-mybatis-plus-sb3-starter + ${warm-flow} + + + + org.dromara.warm + warm-flow-mybatis-plus-solon-plugin + ${warm-flow} + + + + org.dromara.warm + warm-flow-plugin-modes-sb + ${warm-flow} + + + + org.dromara.warm + warm-flow-plugin-modes-solon + ${warm-flow} + + + + org.dromara.warm + warm-flow-plugin-json + ${warm-flow} + + + + org.dromara.warm + warm-flow-plugin-vue3-ui + ${warm-flow} + + + + org.dromara.warm + warm-flow-plugin-ui-core + ${warm-flow} + + + + org.dromara.warm + warm-flow-plugin-ui-sb-web + ${warm-flow} + + + + org.dromara.warm + warm-flow-plugin-ui-solon-web + ${warm-flow} + + + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.1.0 + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.0 + + + + ${warm-flow} + + + + + + + + + + release + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + private + true + UTF-8 + UTF-8 + UTF-8 + -Xdoclint:none + + + + package + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + sign-artifacts + verify + + sign + + + + + + org.sonatype.central + central-publishing-maven-plugin + 0.6.0 + true + + + ossrh + + true + + + + org.apache.maven.plugins + maven-deploy-plugin + 2.8.2 + + + + + + + + + + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-core/pom.xml b/smart-flow-api/warm-flow/warm-flow-core/pom.xml new file mode 100644 index 0000000..9401998 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/pom.xml @@ -0,0 +1,34 @@ + + + 4.0.0 + + org.dromara.warm + warm-flow + 1.8.4 + + + warm-flow-core + warm-flow-core + https://github.com/dromara/warm-flow + + + + junit + junit + test + + + + org.slf4j + slf4j-api + + + + org.projectlombok + lombok + provided + + + diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/FlowEngine.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/FlowEngine.java new file mode 100644 index 0000000..e5ea298 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/FlowEngine.java @@ -0,0 +1,269 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core; + +import org.dromara.warm.flow.core.config.WarmFlow; +import org.dromara.warm.flow.core.entity.*; +import org.dromara.warm.flow.core.handler.DataFillHandler; +import org.dromara.warm.flow.core.handler.PermissionHandler; +import org.dromara.warm.flow.core.handler.TenantHandler; +import org.dromara.warm.flow.core.invoker.FrameInvoker; +import org.dromara.warm.flow.core.json.JsonConvert; +import org.dromara.warm.flow.core.listener.GlobalListener; +import org.dromara.warm.flow.core.service.*; +import org.dromara.warm.flow.core.utils.ClassUtil; +import org.dromara.warm.flow.core.utils.ObjectUtil; +import org.dromara.warm.flow.core.utils.StringUtils; + +import java.lang.reflect.Constructor; +import java.util.function.Supplier; + +/** + * 流程引擎 + * + * @author warm + */ +public class FlowEngine { + + private static final DefService defService = null; + private static final NodeService nodeService = null; + private static final SkipService skipService = null; + private static final InsService insService = null; + private static final TaskService taskService = null; + private static final HisTaskService hisTaskService = null; + private static final UserService userService = null; + private static final FormService formService = null; + private static final ChartService chartService = null; + + private static Supplier defSupplier; + private static Supplier nodeSupplier; + private static Supplier skipSupplier; + private static Supplier insSupplier; + private static Supplier taskSupplier; + private static Supplier hisTaskSupplier; + private static Supplier userSupplier; + private static Supplier

formSupplier; + + private static WarmFlow flowConfig; + + private static DataFillHandler dataFillHandler; + + private static TenantHandler tenantHandler; + + private static PermissionHandler permissionHandler; + + private static GlobalListener globalListener; + + public static JsonConvert jsonConvert; + + public static DefService defService() { + return getObj(defService, DefService.class); + } + + public static NodeService nodeService() { + return getObj(nodeService, NodeService.class); + } + + public static SkipService skipService() { + return getObj(skipService, SkipService.class); + } + + public static InsService insService() { + return getObj(insService, InsService.class); + } + + public static TaskService taskService() { + return getObj(taskService, TaskService.class); + } + + public static HisTaskService hisTaskService() { + return getObj(hisTaskService, HisTaskService.class); + } + + public static UserService userService() { + return getObj(userService, UserService.class); + } + + public static FormService formService() { + return getObj(formService, FormService.class); + } + + public static ChartService chartService() { + return getObj(chartService, ChartService.class); + } + + public static void setNewDef(Supplier supplier) { + FlowEngine.defSupplier = supplier; + } + + public static Definition newDef() { + return defSupplier.get(); + } + + public static void setNewNode(Supplier supplier) { + FlowEngine.nodeSupplier = supplier; + } + + public static Node newNode() { + return nodeSupplier.get(); + } + + public static void setNewSkip(Supplier supplier) { + FlowEngine.skipSupplier = supplier; + } + + public static Skip newSkip() { + return skipSupplier.get(); + } + + public static void setNewIns(Supplier supplier) { + FlowEngine.insSupplier = supplier; + } + + public static Instance newIns() { + return insSupplier.get(); + } + + public static void setNewTask(Supplier supplier) { + FlowEngine.taskSupplier = supplier; + } + + public static Task newTask() { + return taskSupplier.get(); + } + + public static void setNewHisTask(Supplier supplier) { + FlowEngine.hisTaskSupplier = supplier; + } + + public static HisTask newHisTask() { + return hisTaskSupplier.get(); + } + + public static void setNewUser(Supplier supplier) { + FlowEngine.userSupplier = supplier; + } + + public static User newUser() { + return userSupplier.get(); + } + + public static void setNewForm(Supplier supplier) { + FlowEngine.formSupplier = supplier; + } + + public static Form newForm() { + return formSupplier.get(); + } + + public static WarmFlow getFlowConfig() { + return FlowEngine.flowConfig; + } + + public static void setFlowConfig(WarmFlow flowConfig) { + FlowEngine.flowConfig = flowConfig; + } + + public static void initDataFillHandler(String handlerPath) { + dataFillHandler = initBean(DataFillHandler.class, handlerPath, () -> new DataFillHandler() {}); + } + + public static void initTenantHandler(String handlerPath) { + tenantHandler = initBean(TenantHandler.class, handlerPath, null); + } + + public static void initPermissionHandler() { + permissionHandler = initBean(PermissionHandler.class, null, null); + } + + public static void initGlobalListener() { + globalListener = initBean(GlobalListener.class, null, null); + } + + /** + * 获取填充类 + */ + public static DataFillHandler dataFillHandler() { + return dataFillHandler; + } + + /** + * 获取填充类 + */ + public static PermissionHandler permissionHandler() { + return permissionHandler; + } + + /** + * 获取租户数据 + */ + public static TenantHandler tenantHandler() { + return tenantHandler; + } + + /** + * 获取全局监听器 + */ + public static GlobalListener globalListener() { + return globalListener; + } + + /** + * 获取数据库类型 + */ + public static String dataSourceType() { + return flowConfig.getDataSourceType(); + } + + public static T getObj(T t, Class tClass) { + if (ObjectUtil.isNotNull(t)) { + return t; + } + t = FrameInvoker.getBean(tClass); + return t; + } + + /** + * 初始化bean,先从yml配置获取bean的全包名路径,否则从spring容器获取bean,如果都没有,则通过supplier获取bean + * + * @param tClazz bean的class类型 + * @param beanPath bean全包名路径 + * @param supplier 获取bean的lambda + * @param bean类型 + * @return bean + */ + private static T initBean(Class tClazz, String beanPath, Supplier supplier) { + T hander = null; + try { + if (!StringUtils.isEmpty(beanPath)) { + Class clazz = ClassUtil.getClazz(beanPath); + if (clazz != null && tClazz.isAssignableFrom(clazz)) { + Constructor constructor = clazz.getConstructor(); + hander = tClazz.cast(constructor.newInstance()); + } + } + } catch (Exception ignored) { + } + if (hander == null) { + hander = FrameInvoker.getBean(tClazz); + } + if (hander == null && supplier != null) { + hander = supplier.get(); + } + return hander; + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/condition/AbstractConditionStrategy.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/condition/AbstractConditionStrategy.java new file mode 100644 index 0000000..bf6ffdb --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/condition/AbstractConditionStrategy.java @@ -0,0 +1,72 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.condition; + +import org.dromara.warm.flow.core.constant.ExceptionCons; +import org.dromara.warm.flow.core.constant.FlowCons; +import org.dromara.warm.flow.core.strategy.ConditionStrategy; +import org.dromara.warm.flow.core.utils.AssertUtil; +import org.dromara.warm.flow.core.utils.ExpressionUtil; + +import java.util.Map; + +/** + * 条件表达式抽象类,复用部分代码 + * + * @author warm + */ +public abstract class AbstractConditionStrategy implements ConditionStrategy { + + + /** + * 执行表达式前置方法 合法性校验 + * + * @param name 变量名称:flag + * @param variable 流程变量 + */ + public void preEval(String name, Map variable) { + AssertUtil.isEmpty(variable, ExceptionCons.NULL_CONDITION_VALUE); + Object o = variable.get(name); + AssertUtil.isNull(o, ExceptionCons.NULL_CONDITION_VALUE); + } + + /** + * 执行表达式 + * + * @param expression 表达式:flag@@5 + * 在{@link ExpressionUtil#evalCondition}中格式为,比如:eq@@flag|5, + * 截取前缀进入此方法后为:flag|5 + * @param variable 流程变量 + * @return 执行结果 + */ + @Override + public Boolean eval(String expression, Map variable) { + String[] split = expression.split(FlowCons.SPLIT_VERTICAL); + preEval(split[0].trim(), variable); + String variableValue = String.valueOf(variable.get(split[0].trim())); + return afterEval(split[1].trim(), variableValue); + } + + /** + * 执行表达式后置方法 + * + * @param value 表达式最后一个参数,比如:eq@@flag|5的[5] + * @param variableValue 流程变量值 + * @return 执行结果 + */ + public abstract Boolean afterEval(String value, String variableValue); + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/condition/ConditionStrategyEq.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/condition/ConditionStrategyEq.java new file mode 100644 index 0000000..445e5e7 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/condition/ConditionStrategyEq.java @@ -0,0 +1,42 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.condition; + +import org.dromara.warm.flow.core.enums.ConditionType; +import org.dromara.warm.flow.core.utils.MathUtil; + +/** + * 条件表达式等于 eq@@flag|4 + * + * @author warm + */ +public class ConditionStrategyEq extends AbstractConditionStrategy { + + @Override + public String getType() { + return ConditionType.EQ.getKey(); + } + + @Override + public Boolean afterEval(String value, String variableValue) { + if (MathUtil.isNumeric(value)) { + return MathUtil.determineSize(variableValue, value) == 0; + } else { + return variableValue.equals(value); + } + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/condition/ConditionStrategyGe.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/condition/ConditionStrategyGe.java new file mode 100644 index 0000000..65d8637 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/condition/ConditionStrategyGe.java @@ -0,0 +1,42 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.condition; + +import org.dromara.warm.flow.core.enums.ConditionType; +import org.dromara.warm.flow.core.utils.MathUtil; + +/** + * 条件表达式大于等于 ge@@flag|4 + * + * @author warm + */ +public class ConditionStrategyGe extends AbstractConditionStrategy { + + @Override + public String getType() { + return ConditionType.GE.getKey(); + } + + @Override + public Boolean afterEval(String value, String variableValue) { + if (MathUtil.isNumeric(value)) { + return MathUtil.determineSize(variableValue, value) > 0 || MathUtil.determineSize(variableValue, value) == 0; + } else { + return false; + } + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/condition/ConditionStrategyGt.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/condition/ConditionStrategyGt.java new file mode 100644 index 0000000..63414cf --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/condition/ConditionStrategyGt.java @@ -0,0 +1,42 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.condition; + +import org.dromara.warm.flow.core.enums.ConditionType; +import org.dromara.warm.flow.core.utils.MathUtil; + +/** + * 条件表达式大于 gt@@flag|4 + * + * @author warm + */ +public class ConditionStrategyGt extends AbstractConditionStrategy { + + @Override + public String getType() { + return ConditionType.GT.getKey(); + } + + @Override + public Boolean afterEval(String value, String variableValue) { + if (MathUtil.isNumeric(value)) { + return MathUtil.determineSize(variableValue, value) > 0; + } else { + return false; + } + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/condition/ConditionStrategyLe.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/condition/ConditionStrategyLe.java new file mode 100644 index 0000000..e6dbeed --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/condition/ConditionStrategyLe.java @@ -0,0 +1,42 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.condition; + +import org.dromara.warm.flow.core.enums.ConditionType; +import org.dromara.warm.flow.core.utils.MathUtil; + +/** + * 条件表达式小于等于 le@@flag|4 + * + * @author warm + */ +public class ConditionStrategyLe extends AbstractConditionStrategy { + + @Override + public String getType() { + return ConditionType.LE.getKey(); + } + + @Override + public Boolean afterEval(String value, String variableValue) { + if (MathUtil.isNumeric(value)) { + return MathUtil.determineSize(variableValue, value) < 0 || MathUtil.determineSize(variableValue, value) == 0; + } else { + return false; + } + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/condition/ConditionStrategyLike.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/condition/ConditionStrategyLike.java new file mode 100644 index 0000000..fa5fba3 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/condition/ConditionStrategyLike.java @@ -0,0 +1,37 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.condition; + +import org.dromara.warm.flow.core.enums.ConditionType; + +/** + * 条件表达式包含 like@@flag|4 + * + * @author warm + */ +public class ConditionStrategyLike extends AbstractConditionStrategy { + + @Override + public String getType() { + return ConditionType.LIKE.getKey(); + } + + @Override + public Boolean afterEval(String value, String variableValue) { + return variableValue.contains(value); + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/condition/ConditionStrategyLt.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/condition/ConditionStrategyLt.java new file mode 100644 index 0000000..0e6e69c --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/condition/ConditionStrategyLt.java @@ -0,0 +1,42 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.condition; + +import org.dromara.warm.flow.core.enums.ConditionType; +import org.dromara.warm.flow.core.utils.MathUtil; + +/** + * 条件表达式小于 lt@@flag|4 + * + * @author warm + */ +public class ConditionStrategyLt extends AbstractConditionStrategy { + + @Override + public String getType() { + return ConditionType.LT.getKey(); + } + + @Override + public Boolean afterEval(String value, String variableValue) { + if (MathUtil.isNumeric(value)) { + return MathUtil.determineSize(variableValue, value) < 0; + } else { + return false; + } + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/condition/ConditionStrategyNe.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/condition/ConditionStrategyNe.java new file mode 100644 index 0000000..a460be5 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/condition/ConditionStrategyNe.java @@ -0,0 +1,42 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.condition; + +import org.dromara.warm.flow.core.enums.ConditionType; +import org.dromara.warm.flow.core.utils.MathUtil; + +/** + * 条件表达式不等于 ne@@flag|4 + * + * @author warm + */ +public class ConditionStrategyNe extends AbstractConditionStrategy { + + @Override + public String getType() { + return ConditionType.NE.getKey(); + } + + @Override + public Boolean afterEval(String value, String variableValue) { + if (MathUtil.isNumeric(value)) { + return MathUtil.determineSize(variableValue, value) != 0; + } else { + return !variableValue.equals(value); + } + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/condition/ConditionStrategyNotLike.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/condition/ConditionStrategyNotLike.java new file mode 100644 index 0000000..e38d49b --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/condition/ConditionStrategyNotLike.java @@ -0,0 +1,38 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.condition; + +import org.dromara.warm.flow.core.enums.ConditionType; + +/** + * 条件表达式不包含 notLike@@flag|4 + * 条件表达式不包含 notLike@@flag|4 and eq@@flag|5 or lt@@flag|6 + * + * @author warm + */ +public class ConditionStrategyNotLike extends AbstractConditionStrategy { + + @Override + public String getType() { + return ConditionType.NOT_LIKE.getKey(); + } + + @Override + public Boolean afterEval(String value, String variableValue) { + return !variableValue.contains(value); + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/config/WarmFlow.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/config/WarmFlow.java new file mode 100644 index 0000000..ce7096c --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/config/WarmFlow.java @@ -0,0 +1,157 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.config; + +import lombok.Getter; +import lombok.Setter; +import org.dromara.warm.flow.core.FlowEngine; +import org.dromara.warm.flow.core.enums.ChartStatus; +import org.dromara.warm.flow.core.json.JsonConvert; +import org.dromara.warm.flow.core.utils.ServiceLoaderUtil; + +import java.io.Serializable; +import java.util.List; + +/** + * WarmFlow属性配置文件 + * + * @author warm + */ +@Getter +@Setter +public class WarmFlow implements Serializable { + + /** + * 开关 + */ + private boolean enabled = true; + + /** + * 启动banner + */ + private boolean banner = true; + + /** + * id生成器类型, 不填默认为orm扩展自带生成器或者warm-flow内置的19位雪花算法, SnowId14:14位,SnowId15:15位, SnowFlake19:19位 + */ + private String keyType; + + /** + * 是否开启逻辑删除 + */ + private boolean logicDelete = false; + + /** + * 逻辑删除字段值 + */ + private String logicDeleteValue = "2"; + + /** + * 逻辑未删除字段 + */ + private String logicNotDeleteValue = "0"; + + /** + * 数据填充处理类路径 + */ + private String dataFillHandlerPath; + + /** + * 租户模式处理类路径 + */ + private String tenantHandlerPath; + + /** + * 数据源类型, mybatis模块对orm进一步的封装, 由于各数据库分页语句存在差异, + * 当配置此参数时, 以此参数结果为基准, 未配置时, 取DataSource中数据源类型, + * 兜底为mysql数据库 + */ + private String dataSourceType; + + /** + * ui开关 + */ + private boolean ui = true; + + /** + * 如果需要工作流共享业务系统权限,默认Authorization,如果有多个token,用逗号分隔 + */ + private String tokenName = "Authorization"; + + /** + * 公共模型流程状态对应的三原色 + */ + private List chartStatusColor; + + /** + * 经典模式流程状态对应的三原色 + */ + private List chartStatusColorClassics; + + /** + * 仿钉钉模式流程状态对应的三原色 + */ + private List chartStatusColorMimic; + + /** + * 是否显示流程图顶部文字 + */ + private boolean topTextShow = true; + + public void init() { + // 设置租户模式 + FlowEngine.initTenantHandler(this.getTenantHandlerPath()); + + // 设置数据填充处理类 + FlowEngine.initDataFillHandler(this.getDataFillHandlerPath()); + + // 设置办理人权限处理类 + FlowEngine.initPermissionHandler(); + + // 设置全局监听器 + FlowEngine.initGlobalListener(); + + // 打印banner图 + printBanner(); + + // 初始化流程状态对应的自定义三原色 + ChartStatus.initCustomColor(this.getChartStatusColor(), this.getChartStatusColorClassics(), this.getChartStatusColorMimic()); + + // 通过SPI机制 + spiLoad(); + + } + + public void spiLoad() { + // 通过SPI机制加载json转换策略实现类 + FlowEngine.jsonConvert = ServiceLoaderUtil.loadFirst(JsonConvert.class); + } + + private void printBanner() { + if (this.isBanner()) { + System.out.println("\n" + + " ▄ ▄ ▄▄▄▄▄▄ ▄ \n" + + " █ █ █ ▄▄▄ ▄ ▄▄ ▄▄▄▄▄ █ █ ▄▄▄ ▄ ▄ \n" + + " ▀ █▀█ █ ▀ █ █▀ ▀ █ █ █ ▄▄▄▄▄ █▄▄▄▄▄ █ █▀ ▀█ ▀▄ ▄ ▄▀ \n" + + " ██ ██▀ ▄▀▀▀█ █ █ █ █ █ █ █ █ █▄█▄█ \n" + + " █ █ ▀▄▄▀█ █ █ █ █ █ █▄▄ ▀█▄█▀ █ █ \n" + + "\n" + + "\033[32m :: Warm-Flow :: (v" + WarmFlow.class.getPackage() + .getImplementationVersion() + ")\033[0m\n"); + } + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/constant/ExceptionCons.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/constant/ExceptionCons.java new file mode 100644 index 0000000..02fb8e5 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/constant/ExceptionCons.java @@ -0,0 +1,158 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.constant; + +/** + * 工作流中用到的一些常量 + * + * @author warm + * @since 2023/3/30 14:05 + */ +public class ExceptionCons { + + public static final String SAME_CONDITION_VALUE = "中间节点,同一个节点不能有相同跳转类型,跳转同一个目标节点!"; + + public static final String SAME_CONDITION_NODE = "互斥网关,同一个节点不能有相同跳转条件,跳转同一个目标节点!"; + + public static final String SAME_DEST_NODE = "并行网关,同一个节点不能跳转同一个目标节点!"; + + public static final String MUL_START_NODE = "开始节点不能超过1个!"; + + public static final String MUL_SKIP_BETWEEN = "不可同时通过或者退回到多个中间节点,必须先流转到网关节点!"; + + public static final String MUL_START_SKIP = "节点流转条件不能超过1个!"; + + public static final String LOST_START_NODE = "流程缺少开始节点!"; + + public static final String LOST_NODE_CODE = "节点编码缺失"; + + public static final String MUST_SKIP = "开始或者中间节点必须画跳转线"; + + public static final String SAME_NODE_CODE = "同一流程中节点编码重复!"; + + public static final String NULL_DEST_NODE = "无法他跳转,未配置目标节点!"; + + public static final String NULL_SKIP_TYPE = "未找到跳转类型匹配的目标节点!"; + + public static final String NULL_CONDITION_VALUE_NODE = "未找到跳转条件,不支持跳转!"; + + public static final String NULL_CONDITION_VALUE = "跳转条件不能为空!"; + + public static final String NULL_CONDITION_STRATEGY = "条件表达式策略不能为空!"; + + public static final String NULL_VARIABLE_STRATEGY = "办理人表达式策略不能为空!"; + + public static final String NULL_LISTENER_STRATEGY = "办理人表达式策略不能为空!"; + + public static final String NULL_VOTESIGN_STRATEGY = "票签表达式策略不能为空!"; + + public static final String FIRST_FORBID_BACK = "禁止退回到第一个节点"; + + public static final String NULL_ROLE_NODE = "无法跳转到该节点,请检查当前用户是否有权限!"; + + public static final String LOST_DEST_NODE = "目标节点为空!"; + + public static final String LOST_CUR_NODE = "当前流程节点丢失!"; + + public static final String NULL_NODE_CODE = "目标节点编码不存在!"; + + public static final String NULL_BUSINESS_ID = "业务id为空!"; + + public static final String NULL_FLOW_CODE = "流程编码缺失!"; + + public static final String NOT_FOUNT_DEF = "流程定义不存在!"; + + public static final String NOT_FOUNT_INSTANCE = "流程实例获取失败!"; + + public static final String NULL_INSTANCE_ID = "流程实例id不能为空!"; + + public static final String NULL_TASK_ID = "任务id不能为空!"; + + public static final String NOT_FOUNT_TASK = "未找到待办任务!"; + + public static final String TASK_NOT_ONE = "此接口不能同时跳转多个待办任务,请更换!"; + + public static final String NOT_DEFINITION_ID = "流程定义id不能为空!"; + + public static final String NOT_NODE_DATA = "流程节点数据缺失!"; + + public static final String EXIST_START_TASK = "流程定义已开启过审批任务,不可操作!"; + + public static final String FLOW_FINISH = "流程已完成!"; + + public static final String NOT_AUTHORITY = "请检查当前用户是否有权限!"; + + public static final String SIGN_NULL_HANDLER = "会签票签时,办理人标识不能为空"; + + public static final String REDUCTION_SIGN_ONE_ERROR = "办理人不足或者只有一人,不可减签"; + + public static final String IS_ALREADY_SIGN = "已经是待办人,不可加签"; + + public static final String IS_ALREADY_TRANSFER = "已经是转办人,不可转办"; + + public static final String IS_ALREADY_DEPUTE = "已经是受托人,不可委托"; + + public static final String NOT_ACTIVITY = "当前流程定义或者实例已经挂起,请先激活"; + + public static final String NOT_DEFINITION_ACTIVITY = "当前流程定义已挂起,不可开启新的流程"; + + public static final String DEFINITION_ALREADY_ACTIVITY = "当前流程定义已经激活"; + + public static final String DEFINITION_ALREADY_SUSPENDED = "当前流程定义已经挂起"; + + public static final String INSTANCE_ALREADY_ACTIVITY = "当前流程实例已经激活"; + + public static final String FORM_ALREADY_PUBLISH = "当前表单状态已发布"; + + public static final String FORM_ALREADY_UN_PUBLISH = "当前表单状态未发布"; + + public static final String FORM_NOT_ONE = "表单数据错误, 请联系管理员排查!"; + + public static final String ID_EMPTY = "ID不能为空"; + + public static final String NOT_DEF_PROMOTER_NOT_CANCEL = "不是当前流程的发起人,无法撤销"; + + public static final String HANDLER_NOT_EMPTY = "办理人不能为空"; + + public static final String TAR_NOT_GATEWAY = "目标节点不能是网关节点!"; + + public static final String NOT_FOUND_FLOW_TASK = "未获取到流程任务"; + + public static final String FLOW_HAVE_USELESS_SKIP = "存在无用的跳转"; + + public static final String NULL_TRANSFER_HANDLER = "转办对象不能为空"; + + public static final String NULL_DEPUTE_HANDLER = "委托对象不能为空"; + + public static final String NULL_ADD_SIGNATURE_HANDLER = "加签对象不能为空"; + + public static final String NULL_REDUCTION_SIGNATURE_HANDLER = "减签对象不能为空"; + + public static final String READ_IS_ERROR = "读取is流失败"; + + public static final String EXIST_USE_FORM = "流程表单已使用,不可操作!"; + + public static final String NOT_FOUNT_LAST_TASK = "未找到前置任务!"; + + public static final String NOT_FOUNT_HANDLED_TASK = "未找到您已办理过的任务!"; + + public static final String NOT_FOUNT_HANDLED_TASK_HANDLER = "拿回的任务未办理!"; + + public static final String START_NODE_NOT_ALLOW_JUMP = "开始节点不允许跳转!"; + + public static final String NOT_DRAW_FLOW_ERROR = "未绘制流程图,不可发布"; + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/constant/FlowConfigCons.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/constant/FlowConfigCons.java new file mode 100644 index 0000000..9096b3c --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/constant/FlowConfigCons.java @@ -0,0 +1,75 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.constant; + +/** + * warm-flow配置文件常量 + * + * @author warm + */ +public class FlowConfigCons { + + /** + * 是否支持任意跳转 + */ + public static final String BANNER = "warm-flow.banner"; + + /** + * 是否开启逻辑删除 + */ + public static final String LOGIC_DELETE = "warm-flow.logic_delete"; + + /** + * 是否开启逻辑删除 + */ + public static final String KEY_TYPE = "warm-flow.key_type"; + + /** + * 逻辑删除字段值 + */ + public static final String LOGIC_DELETE_VALUE = "warm-flow.logic_delete_value"; + + /** + * 逻辑未删除字段 + */ + public static final String LOGIC_NOT_DELETE_VALUE = "warm-flow.logic_not_delete_value"; + + /** + * 数据填充处理类路径 + */ + public static final String DATA_FILL_HANDLE_PATH = "warm-flow.data-fill-handler-path"; + + /** + * 租户处理类路径 + */ + public static final String TENANT_HANDLER_PATH = "warm-flow.tenant_handler_path"; + /** + * 数据源类型, mybatis模块对orm进一步的封装, 由于各数据库分页语句存在差异, + * 当配置此参数时, 以此参数结果为基准, 未配置时, 取DataSource中数据源类型, + * 兜底为mysql数据库 + */ + public static final String DATA_SOURCE_TYPE = "warm-flow.data_source_type"; + + /** + * 是否支持ui + */ + public static final String UI = "warm-flow.ui"; + + /** + * 如果需要工作流共享业务系统权限 + */ + public static final String TOKEN_NAME = "warm-flow.token-name"; +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/constant/FlowCons.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/constant/FlowCons.java new file mode 100644 index 0000000..93c86eb --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/constant/FlowCons.java @@ -0,0 +1,82 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.constant; + +import java.util.regex.Pattern; + +/** + * warm-flow常量 + * + * @author warm + */ +public class FlowCons { + + /** + * 分隔符 + */ + public static final String SPLIT_AT = "@@"; + public static final String SPLIT_VERTICAL = "\\|"; + public static final String DEFAULT = "default"; + public static final String SPEL = "spel"; + + public static final Pattern LISTENER_PATTERN = Pattern.compile("^([^()]*)(.*)$"); + + /** + * 权限标识中的发起人标识符,办理过程中进行替换 + */ + public static final String WARMFLOWINITIATOR = "warmFlowInitiator"; + + /** + * 监听器参数 + */ + public static final String WARM_LISTENER_PARAM = "WarmListenerParam"; + + /** + * 雪花id 14位 + */ + public static final String SNOWID14 = "SnowId14"; + + /** + * 雪花id 15位 + */ + public static final String SNOWID15 = "SnowId15"; + + /** + * 雪花id 14位 + */ + public static final String SNOWID19 = "SnowId19"; + + + /** + * 表单自定义状态 + * 内置表单 + */ + public static final String FORM_CUSTOM_Y = "Y"; + /** + * 外挂表单路径 + */ + public static final String FORM_CUSTOM_N = "N"; + + /** + * 表单数据 + */ + public static final String FORM_DATA = "formData"; + + + public static final String PREVIOUS = "previous"; + + public static final String SUFFIX = "suffix"; +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/dto/ApiResult.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/dto/ApiResult.java new file mode 100644 index 0000000..e13f61d --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/dto/ApiResult.java @@ -0,0 +1,96 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.dto; + +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; + +/** + * 响应信息主体 + * + * @author ruoyi + */ +@Setter +@Getter +public class ApiResult implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 成功 + */ + public static final int SUCCESS = 200; + + /** + * 失败 + */ + public static final int FAIL = 500; + + private int code; + + private String msg; + + private T data; + + public static ApiResult ok() { + return restResult(null, SUCCESS, "操作成功"); + } + + public static ApiResult ok(T data) { + return restResult(data, SUCCESS, "操作成功"); + } + + public static ApiResult ok(T data, String msg) { + return restResult(data, SUCCESS, msg); + } + + public static ApiResult fail() { + return restResult(null, FAIL, "操作失败"); + } + + public static ApiResult fail(String msg) { + return restResult(null, FAIL, msg); + } + + public static ApiResult fail(T data) { + return restResult(data, FAIL, "操作失败"); + } + + public static ApiResult fail(T data, String msg) { + return restResult(data, FAIL, msg); + } + + public static ApiResult fail(int code, String msg) { + return restResult(null, code, msg); + } + + private static ApiResult restResult(T data, int code, String msg) { + ApiResult apiResult = new ApiResult<>(); + apiResult.setCode(code); + apiResult.setData(data); + apiResult.setMsg(msg); + return apiResult; + } + + public static Boolean isError(ApiResult ret) { + return !isSuccess(ret); + } + + public static Boolean isSuccess(ApiResult ret) { + return ApiResult.SUCCESS == ret.getCode(); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/dto/DefChart.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/dto/DefChart.java new file mode 100644 index 0000000..eef0a2d --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/dto/DefChart.java @@ -0,0 +1,50 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.dto; + + +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; + + +/** + * 流程图所需数据集合 + * + * @author warm + * @since 2023/3/30 14:27 + */ +@Getter +@Setter +public class DefChart { + /** + * 流程图所需的流程定义 + */ + private DefJson defJson = new DefJson(); + + /** + * 流程图所需的流程节点 + */ + private List nodeJsonList = new ArrayList<>(); + + /** + * 流程图所需的流程跳转 + */ + private List skipJsonList = new ArrayList<>(); + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/dto/DefJson.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/dto/DefJson.java new file mode 100644 index 0000000..afb57ad --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/dto/DefJson.java @@ -0,0 +1,291 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.dto; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; +import org.dromara.warm.flow.core.FlowEngine; +import org.dromara.warm.flow.core.entity.Definition; +import org.dromara.warm.flow.core.entity.Instance; +import org.dromara.warm.flow.core.entity.Node; +import org.dromara.warm.flow.core.entity.Skip; +import org.dromara.warm.flow.core.utils.CollUtil; +import org.dromara.warm.flow.core.utils.StringUtils; + +import java.math.BigDecimal; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 流程定义json对象 + * + * @author warm + * @since 2023-03-29 + */ +@Setter +@Getter +@Accessors(chain = true) +@ToString +public class DefJson { + + /** + * 主键 + */ + private Long id; + + /** + * 流程编码 + */ + private String flowCode; + + /** + * 流程名称 + */ + private String flowName; + + /** + * 设计器模型(CLASSICS经典模型 MIMIC仿钉钉模型) + */ + private String modelValue; + + /** + * 流程类别 + */ + private String category; + + /** + * 流程版本 + */ + private String version; + + /** + * 是否发布(0未开启 1开启) + */ + private Integer isPublish; + + /** + * 审批表单是否自定义(Y=是 N=否) + */ + private String formCustom; + + /** + * 审批表单是否自定义(Y=是 N=否) + */ + private String formPath; + + /** + * 监听器类型 + */ + private String listenerType; + + /** + * 监听器路径 + */ + private String listenerPath; + + /** + * 实例对象 + */ + private Instance instance; + + /** + * 扩展字段,预留给业务系统使用 + */ + private String ext; + + /** + * 扩展map,保存业务自定义扩展属性 + */ + private Map extMap; + + /** + * 所有节点结合 + */ + private List nodeList = new ArrayList<>(); + + /** + * 流程状态对应的三原色 + */ + private List chartStatusColor; + + /** + * 顶部信息: 比如流程名称等 + */ + private String topText; + + /** + * 顶部信息: 流程名称是否显示 + */ + private boolean topTextShow; + + private String createBy; + + private String updateBy; + + /** + * 流程类别 + */ + private List categoryList; + + /** + * 自定义表单的唯一标识:如formCode+version + */ + private List formPathList; + + + public String getModelValue() { + if (StringUtils.isEmpty(modelValue)) { + modelValue = "CLASSICS"; + } + return modelValue; + } + + public static DefJson copyDef(Definition definition) { + DefJson defJson = new DefJson() + .setFlowCode(definition.getFlowCode()) + .setFlowName(definition.getFlowName()) + .setModelValue(definition.getModelValue()) + .setVersion(definition.getVersion()) + .setIsPublish(definition.getIsPublish()) + .setCategory(definition.getCategory()) + .setFormCustom(definition.getFormCustom()) + .setFormPath(definition.getFormPath()) + .setListenerType(definition.getListenerType()) + .setListenerPath(definition.getListenerPath()) + .setExt(definition.getExt()) + .setCreateBy(definition.getCreateBy()) + .setUpdateBy(definition.getUpdateBy()); + + List nodeList = new ArrayList<>(); + defJson.setNodeList(nodeList); + for (Node node : definition.getNodeList()) { + // 向节点中添加子节点 + NodeJson nodeJson = new NodeJson() + .setNodeType(node.getNodeType()) + .setNodeCode(node.getNodeCode()) + .setNodeName(node.getNodeName()) + .setPermissionFlag(node.getPermissionFlag()) + .setNodeRatio(node.getNodeRatio()) + .setCoordinate(node.getCoordinate()) + .setAnyNodeSkip(node.getAnyNodeSkip()) + .setListenerType(node.getListenerType()) + .setListenerPath(node.getListenerPath()) + .setFormCustom(node.getFormCustom()) + .setFormPath(node.getFormPath()) + .setExt(node.getExt()) + .setCreateBy(node.getCreateBy()) + .setUpdateBy(node.getUpdateBy()); + nodeList.add(nodeJson); + + List skipList = new ArrayList<>(); + nodeJson.setSkipList(skipList); + if (CollUtil.isNotEmpty(node.getSkipList())) { + for (Skip skip : node.getSkipList()) { + skipList.add(new SkipJson() + .setCoordinate(skip.getCoordinate()) + .setSkipType(skip.getSkipType()) + .setSkipName(skip.getSkipName()) + .setSkipCondition(skip.getSkipCondition()) + .setNowNodeCode(skip.getNowNodeCode()) + .setNextNodeCode(skip.getNextNodeCode()) + .setCreateBy(skip.getCreateBy()) + .setUpdateBy(skip.getUpdateBy())); + } + } + + } + return defJson; + } + + public static Definition copyDef(DefJson defJson) { + Definition definition = FlowEngine.newDef() + .setId(defJson.getId()) + .setFlowCode(defJson.getFlowCode()) + .setFlowName(defJson.getFlowName()) + .setModelValue(defJson.getModelValue()) + .setVersion(defJson.getVersion()) + .setCategory(defJson.getCategory()) + .setFormCustom(defJson.getFormCustom()) + .setFormPath(defJson.getFormPath()) + .setListenerType(defJson.getListenerType()) + .setListenerPath(defJson.getListenerPath()) + .setExt(defJson.getExt()) + .setCreateBy(defJson.getCreateBy()) + .setUpdateBy(defJson.getUpdateBy()); + + List nodeList = new ArrayList<>(); + definition.setNodeList(nodeList); + for (NodeJson nodeJson : defJson.getNodeList()) { + // 向节点中添加子节点 + Node node = FlowEngine.newNode() + .setNodeType(nodeJson.getNodeType()) + .setNodeCode(nodeJson.getNodeCode()) + .setNodeName(nodeJson.getNodeName()) + .setPermissionFlag(nodeJson.getPermissionFlag()) + .setNodeRatio(nodeJson.getNodeRatio() != null ? nodeJson.getNodeRatio() : "0") + .setCoordinate(nodeJson.getCoordinate()) + .setAnyNodeSkip(nodeJson.getAnyNodeSkip()) + .setListenerType(nodeJson.getListenerType()) + .setListenerPath(nodeJson.getListenerPath()) + .setFormCustom(nodeJson.getFormCustom()) + .setFormPath(nodeJson.getFormPath()) + .setExt(nodeJson.getExt()) + .setCreateBy(nodeJson.getCreateBy()) + .setUpdateBy(nodeJson.getUpdateBy()); + nodeList.add(node); + + List skipList = new ArrayList<>(); + node.setSkipList(skipList); + + if (CollUtil.isNotEmpty(nodeJson.getSkipList())) { + for (SkipJson skipJson : nodeJson.getSkipList()) { + skipList.add(FlowEngine.newSkip() + .setCoordinate(skipJson.getCoordinate()) + .setSkipType(skipJson.getSkipType()) + .setSkipName(skipJson.getSkipName()) + .setSkipCondition(skipJson.getSkipCondition()) + .setNowNodeCode(skipJson.getNowNodeCode()) + .setNextNodeCode(skipJson.getNextNodeCode()) + .setCreateBy(skipJson.getCreateBy()) + .setUpdateBy(skipJson.getUpdateBy())); + } + } + + } + return definition; + } + + public static FlowCombine copyCombine(DefJson defJson) { + Definition definition = copyDef(defJson); + FlowCombine flowCombine = new FlowCombine(); + flowCombine.setDefinition(definition); + flowCombine.setAllNodes(definition.getNodeList()); + List skipList = Optional.of(definition) + .map(Definition::getNodeList) + .orElse(Collections.emptyList()) + .stream() + .map(Node::getSkipList) + .filter(Objects::nonNull) + .flatMap(List::stream) + .collect(Collectors.toList()); + + flowCombine.setAllSkips(skipList); + return flowCombine; + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/dto/FlowCombine.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/dto/FlowCombine.java new file mode 100644 index 0000000..2b85359 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/dto/FlowCombine.java @@ -0,0 +1,58 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.dto; + + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.dromara.warm.flow.core.FlowEngine; +import org.dromara.warm.flow.core.entity.Definition; +import org.dromara.warm.flow.core.entity.Node; +import org.dromara.warm.flow.core.entity.Skip; + +import java.util.ArrayList; +import java.util.List; + + +/** + * 流程数据集合 + * + * @author warm + * @since 2023/3/30 14:27 + */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class FlowCombine { + /** + * 所有的流程定义 + */ + private Definition definition = FlowEngine.newDef(); + + /** + * 所有的流程节点 + */ + private List allNodes = new ArrayList<>(); + + /** + * 所有的流程节点跳转关联 + */ + private List allSkips = new ArrayList<>(); + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/dto/FlowDto.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/dto/FlowDto.java new file mode 100644 index 0000000..402675d --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/dto/FlowDto.java @@ -0,0 +1,53 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.dto; + + +import lombok.Getter; +import lombok.Setter; +import org.dromara.warm.flow.core.entity.Form; + +import java.io.Serializable; + +/** + * @author vanlin + * @since 2024-9-24 11:11 + */ +@Getter +@Setter +public class FlowDto implements Serializable { + + /** + * ID + */ + private Long id; + + /** + * 表单内容 + */ + private String formContent; + + /** + * 表单数据 + */ + private Form form; + + /** + * 数据 + */ + private Object data; + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/dto/FlowPage.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/dto/FlowPage.java new file mode 100644 index 0000000..a9e5927 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/dto/FlowPage.java @@ -0,0 +1,70 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.dto; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.util.List; + +/** + * 表格分页数据对象 + * + * @author ruoyi + */ +@Getter +@Setter +@NoArgsConstructor +@Accessors(chain = true) +public class FlowPage implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 总记录数 + */ + private long total; + + /** + * 列表数据 + */ + private List rows; + + /** + * 消息状态码 + */ + private int code; + + /** + * 消息内容 + */ + private String msg; + + /** + * 分页 + * + * @param list 列表数据 + * @param total 总记录数 + */ + public FlowPage(List list, int total) { + this.rows = list; + this.total = total; + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/dto/FlowParams.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/dto/FlowParams.java new file mode 100644 index 0000000..a06d37c --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/dto/FlowParams.java @@ -0,0 +1,339 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.dto; + +import lombok.Getter; +import org.dromara.warm.flow.core.FlowEngine; +import org.dromara.warm.flow.core.handler.PermissionHandler; +import org.dromara.warm.flow.core.utils.CollUtil; +import org.dromara.warm.flow.core.utils.StringUtils; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 工作流内置参数 + * + * @author warm + * @since 2023/3/31 17:18 + */ +public class FlowParams implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 流程编码 + */ + @Getter + private String flowCode; + + /** + * 当前办理人唯一标识:就是确定唯一用的,如用户id,通常用来入库,记录流程实例创建人,办理人 + */ + private String handler; + + private List handlerList; + + /** + * 节点编码(如果要指定跳转节点,传入) + */ + @Getter + private String nodeCode; + + /** + * 用户权限标识:和办理权限有关,是否有办理权限,通俗来说,就是设计器里面预设的办理人,和这个标识是否有交集,有交集就可以办理,审批的时候,就不会提示报错 + */ + private List permissionFlag; + + /** + * 跳转类型(PASS审批通过 REJECT退回) + */ + @Getter + private String skipType; + + /** + * 审批意见 + */ + @Getter + private String message; + + /** + * 流程变量 + */ + @Getter + private Map variable; + + /** + * 流程实例状态 + */ + @Getter + private String flowStatus; + + /** + * 历史任务表状态 + */ + @Getter + private String hisStatus; + + /** + * 流程激活状态(0挂起 1激活) + */ + @Getter + private Integer activityStatus; + + /** + * 协作方式(1审批 2转办 3委派 4会签 5票签 6加签 7减签) + */ + @Getter + private Integer cooperateType; + + /** + * 扩展字段,预留给业务系统使用 + */ + @Getter + private String ext; + + /** + * 扩展字段,预留给业务系统使用 + */ + @Getter + private String hisTaskExt; + + /** + * 增加办理人:加签,转办,委托 + */ + @Getter + private List addHandlers; + + /** + * 减少办理人:减签,委托 + */ + @Getter + private List reductionHandlers; + + /** + * 忽略-办理权限校验(true:忽略,false:不忽略) + */ + @Getter + private boolean ignore; + + /** + * 忽略-委派处理(true:忽略,false:不忽略) + */ + @Getter + private boolean ignoreDepute; + + /** + * 忽略-会签票签处理(true:忽略,false:不忽略) + */ + @Getter + private boolean ignoreCooperate; + + /** + * 执行的下个任务的办理人 + */ + @Getter + private String[] nextHandler; + + /** + * 下个任务处理人配置类型(true-追加,false-覆盖,默认false) + */ + @Getter + private boolean nextHandlerAppend; + + public FlowParams() { + } + + public FlowParams(String skipType, String message, Map variable) { + this.skipType = skipType; + this.message = message; + this.variable = variable; + } + + public FlowParams(String nodeCode, String skipType, String message, Map variable) { + this.nodeCode = nodeCode; + this.skipType = skipType; + this.message = message; + this.variable = variable; + } + + public FlowParams(String skipType, String message, Map variable + , String flowStatus, String hisStatus) { + this.skipType = skipType; + this.message = message; + this.variable = variable; + this.flowStatus = flowStatus; + this.hisStatus = hisStatus; + } + + public FlowParams(String nodeCode, String skipType, String message, Map variable + , String flowStatus, String hisStatus) { + this.nodeCode = nodeCode; + this.skipType = skipType; + this.message = message; + this.variable = variable; + this.flowStatus = flowStatus; + this.hisStatus = hisStatus; + } + + public static FlowParams build() { + return new FlowParams(); + } + + public FlowParams flowCode(String flowCode) { + this.flowCode = flowCode; + return this; + } + + public FlowParams handler(String handler) { + this.handler = handler; + return this; + } + + public FlowParams nodeCode(String nodeCode) { + this.nodeCode = nodeCode; + return this; + } + + public FlowParams permissionFlag(List permissionFlag) { + this.permissionFlag = permissionFlag; + return this; + } + + public FlowParams message(String message) { + this.message = message; + return this; + } + + public FlowParams variable(Map variable) { + this.variable = variable; + return this; + } + + public FlowParams flowStatus(String flowStatus) { + this.flowStatus = flowStatus; + return this; + } + + public FlowParams hisStatus(String hisStatus) { + this.hisStatus = hisStatus; + return this; + } + + public FlowParams activityStatus(Integer activityStatus) { + this.activityStatus = activityStatus; + return this; + } + + public FlowParams cooperateType(Integer cooperateType) { + this.cooperateType = cooperateType; + return this; + } + + public FlowParams ext(String ext) { + this.ext = ext; + return this; + } + + public FlowParams hisTaskExt(String hisTaskExt) { + this.hisTaskExt = hisTaskExt; + return this; + } + + public FlowParams nextHandler(String... nextHandler) { + this.nextHandler = nextHandler; + return this; + } + + public String getVariableStr() { + return FlowEngine.jsonConvert.objToStr(variable); + } + + public String getHandler() { + if (StringUtils.isEmpty(handler)) { + PermissionHandler permissionHandler = FlowEngine.permissionHandler(); + if (permissionHandler != null) { + handler = permissionHandler.getHandler(); + } + } + return handler; + } + + public List getHandlerList() { + if (null == handlerList) { + PermissionHandler permissionHandler = FlowEngine.permissionHandler(); + if (permissionHandler != null) { + handlerList = permissionHandler.getHandlerList(); + } + } + return handlerList; + } + + public List getPermissionFlag() { + if (CollUtil.isEmpty(permissionFlag)) { + PermissionHandler permissionHandler = FlowEngine.permissionHandler(); + if (permissionHandler != null) { + permissionFlag = permissionHandler.permissions(); + } + } + return permissionFlag; + } + + public FlowParams skipType(String skipType) { + this.skipType = skipType; + return this; + } + + public FlowParams addHandlers(List addHandlers) { + this.addHandlers = addHandlers; + return this; + } + + public FlowParams reductionHandlers(List reductionHandlers) { + this.reductionHandlers = reductionHandlers; + return this; + } + + public FlowParams ignore(boolean ignore) { + this.ignore = ignore; + return this; + } + + public FlowParams ignoreDepute(boolean ignoreDepute) { + this.ignoreDepute = ignoreDepute; + return this; + } + + public FlowParams ignoreCooperate(boolean ignoreCooperate) { + this.ignoreCooperate = ignoreCooperate; + return this; + } + + public FlowParams nextHandlerAppend(boolean nextHandlerAppend) { + this.nextHandlerAppend = nextHandlerAppend; + return this; + } + + public FlowParams formData(Map formData) { + if (this.variable == null) { + this.variable = new HashMap<>(); + } + this.variable.put("formData", formData); + return this; + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/dto/NodeJson.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/dto/NodeJson.java new file mode 100644 index 0000000..dd47758 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/dto/NodeJson.java @@ -0,0 +1,125 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.dto; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.dromara.warm.flow.core.utils.MapUtil; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 流程节点对象Vo + * + * @author warm + * @since 2023-03-29 + */ +@Setter +@Getter +@Accessors(chain = true) +public class NodeJson { + + /** + * 节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关) + */ + private Integer nodeType; + /** + * 流程节点编码 每个流程的nodeCode是唯一的,即definitionId+nodeCode唯一,在数据库层面做了控制 + */ + private String nodeCode; + /** + * 流程节点名称 + */ + private String nodeName; + /** + * 流程节点版本 + */ + private String version; + /** + * 权限标识(权限类型:权限标识,可以多个,用@@隔开) + */ + private String permissionFlag; + /** + * 流程签署比例值 + */ + private String nodeRatio; + /** + * 流程节点坐标 + */ + private String coordinate; + /** + * 任意结点跳转 + */ + private String anyNodeSkip; + /** + * 监听器类型 + */ + private String listenerType; + /** + * 监听器路径 + */ + private String listenerPath; + /** + * 审批表单是否自定义(Y=是 N=否) + */ + private String formCustom; + + /** + * 审批表单是否自定义(Y=是 N=否) + */ + private String formPath; + + /** + * 节点扩展属性 + */ + private String ext; + + /** + * 办理状态: 0未办理 1待办理 2已办理 + */ + private Integer status; + + /** + * 扩展map,保存业务自定义扩展属性 + */ + private Map extMap; + + /** + * 流程图节点提示内容 + */ + private PromptContent promptContent; + + /** + * 跳转条件 + */ + private List skipList = new ArrayList<>(); + + private String createBy; + + private String updateBy; + + public Map getExtMap() { + if (MapUtil.isEmpty(extMap)) { + extMap = new HashMap<>(); + } + return extMap; + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/dto/PathWayData.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/dto/PathWayData.java new file mode 100644 index 0000000..a258442 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/dto/PathWayData.java @@ -0,0 +1,69 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.dto; + + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.dromara.warm.flow.core.entity.Node; +import org.dromara.warm.flow.core.entity.Skip; + +import java.util.ArrayList; +import java.util.List; + + +/** + * 办理过程中途径数据,用于渲染流程图 + * + * @author warm + * @since 2025/1/4 + */ +@Getter +@Setter +@Accessors(chain = true) +public class PathWayData { + /** + * 流程定义id + */ + private Long defId; + + /** + * 流程实例id + */ + private Long insId; + + /** + * 跳转类型(PASS审批通过 REJECT退回) + */ + private String skipType; + + /** + * 目标结点集合 + */ + private List targetNodes = new ArrayList<>(); + + /** + * 途径结点集合 + */ + private List pathWayNodes = new ArrayList<>(); + + /** + * 途径流程跳转线 + */ + private List pathWaySkips = new ArrayList<>(); + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/dto/PromptContent.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/dto/PromptContent.java new file mode 100644 index 0000000..1a5f388 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/dto/PromptContent.java @@ -0,0 +1,80 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.Accessors; + +import java.util.List; +import java.util.Map; + +/** + * 提示信息 + * + * @author warm + * @since 2025/6/5 + */ +@Getter +@Setter +@Accessors(chain = true) +@AllArgsConstructor +@NoArgsConstructor +public class PromptContent { + /** + * 弹窗样式 + */ + private Map dialogStyle; + /** + * 提示信息 + */ + private List info; + + /** + * 提示信息项 + */ + @Getter + @Setter + @Accessors(chain = true) + @AllArgsConstructor + @NoArgsConstructor + public static class InfoItem { + /** + * 前缀 + */ + private String prefix; + /** + * 前缀样式 + */ + private Map prefixStyle; + /** + * 内容 + */ + private String content; + /** + * 内容样式 + */ + private Map contentStyle; + /** + * 行样式 + */ + private Map rowStyle; + + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/dto/SkipJson.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/dto/SkipJson.java new file mode 100644 index 0000000..e63b206 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/dto/SkipJson.java @@ -0,0 +1,85 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.dto; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +import java.util.List; +import java.util.Map; + +/** + * 节点跳转关联对象Vo + * + * @author warm + * @since 2023-03-29 + */ +@Setter +@Getter +@Accessors(chain = true) +public class SkipJson { + + /** + * 当前流程节点的编码 + */ + private String nowNodeCode; + + /** + * 下一个流程节点的编码 + */ + private String nextNodeCode; + + /** + * 跳转名称 + */ + private String skipName; + + /** + * 跳转类型(PASS审批通过 REJECT退回) + */ + private String skipType; + + /** + * 跳转条件 + */ + private String skipCondition; + + /** + * 流程跳转坐标 + */ + private String coordinate; + + /** + * 办理状态: 0未办理 1待办理 2已办理 + */ + private Integer status; + + /** + * 扩展map,保存业务自定义扩展属性 + */ + private Map extMap; + + /** + * 流程图节点提示内容 + */ + private List promptContent; + + private String createBy; + + private String updateBy; + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/dto/Tree.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/dto/Tree.java new file mode 100644 index 0000000..1a9957e --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/dto/Tree.java @@ -0,0 +1,62 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * 页面树列表 + * + * @author ruoyi + */ +@Getter +@Setter +@Accessors(chain = true) +@AllArgsConstructor +@NoArgsConstructor +public class Tree implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * ID + */ + private String id; + + /** + * 名称 + */ + private String name; + + /** + * 父ID + */ + private String parentId; + + /** + * 子 + */ + private List children = new ArrayList<>(); + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/entity/Definition.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/entity/Definition.java new file mode 100644 index 0000000..879c45b --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/entity/Definition.java @@ -0,0 +1,195 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.entity; + +import org.dromara.warm.flow.core.FlowEngine; + +import java.util.Date; +import java.util.List; + +/** + * 流程定义对象 flow_definition + * + * @author warm + * @since 2023-03-29 + */ +public interface Definition extends RootEntity { + + @Override + Long getId(); + + @Override + Definition setId(Long id); + + @Override + Date getCreateTime(); + + @Override + Definition setCreateTime(Date createTime); + + @Override + Date getUpdateTime(); + + @Override + Definition setUpdateTime(Date updateTime); + + @Override + String getCreateBy(); + + @Override + Definition setCreateBy(String createBy); + + @Override + String getUpdateBy(); + + @Override + Definition setUpdateBy(String updateBy); + + @Override + String getTenantId(); + + @Override + Definition setTenantId(String tenantId); + + @Override + String getDelFlag(); + + @Override + Definition setDelFlag(String delFlag); + + /** + * 获取流程编码 + * @return 流程编码 + */ + String getFlowCode(); + + /** + * 设置流程编码 + * @param flowCode flowCode + * @return Definition + */ + Definition setFlowCode(String flowCode); + + /** + * 获取流程名称 + * @return 流程名称 + */ + String getFlowName(); + + /** + * 设置流程名称 + * @param flowName flowName + * @return Definition + */ + Definition setFlowName(String flowName); + + /** + * 设计器模型(CLASSICS经典模型 MIMIC仿钉钉模型) + * @see org.dromara.warm.flow.core.enums.ModelEnum + * @return 设计器模型 + */ + String getModelValue(); + + Definition setModelValue(String modelValue); + + String getCategory(); + + Definition setCategory(String category); + + /** + * 获取流程定义的版本号 + * @return 版本号 + */ + String getVersion(); + + Definition setVersion(String version); + + /** + * 获取是否发布状态 (0未发布 1已发布 9已失效) + * @return 发布状态 + */ + Integer getIsPublish(); + + Definition setIsPublish(Integer isPublish); + + /** + * 审批表单是否自定义(Y=是 N=否) + * @return 是否自定义 + */ + String getFormCustom(); + + Definition setFormCustom(String formCustom); + + String getFormPath(); + + Definition setFormPath(String formPath); + + String getExt(); + + Definition setExt(String ext); + + List getNodeList(); + + Definition setNodeList(List nodeList); + + List getUserList(); + + Definition setUserList(List userList); + + /** + * 流程激活状态(0=挂起 1=激活) + * @see org.dromara.warm.flow.core.enums.ActivityStatus + * @return 流程激活状态 + */ + Integer getActivityStatus(); + + Definition setActivityStatus(Integer activityStatus); + + /** + * 获取监听器类型 + * @return 监听器类型 + */ + String getListenerType(); + + Definition setListenerType(String listenerType); + + /** + * 获取监听器路径 + * @return 监听器路径 + */ + String getListenerPath(); + + Definition setListenerPath(String listenerPath); + + default Definition copy() { + return FlowEngine.newDef() + .setTenantId(this.getTenantId()) + .setDelFlag(this.getDelFlag()) + .setFlowCode(this.getFlowCode()) + .setFlowName(this.getFlowName()) + .setModelValue(this.getModelValue()) + .setCategory(this.getCategory()) + .setVersion(this.getVersion()) + .setFormCustom(this.getFormCustom()) + .setFormPath(this.getFormPath()) + .setListenerType(this.getListenerType()) + .setListenerPath(this.getListenerPath()) + .setExt(this.getExt()) + .setCreateBy(this.getCreateBy()) + .setUpdateBy(this.getUpdateBy()); + + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/entity/Form.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/entity/Form.java new file mode 100644 index 0000000..216b279 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/entity/Form.java @@ -0,0 +1,111 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.entity; + +import java.util.Date; + +/** + * 流程表单 flow_form + * + * @author vanlin + * @since 2024/8/19 9:59 + */ +public interface Form extends RootEntity { + + @Override + Long getId(); + + @Override + Form setId(Long id); + + @Override + Date getCreateTime(); + + @Override + Form setCreateTime(Date createTime); + + @Override + Date getUpdateTime(); + + @Override + Form setUpdateTime(Date updateTime); + + @Override + String getCreateBy(); + + @Override + Form setCreateBy(String createBy); + + @Override + String getUpdateBy(); + + @Override + Form setUpdateBy(String updateBy); + + @Override + String getTenantId(); + + @Override + Form setTenantId(String tenantId); + + @Override + String getDelFlag(); + + @Override + Form setDelFlag(String delFlag); + + /** + * 获取表单编码 + * @return 表单编码 + */ + String getFormCode(); + + Form setFormCode(String formCode); + + String getFormName(); + + Form setFormName(String formName); + + String getVersion(); + + Form setVersion(String version); + + /** + * 是否发布(0未发布 1已发布 9失效) + */ + Integer getIsPublish(); + + Form setIsPublish(Integer isPublish); + + /** + * 表单类型(0内置表单 存 form_content 1外挂表单 存form_path) + */ + Integer getFormType(); + + Form setFormType(Integer formType); + + String getFormContent(); + + Form setFormContent(String formContent); + + String getFormPath(); + + Form setFormPath(String formPath); + + String getExt(); + + Form setExt(String ext); +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/entity/HisTask.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/entity/HisTask.java new file mode 100644 index 0000000..cb3dd10 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/entity/HisTask.java @@ -0,0 +1,163 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.entity; + +import org.dromara.warm.flow.core.FlowEngine; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * 历史任务记录对象 flow_his_task + * + * @author warm + * @since 2023-03-29 + */ +public interface HisTask extends RootEntity { + + @Override + Long getId(); + + @Override + HisTask setId(Long id); + + @Override + Date getCreateTime(); + + @Override + HisTask setCreateTime(Date createTime); + + @Override + Date getUpdateTime(); + + @Override + HisTask setUpdateTime(Date updateTime); + + @Override + String getTenantId(); + + @Override + HisTask setTenantId(String tenantId); + + @Override + String getDelFlag(); + + @Override + HisTask setDelFlag(String delFlag); + + /** + * 获取流程定义ID + * @return 流程定义ID + */ + Long getDefinitionId(); + + HisTask setDefinitionId(Long definitionId); + + String getFlowName(); + + HisTask setFlowName(String flowName); + + /** + * 获取流程实例ID + * @return 流程实例ID + */ + Long getInstanceId(); + + HisTask setInstanceId(Long instanceId); + + Integer getCooperateType(); + + HisTask setCooperateType(Integer cooperateType); + + Long getTaskId(); + + HisTask setTaskId(Long taskId); + + String getBusinessId(); + + HisTask setBusinessId(String businessId); + + String getNodeCode(); + + HisTask setNodeCode(String nodeCode); + + String getNodeName(); + + HisTask setNodeName(String nodeName); + + Integer getNodeType(); + + HisTask setNodeType(Integer nodeType); + + String getTargetNodeCode(); + + HisTask setTargetNodeCode(String targetNodeCode); + + String getTargetNodeName(); + + HisTask setTargetNodeName(String targetNodeName); + + String getApprover(); + + HisTask setApprover(String approver); + + String getCollaborator(); + + HisTask setCollaborator(String collaborator); + + List getPermissionList(); + + HisTask setPermissionList(List permissionList); + + String getSkipType(); + + HisTask setSkipType(String skipType); + + /** + * 获取流程状态 + * @see org.dromara.warm.flow.core.enums.FlowStatus + * @return 流程状态 + */ + String getFlowStatus(); + + HisTask setFlowStatus(String flowStatus); + + String getMessage(); + + HisTask setMessage(String message); + + String getVariable(); + + HisTask setVariable(String variable); + + default Map getVariableMap() { + return FlowEngine.jsonConvert.strToMap(this.getVariable()); + } + + String getExt(); + + HisTask setExt(String ext); + + String getFormCustom(); + + HisTask setFormCustom(String formCustom); + + String getFormPath(); + + HisTask setFormPath(String formPath); + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/entity/Instance.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/entity/Instance.java new file mode 100644 index 0000000..9275e8a --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/entity/Instance.java @@ -0,0 +1,165 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.entity; + +import org.dromara.warm.flow.core.FlowEngine; + +import java.util.Date; +import java.util.Map; + +/** + * 流程实例对象 flow_instance + * + * @author warm + * @since 2023-03-29 + */ +public interface Instance extends RootEntity { + + @Override + Long getId(); + + @Override + Instance setId(Long id); + + @Override + Date getCreateTime(); + + @Override + Instance setCreateTime(Date createTime); + + @Override + Date getUpdateTime(); + + @Override + Instance setUpdateTime(Date updateTime); + + @Override + String getCreateBy(); + + @Override + Instance setCreateBy(String createBy); + + @Override + String getUpdateBy(); + + @Override + Instance setUpdateBy(String updateBy); + + @Override + String getTenantId(); + + @Override + Instance setTenantId(String tenantId); + + @Override + String getDelFlag(); + + @Override + Instance setDelFlag(String delFlag); + + /** + * flow_definition.id + * @return flow_definition.id + */ + Long getDefinitionId(); + + Instance setDefinitionId(Long definitionId); + + /** + * 流程名称 + * @return 流程名称 + */ + String getFlowName(); + + Instance setFlowName(String flowName); + + /** + * 业务ID + * @return 业务ID + */ + String getBusinessId(); + + Instance setBusinessId(String businessId); + + /** + * @see org.dromara.warm.flow.core.enums.NodeType + * @return 节点类型 + */ + Integer getNodeType(); + + Instance setNodeType(Integer nodeType); + + String getNodeCode(); + + Instance setNodeCode(String nodeCode); + + /** + * 流程节点名称 + * @return 节点名称 + */ + String getNodeName(); + + Instance setNodeName(String nodeName); + + /** + * 流程变量 + * @return 流程变量 + */ + String getVariable(); + + Instance setVariable(String variable); + + default Map getVariableMap() { + return FlowEngine.jsonConvert.strToMap(getVariable()); + } + + /** + * @see org.dromara.warm.flow.core.enums.FlowStatus + * @return 流程状态 + */ + String getFlowStatus(); + + Instance setFlowStatus(String flowStatus); + + /** + * 审批表单是否自定义(Y是 N否) + * @return (Y是 N否) + */ + String getFormCustom(); + + Instance setFormCustom(String formCustom); + + String getFormPath(); + + Instance setFormPath(String formPath); + + String getDefJson(); + + Instance setDefJson(String defJson); + + String getExt(); + + Instance setExt(String ext); + + /** + * @see org.dromara.warm.flow.core.enums.ActivityStatus + * @return 激活状态 + */ + Integer getActivityStatus(); + + Instance setActivityStatus(Integer activityStatus); + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/entity/Node.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/entity/Node.java new file mode 100644 index 0000000..b542dbd --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/entity/Node.java @@ -0,0 +1,161 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.entity; + +import org.dromara.warm.flow.core.FlowEngine; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; + +/** + * 流程节点对象 flow_node + * + * @author warm + * @since 2023-03-29 + */ +public interface Node extends RootEntity { + + @Override + Long getId(); + + @Override + Node setId(Long id); + + @Override + Date getCreateTime(); + + @Override + Node setCreateTime(Date createTime); + + @Override + Date getUpdateTime(); + + @Override + Node setUpdateTime(Date updateTime); + + @Override + String getCreateBy(); + + @Override + Node setCreateBy(String createBy); + + @Override + String getUpdateBy(); + + @Override + Node setUpdateBy(String updateBy); + + @Override + String getTenantId(); + + @Override + Node setTenantId(String tenantId); + + @Override + String getDelFlag(); + + @Override + Node setDelFlag(String delFlag); + + Integer getNodeType(); + + Node setNodeType(Integer nodeType); + + Long getDefinitionId(); + + Node setDefinitionId(Long definitionId); + + String getNodeCode(); + + Node setNodeCode(String nodeCode); + + String getNodeName(); + + Node setNodeName(String nodeName); + + String getNodeRatio(); + + Node setNodeRatio(String nodeRatio); + + String getPermissionFlag(); + + Node setPermissionFlag(String permissionFlag); + + String getCoordinate(); + + Node setCoordinate(String coordinate); + + String getAnyNodeSkip(); + + Node setAnyNodeSkip(String anyNodeSkip); + + String getListenerType(); + + Node setListenerType(String listenerType); + + String getListenerPath(); + + Node setListenerPath(String listenerPath); + + String getFormCustom(); + + Node setFormCustom(String formCustom); + + String getFormPath(); + + Node setFormPath(String formPath); + + String getExt(); + + Node setExt(String ext); + + /** + * @deprecated 下个版本废弃 + */ + @Deprecated + String getVersion(); + + /** + * @deprecated 下个版本废弃 + */ + @Deprecated + Node setVersion(String version); + + List getSkipList(); + + Node setSkipList(List skipList); + + default Node copy() { + return FlowEngine.newNode() + .setTenantId(this.getTenantId()) + .setDelFlag(this.getDelFlag()) + .setNodeType(this.getNodeType()) + .setDefinitionId(this.getDefinitionId()) + .setNodeCode(this.getNodeCode()) + .setNodeName(this.getNodeName()) + .setNodeRatio(this.getNodeRatio()) + .setPermissionFlag(this.getPermissionFlag()) + .setCoordinate(this.getCoordinate()) + .setVersion(this.getVersion()) + .setAnyNodeSkip(this.getAnyNodeSkip()) + .setListenerType(this.getListenerType()) + .setListenerPath(this.getListenerPath()) + .setFormCustom(this.getFormCustom()) + .setFormPath(this.getFormPath()) + .setExt(this.getExt()); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/entity/RootEntity.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/entity/RootEntity.java new file mode 100644 index 0000000..5076ee5 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/entity/RootEntity.java @@ -0,0 +1,65 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.entity; + +import java.io.Serializable; +import java.util.Date; + +/** + * 流程基础entity + * + * @author warm + * @since 2023/5/17 17:23 + */ +public interface RootEntity extends Serializable { + + Long getId(); + + RootEntity setId(Long id); + + Date getCreateTime(); + + RootEntity setCreateTime(Date createTime); + + Date getUpdateTime(); + + RootEntity setUpdateTime(Date updateTime); + + default String getCreateBy() { + return null; + } + + default RootEntity setCreateBy(String createBy) { + return this; + } + + default String getUpdateBy() { + return null; + } + + default RootEntity setUpdateBy(String updateBy) { + return this; + } + + String getTenantId(); + + RootEntity setTenantId(String tenantId); + + String getDelFlag(); + + RootEntity setDelFlag(String delFlag); + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/entity/Skip.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/entity/Skip.java new file mode 100644 index 0000000..2f520db --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/entity/Skip.java @@ -0,0 +1,127 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.entity; + +import org.dromara.warm.flow.core.FlowEngine; + +import java.util.Date; + +/** + * 节点跳转关联对象 flow_skip + * + * @author warm + * @since 2023-03-29 + */ +public interface Skip extends RootEntity { + + @Override + Long getId(); + + @Override + Skip setId(Long id); + + @Override + Date getCreateTime(); + + @Override + Skip setCreateTime(Date createTime); + + @Override + Date getUpdateTime(); + + @Override + Skip setUpdateTime(Date updateTime); + + @Override + String getCreateBy(); + + @Override + Skip setCreateBy(String createBy); + + @Override + String getUpdateBy(); + + @Override + Skip setUpdateBy(String updateBy); + + @Override + String getTenantId(); + + @Override + Skip setTenantId(String tenantId); + + @Override + String getDelFlag(); + + @Override + Skip setDelFlag(String delFlag); + + Long getDefinitionId(); + + Skip setDefinitionId(Long definitionId); + + Long getNodeId(); + + Skip setNodeId(Long nodeId); + + String getNowNodeCode(); + + Skip setNowNodeCode(String nowNodeCode); + + Integer getNowNodeType(); + + Skip setNowNodeType(Integer nowNodeType); + + String getNextNodeCode(); + + Skip setNextNodeCode(String nextNodeCode); + + Integer getNextNodeType(); + + Skip setNextNodeType(Integer nextNodeType); + + String getSkipName(); + + Skip setSkipName(String skipName); + + String getSkipType(); + + Skip setSkipType(String skipType); + + String getSkipCondition(); + + Skip setSkipCondition(String skipCondition); + + String getCoordinate(); + + Skip setCoordinate(String coordinate); + + default Skip copy() { + return FlowEngine.newSkip() + .setTenantId(getTenantId()) + .setDelFlag(getDelFlag()) + .setDefinitionId(getDefinitionId()) + .setNowNodeCode(getNowNodeCode()) + .setNowNodeType(getNowNodeType()) + .setNextNodeCode(getNextNodeCode()) + .setNextNodeType(getNextNodeType()) + .setSkipName(getSkipName()) + .setSkipType(getSkipType()) + .setSkipCondition(getSkipCondition()) + .setCoordinate(getCoordinate()); + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/entity/Task.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/entity/Task.java new file mode 100644 index 0000000..56bc519 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/entity/Task.java @@ -0,0 +1,135 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.entity; + +import java.util.Date; +import java.util.List; + +/** + * 待办任务记录对象 flow_task + * + * @author warm + * @since 2023-03-29 + */ +public interface Task extends RootEntity { + + @Override + Long getId(); + + @Override + Task setId(Long id); + + @Override + Date getCreateTime(); + + @Override + Task setCreateTime(Date createTime); + + @Override + Date getUpdateTime(); + + @Override + Task setUpdateTime(Date updateTime); + + @Override + String getCreateBy(); + + @Override + Task setCreateBy(String createBy); + + @Override + String getUpdateBy(); + + @Override + Task setUpdateBy(String updateBy); + + @Override + String getTenantId(); + + @Override + Task setTenantId(String tenantId); + + @Override + String getDelFlag(); + + @Override + Task setDelFlag(String delFlag); + + /** + * 获取流程定义ID + * @return 流程定义ID + */ + Long getDefinitionId(); + + Task setDefinitionId(Long definitionId); + + /** + * 获取流程实例ID + * @return 流程实例ID + */ + Long getInstanceId(); + + Task setInstanceId(Long instanceId); + + String getFlowName(); + + Task setFlowName(String flowName); + + /** + * 获取业务ID + * @return 业务ID + */ + String getBusinessId(); + + Task setBusinessId(String businessId); + + String getNodeCode(); + + Task setNodeCode(String nodeCode); + + String getNodeName(); + + Task setNodeName(String nodeName); + + Integer getNodeType(); + + Task setNodeType(Integer nodeType); + + /** + * 获取流程状态 + * @see org.dromara.warm.flow.core.enums.FlowStatus + * @return 流程状态 + */ + String getFlowStatus(); + + Task setFlowStatus(String flowStatus); + + List getPermissionList(); + + Task setPermissionList(List permissionList); + + List getUserList(); + + Task setUserList(List userList); + + String getFormCustom(); + + Task setFormCustom(String formCustom); + + String getFormPath(); + + Task setFormPath(String formPath); +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/entity/User.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/entity/User.java new file mode 100644 index 0000000..5159957 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/entity/User.java @@ -0,0 +1,94 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.entity; + +import java.util.Date; + +/** + * 流程用户 flow_user + * + * @author xiarg + * @since 2024/5/10 10:41 + */ +public interface User extends RootEntity { + + @Override + Long getId(); + + @Override + User setId(Long id); + + @Override + Date getCreateTime(); + + @Override + User setCreateTime(Date createTime); + + @Override + Date getUpdateTime(); + + @Override + User setUpdateTime(Date updateTime); + + @Override + String getCreateBy(); + + @Override + User setCreateBy(String createBy); + + @Override + String getUpdateBy(); + + @Override + User setUpdateBy(String updateBy); + + @Override + String getTenantId(); + + @Override + User setTenantId(String tenantId); + + @Override + String getDelFlag(); + + @Override + User setDelFlag(String delFlag); + + /** + * 获取人员类型 + * @see org.dromara.warm.flow.core.enums.UserType + * @return 人员类型(1待办任务的审批人权限 2待办任务的转办人权限 3待办任务的委托人权限) + */ + String getType(); + + User setType(String type); + + /** + * 获取 权限人 + * @return 权限人 + */ + String getProcessedBy(); + + User setProcessedBy(String processedBy); + + /** + * 获取 任务表ID + * @return 任务表ID + */ + Long getAssociated(); + + User setAssociated(Long associated); +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/enums/ActivityStatus.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/enums/ActivityStatus.java new file mode 100644 index 0000000..b149981 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/enums/ActivityStatus.java @@ -0,0 +1,55 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.dromara.warm.flow.core.utils.ObjectUtil; + +/** + * 激活状态 + * + * @author warm + * @since 2025/6/25 + */ +@Getter +@AllArgsConstructor +public enum ActivityStatus { + + /** + * 激活状态 + */ + SUSPENDED(0, "挂起"), + + ACTIVITY(1, "激活"); + + private final Integer key; + private final String value; + + /** + * 判断流程是否激活 + */ + public static Boolean isActivity(Integer key) { + return ObjectUtil.isNotNull(key) && (ActivityStatus.ACTIVITY.getKey().equals(key)); + } + + /** + * 判断流程是否挂起 + */ + public static Boolean isSuspended(Integer key) { + return ObjectUtil.isNotNull(key) && (ActivityStatus.SUSPENDED.getKey().equals(key)); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/enums/ChartStatus.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/enums/ChartStatus.java new file mode 100644 index 0000000..d9dc4a4 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/enums/ChartStatus.java @@ -0,0 +1,154 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.dromara.warm.flow.core.utils.CollUtil; +import org.dromara.warm.flow.core.utils.ObjectUtil; +import org.dromara.warm.flow.core.utils.StringUtils; + +import java.awt.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 流程图状态 + * + * @author warm + * @since 2023/3/31 12:16 + */ +@Getter +@AllArgsConstructor +public enum ChartStatus { + /** + * 流程图状态 + */ + NOT_DONE(0, "未办理", new Color(166, 178, 189)), + + TO_DO(1, "待办理", new Color(255, 197, 90)), + + DONE(2, "已办理", new Color(135, 206, 250)); + + private final Integer key; + private final String value; + private final Color color; + + private static final Map CUSTOM_COLOR = new HashMap<>(); + private static final Map CUSTOM_COLOR_CLASSICS = new HashMap<>(); + private static final Map CUSTOM_COLOR_MIMIC = new HashMap<>(); + + public static void initCustomColor(List chartStatusColor, List chartStatusColorClassics, + List chartStatusColorMimic) { + if (CollUtil.isNotEmpty(chartStatusColor) && chartStatusColor.size() == 3) { + for (int i = 0; i < chartStatusColor.size(); i++) { + String statusColor = chartStatusColor.get(i); + if (StringUtils.isNotEmpty(statusColor)) { + String[] colorArr = statusColor.split(","); + if (colorArr.length == 3) { + ChartStatus.CUSTOM_COLOR.put(i, new Color(Integer.parseInt(colorArr[0]), Integer.parseInt(colorArr[1]), Integer.parseInt(colorArr[2]))); + } + } + } + } + if (CollUtil.isNotEmpty(chartStatusColorClassics) && chartStatusColorClassics.size() == 3) { + for (int i = 0; i < chartStatusColorClassics.size(); i++) { + String statusColor = chartStatusColorClassics.get(i); + if (StringUtils.isNotEmpty(statusColor)) { + String[] colorArr = statusColor.split(","); + if (colorArr.length == 3) { + ChartStatus.CUSTOM_COLOR_CLASSICS.put(i, new Color(Integer.parseInt(colorArr[0]), Integer.parseInt(colorArr[1]), Integer.parseInt(colorArr[2]))); + } + } + } + } + if (CollUtil.isNotEmpty(chartStatusColorMimic) && chartStatusColorMimic.size() == 3) { + for (int i = 0; i < chartStatusColorMimic.size(); i++) { + String statusColor = chartStatusColorMimic.get(i); + if (StringUtils.isNotEmpty(statusColor)) { + String[] colorArr = statusColor.split(","); + if (colorArr.length == 3) { + ChartStatus.CUSTOM_COLOR_MIMIC.put(i, new Color(Integer.parseInt(colorArr[0]), Integer.parseInt(colorArr[1]), Integer.parseInt(colorArr[2]))); + } + } + } + } + } + + public static Color getNotDone(String modelValue) { + return getColorByKey(ChartStatus.NOT_DONE, modelValue); + } + + public static Color getToDo(String modelValue) { + return getColorByKey(ChartStatus.TO_DO, modelValue); + } + + public static Color getDone(String modelValue) { + return getColorByKey(ChartStatus.DONE, modelValue); + } + + public static Color getColorByKey(ChartStatus chartStatus, String modelValue) { + Color color = null; + if (ModelEnum.CLASSICS.name().equals(modelValue)) { + color = ChartStatus.CUSTOM_COLOR_CLASSICS.get(chartStatus.getKey()); + } else if (ModelEnum.MIMIC.name().equals(modelValue)) { + color = ChartStatus.CUSTOM_COLOR_MIMIC.get(chartStatus.getKey()); + } + if (ObjectUtil.isNull(color)) { + color = ChartStatus.CUSTOM_COLOR.get(chartStatus.getKey()); + } + return ObjectUtil.defaultNull(color, chartStatus.getColor()); + } + + public static Color getColorByKey(Integer key) { + for (ChartStatus item : ChartStatus.values()) { + if (item.getKey().equals(key)) { + Color color = ChartStatus.CUSTOM_COLOR.get(key); + return ObjectUtil.defaultNull(color, item.getColor()); + } + } + return null; + } + + /** + * 判断是否未办理 + * + * @param key 状态 + */ + public static Boolean isNotDone(Integer key) { + return ObjectUtil.isNotNull(key) && (ChartStatus.NOT_DONE.getKey().equals(key)); + } + + /** + * 判断是否待办理 + * + * @param key 状态 + */ + public static Boolean isToDo(Integer key) { + return ObjectUtil.isNotNull(key) && (ChartStatus.TO_DO.getKey().equals(key)); + } + + /** + * 判断是否已办理 + * + * @param key 状态 + */ + public static Boolean isDone(Integer key) { + return ObjectUtil.isNotNull(key) && (ChartStatus.DONE.getKey().equals(key)); + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/enums/ConditionType.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/enums/ConditionType.java new file mode 100644 index 0000000..bef7b5b --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/enums/ConditionType.java @@ -0,0 +1,99 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 条件表达式类型 + * + * @author xiarg + * @since 2025/06/03 17:57:05 + */ +@Getter +@AllArgsConstructor +public enum ConditionType { + + /** + * 等于 + */ + EQ("eq", "等于"), + /** + * 大于等于 + */ + GE("ge", "大于等于"), + /** + * 大于 + */ + GT("gt", "大于"), + /** + * 小于等于 + */ + LE("le", "小于等于"), + /** + * 包含 + */ + LIKE("like", "包含"), + /** + * 小于 + */ + LT("lt", "小于"), + /** + * 不等于 + */ + NE("ne", "不等于"), + /** + * 不包含 + */ + NOT_LIKE("notLike", "不包含"); + + /** + * 表达式类型 + */ + private final String key; + /** + * 表达式描述 + */ + private final String value; + + public static String getKeyByValue(String value) { + for (ConditionType item : ConditionType.values()) { + if (item.getValue().equals(value)) { + return item.getKey(); + } + } + return null; + } + + public static String getValueByKey(String key) { + for (ConditionType item : ConditionType.values()) { + if (item.getKey().equals(key)) { + return item.getValue(); + } + } + return null; + } + + public static ConditionType getByKey(String key) { + for (ConditionType item : ConditionType.values()) { + if (item.getKey().equals(key)) { + return item; + } + } + return null; + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/enums/CooperateType.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/enums/CooperateType.java new file mode 100644 index 0000000..7becbf0 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/enums/CooperateType.java @@ -0,0 +1,196 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.dromara.warm.flow.core.constant.FlowCons; +import org.dromara.warm.flow.core.utils.MathUtil; +import org.dromara.warm.flow.core.utils.StringUtils; + +/** + * 协作类型 + * APPROVAL-无:无其他协作方式 + * TRANSFER-转办:任务转给其他人办理 + * DEPUTE-委派:求助其他人审批,然后参照他的意见决定是否审批通过 + * COUNTERSIGN-会签:和其他人一起审批通过,才算通过 + * VOTE-票签:和部分人一起审批,达到一定通过率,才算通过 + * ADD_SIGNATURE-加签:办理中途,希望其他人一起参与办理 + * REDUCTION_SIGNATURE-减签:办理中途,希望某些人不参与办理 + * + * @author xiarg + * @since 2024/5/10 16:04 + */ +@Getter +@AllArgsConstructor +public enum CooperateType { + + /** + * 协作类型 + */ + APPROVAL(1, "无"), + + TRANSFER(2, "转办"), + + DEPUTE(3, "委派"), + + COUNTERSIGN(4, "会签"), + + VOTE(5, "票签"), + + ADD_SIGNATURE(6, "加签"), + + REDUCTION_SIGNATURE(7, "减签"); + + private final Integer key; + private final String value; + + + /** + * 票签中的固定通过人数策略前缀 + */ + public final static String PASS_COUNT = "passCount"; + /** + * 票签中的固定驳回人数策略前缀 + */ + public final static String REJECT_COUNT = "rejectCount"; + + /** + * 顺签 + */ + public final static String SEQUENCE = "sequence"; + + public static Integer getKeyByValue(String value) { + for (CooperateType item : CooperateType.values()) { + if (item.getValue().equals(value)) { + return item.getKey(); + } + } + return null; + } + + public static String getValueByKey(Integer key) { + for (CooperateType item : CooperateType.values()) { + if (item.getKey().equals(key)) { + return item.getValue(); + } + } + return null; + } + + public static CooperateType getByKey(Integer key) { + for (CooperateType item : CooperateType.values()) { + if (item.getKey().equals(key)) { + return item; + } + } + return null; + } + + + /** + * 判断是否为或签 + * @param ratio 比例 + * @return true:是;false:不是 + */ + public static boolean isOrSign(String ratio) { + return MathUtil.isZero(ratio); + } + + /** + * 判断是否是会签 + * + * @param ratio 比例 + * @return true:是;false:不是 + */ + public static boolean isCountersign(String ratio) { + return MathUtil.isHundred(ratio); + } + + /** + * 判断是否是票签中通过率策略 + * + * @param ratio 比例 + * @return true:是;false:不是 + */ + public static boolean isVoteSignPassRatio(String ratio) { + return MathUtil.isBetweenZeroAndHundred(ratio); + } + + /** + * 判断是否是票签中的固定通过人数策略 + * + * @param passCount 固定通过人数 + * @return true:是;false:不是 + */ + public static boolean isVoteSignPassCount(String passCount) { + return StringUtils.isNotEmpty(passCount) && passCount.startsWith(PASS_COUNT); + } + + /** + * 判断是否是票签中的固定驳回人数策略 + * + * @param rejectCount 固定驳回人数 + * @return true:是;false:不是 + */ + public static boolean isVoteSignRejectCount(String rejectCount) { + return StringUtils.isNotEmpty(rejectCount) && rejectCount.startsWith(REJECT_COUNT); + } + + /** + * 判断是否是票签中的默认表达式策略 + * + * @param expression 默认表达式 + * @return true:是;false:不是 + */ + public static boolean isVoteSignDefault(String expression) { + return StringUtils.isNotEmpty(expression) && expression.startsWith(FlowCons.DEFAULT); + } + + /** + * 判断是否是票签中的spel表达式策略 + * + * @param expression spel表达式 + * @return true:是;false:不是 + */ + public static boolean isVoteSignRejectSpel(String expression) { + return StringUtils.isNotEmpty(expression) && expression.startsWith(FlowCons.SPEL); + } + + /** + * 判断是否是顺签 + * + * @param expression 表达式 + * @return true:是;false:不是 + */ + public static boolean isSequenceSign(String expression) { + return StringUtils.isNotEmpty(expression) && expression.endsWith(FlowCons.SPLIT_AT + SEQUENCE); + } + + /** + * 判断是否是顺签 + * + * @param expression 表达式 + * @return true:是;false:不是 + */ + public static String removeSequence(String expression) { + if (isSequenceSign(expression)) { + return expression.substring(0, expression.lastIndexOf(FlowCons.SPLIT_AT + SEQUENCE)); + } + return expression; + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/enums/FlowStatus.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/enums/FlowStatus.java new file mode 100644 index 0000000..80dccb4 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/enums/FlowStatus.java @@ -0,0 +1,100 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.dromara.warm.flow.core.utils.ObjectUtil; + +/** + * 流程状态 + * + * @author warm + * @since 2023/3/31 12:16 + */ +@Getter +@AllArgsConstructor +public enum FlowStatus { + /** + * 流程状态 + */ + TOBESUBMIT("0", "待提交"), + + APPROVAL("1", "审批中"), + + PASS("2", "审批通过"), + + AUTO_PASS("3", "自动完成"), + + TERMINATE("4", "终止"), + + NULLIFY("5", "作废"), + + CANCEL("6", "撤销"), + + RETRIEVE("7", "取回"), + + FINISHED("8", "已完成"), + + REJECT("9", "已退回"), + + INVALID("10", "失效"), + + TASK_BACK("11", "拿回"), + + RE_START("12", "重启"); + + private final String key; + private final String value; + + public static String getKeyByValue(String value) { + for (FlowStatus item : FlowStatus.values()) { + if (item.getValue().equals(value)) { + return item.getKey(); + } + } + return null; + } + + public static String getValueByKey(String key) { + for (FlowStatus item : FlowStatus.values()) { + if (item.getKey().equals(key)) { + return item.getValue(); + } + } + return null; + } + + public static FlowStatus getByKey(String key) { + for (FlowStatus item : FlowStatus.values()) { + if (item.getKey().equals(key)) { + return item; + } + } + return null; + } + + /** + * 判断是否结束节点 + * + * @param key 枚举key + * @return boolean + */ + public static Boolean isFinished(String key) { + return ObjectUtil.isNotNull(key) && (FlowStatus.FINISHED.getKey().equals(key)); + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/enums/FormCustomEnum.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/enums/FormCustomEnum.java new file mode 100644 index 0000000..ddc2f69 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/enums/FormCustomEnum.java @@ -0,0 +1,40 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 表单类型 + * + * @author warm + * @since 2025/6/25 + */ +@Getter +@AllArgsConstructor +public enum FormCustomEnum { + + /** + * 表单路径 + */ + N, + + /** + * 表单路径 + */ + Y, +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/enums/ModelEnum.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/enums/ModelEnum.java new file mode 100644 index 0000000..afecec3 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/enums/ModelEnum.java @@ -0,0 +1,40 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 设计器模型(CLASSICS经典模型 MIMIC仿钉钉模型) + * + * @author warm + * @since 2025/6/25 + */ +@Getter +@AllArgsConstructor +public enum ModelEnum { + + /** + * 经典模型 + */ + CLASSICS, + + /** + * 仿钉钉模型 + */ + MIMIC +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/enums/NodeType.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/enums/NodeType.java new file mode 100644 index 0000000..3eea7e4 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/enums/NodeType.java @@ -0,0 +1,160 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.dromara.warm.flow.core.utils.ObjectUtil; + +/** + * 节点类型 + * + * @author warm + * @since 2023/3/31 12:16 + */ +@AllArgsConstructor +@Getter +public enum NodeType { + /** + * 开始节点 + */ + START(0, "start"), + /** + * 中间节点 + */ + BETWEEN(1, "between"), + /** + * 结束节点 + */ + END(2, "end"), + + /** + * 互斥网关 + */ + SERIAL(3, "serial"), + + /** + * 并行网关 + */ + PARALLEL(4, "parallel"), + + /** + * 包容网关 + */ + INCLUSIVE(5, "inclusive"); + + private final Integer key; + private final String value; + + public static Integer getKeyByValue(String value) { + for (NodeType item : NodeType.values()) { + if (item.getValue().equals(value)) { + return item.getKey(); + } + } + return null; + } + + public static String getValueByKey(Integer key) { + for (NodeType item : NodeType.values()) { + if (item.getKey().equals(key)) { + return item.getValue(); + } + } + return null; + } + + public static NodeType getByKey(Integer key) { + for (NodeType item : NodeType.values()) { + if (item.getKey().equals(key)) { + return item; + } + } + return null; + } + + /** + * 判断是否开始节点 + * + * @param key 枚举key + * @return boolean + */ + public static Boolean isStart(Integer key) { + return ObjectUtil.isNotNull(key) && (NodeType.START.getKey().equals(key)); + } + + /** + * 判断是否中间节点 + * + * @param key 枚举key + * @return boolean + */ + public static Boolean isBetween(Integer key) { + return ObjectUtil.isNotNull(key) && (NodeType.BETWEEN.getKey().equals(key)); + } + + /** + * 判断是否结束节点 + * + * @param key 枚举key + * @return boolean + */ + public static Boolean isEnd(Integer key) { + return ObjectUtil.isNotNull(key) && (NodeType.END.getKey().equals(key)); + } + + /** + * 判断是否网关节点 + * + * @param key 枚举key + * @return boolean + */ + public static Boolean isGateWay(Integer key) { + return ObjectUtil.isNotNull(key) && (NodeType.SERIAL.getKey().equals(key) + || NodeType.PARALLEL.getKey().equals(key)|| NodeType.INCLUSIVE.getKey().equals(key)); + } + + /** + * 判断是否互斥网关节点 + * + * @param key 枚举key + * @return boolean + */ + public static Boolean isGateWaySerial(Integer key) { + return ObjectUtil.isNotNull(key) && NodeType.SERIAL.getKey().equals(key); + } + + /** + * 判断是否并行网关节点 + * + * @param key 枚举key + * @return boolean + */ + public static Boolean isGateWayParallel(Integer key) { + return ObjectUtil.isNotNull(key) && NodeType.PARALLEL.getKey().equals(key); + } + + /** + * 判断是否包容网关节点 + * + * @param key 枚举key + * @return boolean + */ + public static Boolean isGateWayInclusive(Integer key) { + return ObjectUtil.isNotNull(key) && NodeType.INCLUSIVE.getKey().equals(key); + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/enums/PublishStatus.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/enums/PublishStatus.java new file mode 100644 index 0000000..ba021e9 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/enums/PublishStatus.java @@ -0,0 +1,70 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 节点类型 + * + * @author warm + * @since 2023/3/31 12:16 + */ +@Getter +@AllArgsConstructor +public enum PublishStatus { + + /** + * 9=已失效;0=未发布;1=已发布 + */ + EXPIRED(9, "已失效"), + + UNPUBLISHED(0, "未发布"), + + PUBLISHED(1, "已发布"); + + private final Integer key; + private final String value; + + public static Integer getKeyByValue(String value) { + for (PublishStatus item : PublishStatus.values()) { + if (item.getValue().equals(value)) { + return item.getKey(); + } + } + return null; + } + + public static String getValueByKey(Integer key) { + for (PublishStatus item : PublishStatus.values()) { + if (item.getKey().equals(key)) { + return item.getValue(); + } + } + return null; + } + + public static PublishStatus getByKey(Integer key) { + for (PublishStatus item : PublishStatus.values()) { + if (item.getKey().equals(key)) { + return item; + } + } + return null; + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/enums/SkipType.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/enums/SkipType.java new file mode 100644 index 0000000..3e63a36 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/enums/SkipType.java @@ -0,0 +1,100 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.dromara.warm.flow.core.utils.StringUtils; + +/** + * 审批动作 + * + * @author warm + * @since 2023/3/31 12:16 + */ +@Getter +@AllArgsConstructor +public enum SkipType { + /** + * 审批动作 + */ + PASS("PASS", "审批通过"), + + REJECT("REJECT", "退回"), + + NONE("NONE", "无动作"); + + private final String key; + private final String value; + + public static String getKeyByValue(String value) { + for (SkipType item : SkipType.values()) { + if (item.getValue().equals(value)) { + return item.getKey(); + } + } + return null; + } + + public static String getValueByKey(String key) { + for (SkipType item : SkipType.values()) { + if (item.getKey().equals(key)) { + return item.getValue(); + } + } + return null; + } + + public static SkipType getByKey(String key) { + for (SkipType item : SkipType.values()) { + if (item.getKey().equals(key)) { + return item; + } + } + return null; + } + + /** + * 判断是否通过类型 + * + * @param key 枚举key + * @return boolean + */ + public static Boolean isPass(String key) { + return StringUtils.isNotEmpty(key) && (SkipType.PASS.getKey().equals(key)); + } + + /** + * 判断是否退回类型 + * + * @param key 枚举key + * @return boolean + */ + public static Boolean isReject(String key) { + return StringUtils.isNotEmpty(key) && (SkipType.REJECT.getKey().equals(key)); + } + + /** + * 判断是否无动作类型 + * + * @param key 枚举key + * @return boolean + */ + public static Boolean isNone(String key) { + return StringUtils.isNotEmpty(key) && (SkipType.NONE.getKey().equals(key)); + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/enums/UserType.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/enums/UserType.java new file mode 100644 index 0000000..c56bcdb --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/enums/UserType.java @@ -0,0 +1,70 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 流程用户类型 + * + * @author xiarg + * @since 2024/5/10 16:04 + */ +@Getter +@AllArgsConstructor +public enum UserType { + + /** + * 流程用户类型 + */ + APPROVAL("1", "待办任务的审批人权限"), + + TRANSFER("2", "待办任务的转办人权限"), + + DEPUTE("3", "待办任务的委托人权限"); + + private final String key; + private final String value; + + public static String getKeyByValue(String value) { + for (UserType item : UserType.values()) { + if (item.getValue().equals(value)) { + return item.getKey(); + } + } + return null; + } + + public static String getValueByKey(String key) { + for (UserType item : UserType.values()) { + if (item.getKey().equals(key)) { + return item.getValue(); + } + } + return null; + } + + public static UserType getByKey(String key) { + for (UserType item : UserType.values()) { + if (item.getKey().equals(key)) { + return item; + } + } + return null; + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/exception/FlowException.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/exception/FlowException.java new file mode 100644 index 0000000..e80b386 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/exception/FlowException.java @@ -0,0 +1,80 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.exception; + +import lombok.Getter; + +/** + * 流程异常 + * + * @author warm + */ +public final class FlowException extends RuntimeException { + private static final long serialVersionUID = 1L; + + /** + * 错误码 + */ + @Getter + private Integer code; + + /** + * 错误提示 + */ + private String message; + + /** + * 错误明细,内部调试错误 + */ + @Getter + private String detailMessage; + + /** + * 空构造方法,避免反序列化问题 + */ + public FlowException() { + } + + public FlowException(String message) { + this.message = message; + } + + public FlowException(String message, Throwable cause) { + super(message, cause); + this.message = message; + } + + public FlowException(String message, Integer code) { + this.message = message; + this.code = code; + } + + public FlowException setDetailMessage(String detailMessage) { + this.detailMessage = detailMessage; + return this; + } + + @Override + public String getMessage() { + return message; + } + + public FlowException setMessage(String message) { + this.message = message; + return this; + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/handler/DataFillHandler.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/handler/DataFillHandler.java new file mode 100644 index 0000000..7583fb9 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/handler/DataFillHandler.java @@ -0,0 +1,98 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.handler; + +import org.dromara.warm.flow.core.FlowEngine; +import org.dromara.warm.flow.core.entity.RootEntity; +import org.dromara.warm.flow.core.utils.IdUtils; +import org.dromara.warm.flow.core.utils.ObjectUtil; +import org.dromara.warm.flow.core.utils.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Date; +import java.util.Objects; + +/** + * 数据填充handler,以下三个接口按照实际情况实现 + * + * @author warm + * @since 2023/4/1 15:37 + */ +public interface DataFillHandler { + + Logger logger = LoggerFactory.getLogger(DataFillHandler.class); + + /** + * id填充 + * + * @param object object + */ + default void idFill(Object object) { + RootEntity entity = (RootEntity) object; + if (ObjectUtil.isNull(entity)) { + logger.warn("Insert operation failed - Reason: Entity is null after casting"); + + return; + } + if (Objects.isNull(entity.getId())) { + entity.setId(IdUtils.nextId()); + } + } + + /** + * 新增填充 + * + * @param object object + */ + default void insertFill(Object object) { + RootEntity entity = (RootEntity) object; + if (ObjectUtil.isNull(entity)) { + logger.warn("Insert operation failed - Reason: Entity is null after casting"); + return; + } + entity.setCreateTime(ObjectUtil.isNotNull(entity.getCreateTime()) ? entity.getCreateTime() : new Date()); + entity.setUpdateTime(ObjectUtil.isNotNull(entity.getUpdateTime()) ? entity.getUpdateTime() : new Date()); + + PermissionHandler permissionHandler = FlowEngine.permissionHandler(); + String handler = null; + if (permissionHandler != null) { + handler = permissionHandler.getHandler(); + } + entity.setCreateBy(StringUtils.isNotEmpty(handler) ? handler : entity.getCreateBy()); + entity.setUpdateBy(StringUtils.isNotEmpty(handler) ? handler : entity.getUpdateBy()); + } + + /** + * 设置更新常用参数 + * + * @param object object + */ + default void updateFill(Object object) { + RootEntity entity = (RootEntity) object; + if (ObjectUtil.isNull(entity)) { + logger.warn("Insert operation failed - Reason: Entity is null after casting"); + return; + } + entity.setUpdateTime(ObjectUtil.isNotNull(entity.getUpdateTime()) ? entity.getUpdateTime() : new Date()); + PermissionHandler permissionHandler = FlowEngine.permissionHandler(); + String handler = null; + if (permissionHandler != null) { + handler = permissionHandler.getHandler(); + } + entity.setUpdateBy(StringUtils.isNotEmpty(handler) ? handler : entity.getUpdateBy()); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/handler/DefaultHandlerStrategy.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/handler/DefaultHandlerStrategy.java new file mode 100644 index 0000000..54300ce --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/handler/DefaultHandlerStrategy.java @@ -0,0 +1,40 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.handler; + +import org.dromara.warm.flow.core.strategy.HandlerStrategy; + +import java.util.Map; + +/** + * 默认办理人表达式策略: @@default@@|${flag} + * + * @author warm + */ +public class DefaultHandlerStrategy implements HandlerStrategy { + + @Override + public String getType() { + return "$"; + } + + @Override + public Object preEval(String expression, Map variable) { + String result = expression.replace("${", "").replace("}", ""); + return variable.get(result); + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/handler/PermissionHandler.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/handler/PermissionHandler.java new file mode 100644 index 0000000..8f22c9c --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/handler/PermissionHandler.java @@ -0,0 +1,56 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.handler; + +import org.dromara.warm.flow.core.dto.FlowParams; + +import java.util.List; + +/** + * 办理人权限处理器 + * 用户获取工作流中用到的permissionFlag和handler + * permissionFlag: 办理人权限标识,比如用户,角色,部门等,用于校验是否有权限办理任务 + * handler: 当前办理人唯一标识,就是确定唯一用的,如用户id,通常用来入库,记录流程实例创建人,办理人 + * + * @author shadow + */ +public interface PermissionHandler { + + /** + * 办理人权限标识,比如用户,角色,部门等,用于校验是否有权限办理任务 + * 后续在{@link FlowParams#getPermissionFlag} 中获取 + * 返回当前用户权限集合 + * + */ + List permissions(); + + /** + * 获取当前办理人:就是确定唯一用的,如用户id,通常用来入库,记录流程实例创建人,办理人 + * 后续在{@link FlowParams#getHandler()} 中获取 + * + * @return 当前办理人 + */ + String getHandler(); + + /** + * 转换办理人,比如设计器中预设了能办理的人,如果其中包含角色或者部门id等,可以通过此接口进行转换成用户id + */ + default List convertPermissions(List permissions) { + return permissions; + } + + List getHandlerList(); +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/handler/TenantHandler.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/handler/TenantHandler.java new file mode 100644 index 0000000..b18f1d2 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/handler/TenantHandler.java @@ -0,0 +1,32 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.handler; + +/** + * 全局租户处理器接口 + * + * @author warm + */ +public interface TenantHandler { + + /** + * 获取租户ID + * + * @return 租户ID + */ + String getTenantId(); + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/invoker/FrameInvoker.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/invoker/FrameInvoker.java new file mode 100644 index 0000000..f4606a8 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/invoker/FrameInvoker.java @@ -0,0 +1,71 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.invoker; + +import java.util.function.Function; + +/** + * 获取bean方法 + * + * @author warm + */ +public class FrameInvoker { + + public static FrameInvoker frameInvoker = new FrameInvoker<>(); + + private Function, M> beanFunction; + + private Function cfgFunction; + + public FrameInvoker() { + } + + /** + * 设置获取beanFunction + * + * @param function + * @param + */ + public static void setBeanFunction(Function, M> function) { + frameInvoker.beanFunction = function; + } + + public static M getBean(Class tClass) { + try { + return (M) frameInvoker.beanFunction.apply(tClass); + } catch (Exception e) { + return null; + } + } + + /** + * 设置获取配置function + * + * @param function + */ + public static void setCfgFunction(Function function) { + frameInvoker.cfgFunction = function; + } + + public static String getCfg(String key) { + try { + return (String) frameInvoker.cfgFunction.apply(key); + } catch (Exception e) { + return null; + } + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/json/JsonConvert.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/json/JsonConvert.java new file mode 100644 index 0000000..670ccb6 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/json/JsonConvert.java @@ -0,0 +1,61 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.json; + +import java.util.List; +import java.util.Map; + +/** + * map和json字符串转换工具类 + * + * @author warm + */ +public interface JsonConvert { + + /** + * 将字符串转为map + * + * @param jsonStr json字符串 + * @return map + */ + Map strToMap(String jsonStr); + + /** + * 将字符串转为bean + * + * @param jsonStr json字符串 + * @param clazz Class + * @return T + */ + T strToBean(String jsonStr, Class clazz); + + /** + * TODO 未测试 + * 将字符串转为集合 + * + * @param jsonStr json字符串 + * @return List + */ + List strToList(String jsonStr); + + /** + * 将对象转为字符串 + * + * @param variable object + * @return json字符串 + */ + String objToStr(Object variable); +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/keygen/KenGen.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/keygen/KenGen.java new file mode 100644 index 0000000..ec9d1b7 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/keygen/KenGen.java @@ -0,0 +1,32 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.keygen; + +/** + * 注解生成接口 + * + * @author warm + */ +public interface KenGen { + + /** + * 获得下一个ID (该方法是线程安全的) + * + * @return SnowflakeId + */ + long nextId(); + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/keygen/SnowFlakeId14.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/keygen/SnowFlakeId14.java new file mode 100644 index 0000000..f60f848 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/keygen/SnowFlakeId14.java @@ -0,0 +1,151 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.keygen; + +/** + * warm 生成14位有序id + * + * @author warm + */ +public class SnowFlakeId14 implements KenGen { + /** + * 开始时间截 (本次时间戳为:Thu Nov 04 2010 09:42:54 GMT+0800 (中国标准时间)----1288834974657L---1656543015264587776--19 ) + */ + private final long startTime = 1683803335498L; + + /** + * 机器id所占的位数 + */ + private final long workerIdBits = 3L; + + /** + * 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) + */ + private final long maxWorkerId = -1L ^ (-1L << workerIdBits); + + /** + * 序列在id中占的位数 + */ + private final long sequenceBits = 5L; + + /** + * 机器ID向左移12位 + */ + private final long workerIdShift = sequenceBits; + + /** + * 时间截向左移22位(10+12) + */ + private final long timestampLeftShift = sequenceBits + workerIdBits; + + /** + * 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) + */ + private final long sequenceMask = -1L ^ (-1L << sequenceBits); + + /** + * 工作机器ID(0~1024) + */ + private long workerId; + + /** + * 毫秒内序列(0~4095) + */ + private long sequence = 0L; + + /** + * 上次生成ID的时间截 + */ + private long lastTimestamp = -1L; + + //==============================Constructors===================================== + + /** + * 构造函数 + * + * @param workerId 工作ID (0~1024) + */ + public SnowFlakeId14(long workerId) { + if (workerId > maxWorkerId || workerId < 0) { + throw new IllegalArgumentException(String.format("workerId can't be greater than %d or less than 0", maxWorkerId)); + } + this.workerId = workerId; + } + + // ==============================Methods========================================== + + /** + * 获得下一个ID (该方法是线程安全的) + * + * @return SnowflakeId + */ + @Override + public synchronized long nextId() { + long timestamp = timeGen(); + + //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常 + if (timestamp < lastTimestamp) { + throw new RuntimeException( + String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); + } + + //如果是同一时间生成的,则进行毫秒内序列 + if (lastTimestamp == timestamp) { + sequence = (sequence + 1) & sequenceMask; + //毫秒内序列溢出 + if (sequence == 0) { + //阻塞到下一个毫秒,获得新的时间戳 + timestamp = tilNextMillis(lastTimestamp); + } + } + //时间戳改变,毫秒内序列重置 + else { + sequence = 0L; + } + + //上次生成ID的时间截 + lastTimestamp = timestamp; + + //移位并通过或运算拼到一起组成64位的ID + return ((timestamp - startTime) << timestampLeftShift) + | (workerId << workerIdShift) + | sequence; + } + + /** + * 阻塞到下一个毫秒,直到获得新的时间戳 + * + * @param lastTimestamp 上次生成ID的时间截 + * @return 当前时间戳 + */ + protected long tilNextMillis(long lastTimestamp) { + long timestamp = timeGen(); + while (timestamp <= lastTimestamp) { + timestamp = timeGen(); + } + return timestamp; + } + + /** + * 返回以毫秒为单位的当前时间 + * + * @return 当前时间(毫秒) + */ + protected long timeGen() { + return System.currentTimeMillis(); + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/keygen/SnowFlakeId15.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/keygen/SnowFlakeId15.java new file mode 100644 index 0000000..01b4fad --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/keygen/SnowFlakeId15.java @@ -0,0 +1,104 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.keygen; + +/** + * warm 生成15位有序id + * + * @author HUANGJIANSHISHENG + */ +public class SnowFlakeId15 implements KenGen { + /** + * 开始时间戳 (任意设置,建议为项目的开始时间) + * // 2021-01-01 00:00:00 + */ + private final long epoch = 1609459200000L; + + + /** + * 各部分的位数-序列号占用6位 + */ + private final long sequenceBits = 6L; + /** + * 各部分的位数-机器ID占用4位 + */ + private final long machineIdBits = 4L; + + /** + * 各部分的最大值 + */ + private final long maxMachineId = -1L ^ (-1L << machineIdBits); + private final long maxSequence = -1L ^ (-1L << sequenceBits); + + /** + * 各部分的偏移量 + */ + private final long sequenceShift = 0; + private final long machineIdShift = sequenceBits; + private final long timestampShift = machineIdBits + sequenceBits; + + private long machineId; + private long sequence = 0L; + private long lastTimestamp = -1L; + + public SnowFlakeId15(long machineId) { + if (machineId > maxMachineId || machineId < 0) { + throw new IllegalArgumentException("Machine ID is out of bounds."); + } + this.machineId = machineId; + } + + @Override + public synchronized long nextId() { + long timestamp = timeGen(); + + if (timestamp < lastTimestamp) { + throw new RuntimeException("Clock moved backwards. Refusing to generate id."); + } + + if (timestamp == lastTimestamp) { + // 当前毫秒内,则+1 + sequence = (sequence + 1) & maxSequence; + if (sequence == 0) { + timestamp = tilNextMillis(lastTimestamp); + } + } else { + sequence = 0L; + } + + lastTimestamp = timestamp; + + // 生成ID并保证其为15位 + long id = ((timestamp - epoch) << timestampShift) + | (machineId << machineIdShift) + | sequence; + // 保证生成的ID为15位 + return id % 10_000_000_000_000_000L; + } + + private long tilNextMillis(long lastTimestamp) { + long timestamp = timeGen(); + while (timestamp <= lastTimestamp) { + timestamp = timeGen(); + } + return timestamp; + } + + private long timeGen() { + return System.currentTimeMillis(); + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/keygen/SnowFlakeId19.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/keygen/SnowFlakeId19.java new file mode 100644 index 0000000..b883573 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/keygen/SnowFlakeId19.java @@ -0,0 +1,187 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.keygen; + +/** + * Twitter_Snowflake
+ * SnowFlake的结构如下(每部分用-分开):
+ * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
+ * 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0
+ * 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截) + * 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69
+ * 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId
+ * 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号
+ * 加起来刚好64位,为一个Long型。
+ * SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。 + * + * @author warm + */ +public class SnowFlakeId19 implements KenGen { + + // ==============================Fields=========================================== + /** + * 开始时间截 (2015-01-01) + */ + private final long twepoch = 1420041600000L; + + /** + * 机器id所占的位数 + */ + private final long workerIdBits = 5L; + + /** + * 数据标识id所占的位数 + */ + private final long datacenterIdBits = 5L; + + /** + * 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) + */ + private final long maxWorkerId = -1L ^ (-1L << workerIdBits); + + /** + * 支持的最大数据标识id,结果是31 + */ + private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); + + /** + * 序列在id中占的位数 + */ + private final long sequenceBits = 12L; + + /** + * 机器ID向左移12位 + */ + private final long workerIdShift = sequenceBits; + + /** + * 数据标识id向左移17位(12+5) + */ + private final long datacenterIdShift = sequenceBits + workerIdBits; + + /** + * 时间截向左移22位(5+5+12) + */ + private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; + + /** + * 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) + */ + private final long sequenceMask = -1L ^ (-1L << sequenceBits); + + /** + * 工作机器ID(0~31) + */ + private long workerId; + + /** + * 数据中心ID(0~31) + */ + private long datacenterId; + + /** + * 毫秒内序列(0~4095) + */ + private long sequence = 0L; + + /** + * 上次生成ID的时间截 + */ + private long lastTimestamp = -1L; + + //==============================Constructors===================================== + + /** + * 构造函数 + * + * @param workerId 工作ID (0~31) + * @param datacenterId 数据中心ID (0~31) + */ + public SnowFlakeId19(long workerId, long datacenterId) { + if (workerId > maxWorkerId || workerId < 0) { + throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId)); + } + if (datacenterId > maxDatacenterId || datacenterId < 0) { + throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId)); + } + this.workerId = workerId; + this.datacenterId = datacenterId; + } + + // ==============================Methods========================================== + + /** + * 获得下一个ID (该方法是线程安全的) + * + * @return SnowflakeId + */ + @Override + public synchronized long nextId() { + long timestamp = timeGen(); + + // 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常 + if (timestamp < lastTimestamp) { + throw new RuntimeException( + String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); + } + + // 如果是同一时间生成的,则进行毫秒内序列 + if (lastTimestamp == timestamp) { + sequence = (sequence + 1) & sequenceMask; + // 毫秒内序列溢出 + if (sequence == 0) { + // 阻塞到下一个毫秒,获得新的时间戳 + timestamp = tilNextMillis(lastTimestamp); + } + } + // 时间戳改变,毫秒内序列重置 + else { + sequence = 0L; + } + + // 上次生成ID的时间截 + lastTimestamp = timestamp; + + // 移位并通过或运算拼到一起组成64位的ID + return ((timestamp - twepoch) << timestampLeftShift) + | (datacenterId << datacenterIdShift) + | (workerId << workerIdShift) + | sequence; + } + + /** + * 阻塞到下一个毫秒,直到获得新的时间戳 + * + * @param lastTimestamp 上次生成ID的时间截 + * @return 当前时间戳 + */ + protected long tilNextMillis(long lastTimestamp) { + long timestamp = timeGen(); + while (timestamp <= lastTimestamp) { + timestamp = timeGen(); + } + return timestamp; + } + + /** + * 返回以毫秒为单位的当前时间 + * + * @return 当前时间(毫秒) + */ + protected long timeGen() { + return System.currentTimeMillis(); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/listener/GlobalListener.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/listener/GlobalListener.java new file mode 100644 index 0000000..a7ff256 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/listener/GlobalListener.java @@ -0,0 +1,80 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.listener; + +import java.io.Serializable; + +/** + * 全局监听器: 整个系统只有一个,任务开始、分派、完成和创建、时期执行 + * + * @author warm + * @since 2024/11/17 + */ +public interface GlobalListener extends Serializable { + + /** + * 开始监听器,任务开始办理时执行 + * + * @param listenerVariable 监听器变量 + */ + default void start(ListenerVariable listenerVariable) { + + } + + /** + * 分派监听器,动态修改代办任务信息 + * + * @param listenerVariable 监听器变量 + */ + default void assignment(ListenerVariable listenerVariable) { + + } + + /** + * 完成监听器,当前任务完成后执行 + * + * @param listenerVariable 监听器变量 + */ + default void finish(ListenerVariable listenerVariable) { + + } + + /** + * 创建监听器,任务创建时执行 + * + * @param listenerVariable 监听器变量 + */ + default void create(ListenerVariable listenerVariable) { + + } + + default void notify(String type, ListenerVariable listenerVariable) { + switch (type) { + case Listener.LISTENER_START: + start(listenerVariable); + break; + case Listener.LISTENER_ASSIGNMENT: + assignment(listenerVariable); + break; + case Listener.LISTENER_FINISH: + finish(listenerVariable); + break; + case Listener.LISTENER_CREATE: + create(listenerVariable); + break; + } + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/listener/Listener.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/listener/Listener.java new file mode 100644 index 0000000..4d8dc9c --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/listener/Listener.java @@ -0,0 +1,58 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.listener; + +import java.io.Serializable; + +/** + * 监听器 + * + * @author warm + */ +public interface Listener extends Serializable { + + /** + * 开始监听器,任务开始办理时执行 + */ + String LISTENER_START = "start"; + + /** + * 分派监听器,动态修改代办任务信息 + */ + String LISTENER_ASSIGNMENT = "assignment"; + + /** + * 完成监听器,当前任务完成后执行 + */ + String LISTENER_FINISH = "finish"; + + /** + * 创建监听器,任务创建时执行 + */ + String LISTENER_CREATE = "create"; + + /** + * 表单数据加载监听器,1.3.0 内置表单使用 + */ + String LISTENER_FORM_LOAD = "formLoad"; + + /** + * 通知 + * + * @param variable variable + */ + void notify(ListenerVariable variable); +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/listener/ListenerVariable.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/listener/ListenerVariable.java new file mode 100644 index 0000000..bdd693b --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/listener/ListenerVariable.java @@ -0,0 +1,212 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.listener; + +import org.dromara.warm.flow.core.dto.FlowParams; +import org.dromara.warm.flow.core.entity.Definition; +import org.dromara.warm.flow.core.entity.Instance; +import org.dromara.warm.flow.core.entity.Node; +import org.dromara.warm.flow.core.entity.Task; + +import java.util.List; +import java.util.Map; + +/** + * 监听器变量 + * + * @author warm + */ +public class ListenerVariable { + + /** + * 流程定义 + */ + private Definition definition; + + /** + * 流程实例 + */ + private Instance instance; + + /** + * 监听器对应的节点 + */ + private Node node; + + /** + * 当前任务 + */ + private Task task; + + /** + * 下一次执行的节点集合 + */ + private List nextNodes; + + /** + * 新创建任务集合 + */ + private List nextTasks; + + /** + * 流程变量 + */ + private Map variable; + + /** + * 工作流内置参数 + */ + private FlowParams flowParams; + + + public ListenerVariable() { + } + + public ListenerVariable(Definition definition, Instance instance, Map variable) { + this.definition = definition; + this.instance = instance; + this.variable = variable; + } + + public ListenerVariable(Definition definition, Instance instance, Node node, Map variable) { + this.definition = definition; + this.instance = instance; + this.node = node; + this.variable = variable; + } + + public ListenerVariable(Definition definition, Instance instance, Map variable, Task task) { + this.definition = definition; + this.instance = instance; + this.variable = variable; + this.task = task; + } + + public ListenerVariable(Definition definition, Instance instance, Node node, Map variable, Task task) { + this.definition = definition; + this.instance = instance; + this.node = node; + this.variable = variable; + this.task = task; + } + + public ListenerVariable(Definition definition, Instance instance, Node node, Map variable, Task task, List nextNodes) { + this.definition = definition; + this.instance = instance; + this.node = node; + this.variable = variable; + this.task = task; + this.nextNodes = nextNodes; + } + + public ListenerVariable(Definition definition, Instance instance, Node node, Map variable, Task task + , List nextNodes, List nextTasks) { + this.definition = definition; + this.instance = instance; + this.node = node; + this.variable = variable; + this.task = task; + this.nextNodes = nextNodes; + this.nextTasks = nextTasks; + } + + public Definition getDefinition() { + return definition; + } + + public ListenerVariable setDefinition(Definition definition) { + this.definition = definition; + return this; + } + + public Instance getInstance() { + return instance; + } + + public ListenerVariable setInstance(Instance instance) { + this.instance = instance; + return this; + } + + public Node getNode() { + return node; + } + + public ListenerVariable setNode(Node node) { + this.node = node; + return this; + } + + public Task getTask() { + return task; + } + + public ListenerVariable setTask(Task task) { + this.task = task; + return this; + } + + public List getNextNodes() { + return nextNodes; + } + + public ListenerVariable setNextNodes(List nextNodes) { + this.nextNodes = nextNodes; + return this; + } + + public List getNextTasks() { + return nextTasks; + } + + public ListenerVariable setNextTasks(List nextTasks) { + this.nextTasks = nextTasks; + return this; + } + + public Map getVariable() { + return variable; + } + + public ListenerVariable setVariable(Map variable) { + this.variable = variable; + return this; + } + + public FlowParams getFlowParams() { + return flowParams; + } + + public ListenerVariable setFlowParams(FlowParams flowParams) { + this.flowParams = flowParams; + return this; + } + + + @Override + public String toString() { + return "ListenerVariable{" + + "definition=" + definition + + ", instance=" + instance + + ", node=" + node + + ", task=" + task + + ", nextNodes=" + nextNodes + + ", nextTasks=" + nextTasks + + ", variable=" + variable + + ", flowParams=" + flowParams + + '}'; + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/listener/ValueHolder.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/listener/ValueHolder.java new file mode 100644 index 0000000..5473c4a --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/listener/ValueHolder.java @@ -0,0 +1,39 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.listener; + +import lombok.Data; + +/** + * @author warm + */ +@Data +public class ValueHolder { + + /** + * 路径 + */ + private String path; + /** + * 监听器 + */ + private Listener listener; + /** + * 参数 + */ + private String params; + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/orm/agent/WarmQuery.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/orm/agent/WarmQuery.java new file mode 100644 index 0000000..9de3408 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/orm/agent/WarmQuery.java @@ -0,0 +1,178 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.orm.agent; + +import lombok.Getter; +import lombok.Setter; +import org.dromara.warm.flow.core.orm.service.IWarmService; +import org.dromara.warm.flow.core.utils.CollUtil; +import org.dromara.warm.flow.core.utils.ObjectUtil; +import org.dromara.warm.flow.core.utils.page.OrderBy; +import org.dromara.warm.flow.core.utils.page.Page; + +import java.util.List; + +/** + * 查询代理层处理 + * + * @author warm + * @since 2023-03-17 + */ +public class WarmQuery implements OrderBy { + + /** + * 排序字段 + */ + @Setter + private String orderBy; + + /** + * 排序的方向desc或者asc + */ + private String isAsc = "ASC"; + + @Setter + @Getter + private IWarmService warmService; + + public WarmQuery(IWarmService warmService) { + this.warmService = warmService; + } + + /** + * 查询列表 + * + * @param entity 实体列表 + * @return 集合 + */ + public Page page(T entity, Page page) { + if (ObjectUtil.isNull(page)) { + page = new Page<>(1, 10, orderBy, isAsc); + } + return warmService.page(entity, page.setOrderBy(orderBy).setIsAsc(isAsc)); + } + + /** + * 查询列表 + * + * @param entity 实体列表 + * @return 集合 + */ + public List list(T entity) { + return warmService.list(entity, this); + } + + /** + * 查询列表 + * + * @param entity 实体列表 + * @return 集合 + */ + public T getOne(T entity) { + List list = warmService.list(entity, this); + return CollUtil.getOne(list); + } + + /** + * id设置正序排列 + * + * @return 集合 + */ + public WarmQuery orderById() { + this.orderBy = "id"; + return this; + } + + /** + * 创建时间设置正序排列 + * + * @return 集合 + */ + public WarmQuery orderByCreateTime() { + this.orderBy = "create_time"; + return this; + } + + /** + * 更新时间设置正序排列 + * + * @return 集合 + */ + public WarmQuery orderByUpdateTime() { + this.orderBy = "update_time"; + return this; + } + + /** + * 设置正序排列 + * + * @return 集合 + */ + public WarmQuery desc() { + this.isAsc = "DESC"; + return this; + } + + /** + * 设置正序排列 + * + * @param orderByField 排序字段 + * @return 集合 + */ + public WarmQuery orderByAsc(String orderByField) { + this.orderBy = orderByField; + this.isAsc = "ASC"; + return this; + } + + /** + * 设置倒序排列 + * + * @param orderByField 排序字段 + * @return 集合 + */ + public WarmQuery orderByDesc(String orderByField) { + this.orderBy = orderByField; + this.isAsc = "DESC"; + return this; + } + + /** + * 用户自定义排序方案 + * + * @param orderByField 排序字段 + * @return 集合 + */ + public WarmQuery orderBy(String orderByField) { + this.orderBy = orderByField; + return this; + } + + @Override + public String getOrderBy() { + return orderBy; + } + + @Override + public String getIsAsc() { + return isAsc; + } + + public WarmQuery setIsAsc(String isAsc) { + this.isAsc = isAsc; + return this; + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/orm/dao/FlowDefinitionDao.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/orm/dao/FlowDefinitionDao.java new file mode 100644 index 0000000..a71c5d1 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/orm/dao/FlowDefinitionDao.java @@ -0,0 +1,44 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.orm.dao; + +import org.dromara.warm.flow.core.entity.Definition; + +import java.util.List; + +/** + * 流程定义Dao接口,不同的orm扩展包实现它 + * + * @author warm + * @since 2023-03-29 + */ +public interface FlowDefinitionDao extends WarmDao { + + /** + * 根据编码批量查询 + * @param flowCodeList 流程编码集 + * @return 查询结果 + */ + List queryByCodeList(List flowCodeList); + + /** + * 根据ID批量修改发布状态 + * @see org.dromara.warm.flow.core.enums.PublishStatus + * @param ids ids + * @param publishStatus 发布状态(9=已失效;0=未发布;1=已发布) + */ + void updatePublishStatus(List ids, Integer publishStatus); +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/orm/dao/FlowFormDao.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/orm/dao/FlowFormDao.java new file mode 100644 index 0000000..ef7c4fe --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/orm/dao/FlowFormDao.java @@ -0,0 +1,32 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.orm.dao; + +import org.dromara.warm.flow.core.entity.Form; + +import java.util.List; + +/** + * 流程表单Dao接口,不同的orm扩展包实现它 + * + * @author vanlin + * @className FlowFormDao + * @description + * @since 2024/8/19 10:24 + */ +public interface FlowFormDao extends WarmDao { + List queryByCodeList(List formCodeList); +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/orm/dao/FlowHisTaskDao.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/orm/dao/FlowHisTaskDao.java new file mode 100644 index 0000000..7f6741d --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/orm/dao/FlowHisTaskDao.java @@ -0,0 +1,64 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.orm.dao; + +import org.dromara.warm.flow.core.entity.HisTask; + +import java.util.List; + +/** + * 历史任务记录Mapper接口 + * + * @author warm + * @since 2023-03-29 + */ +public interface FlowHisTaskDao extends WarmDao { + + /** + * 根据instanceId获取未退回的历史记录 + * + * @param instanceId + * @return + */ + List getNoReject(Long instanceId); + + + /** + * 根据instanceId和流程编码获取未退回的历史记录 + * + * @param instanceId + * @param nodeCodes + * @return + */ + List getByInsAndNodeCodes(Long instanceId, List nodeCodes); + + /** + * 根据instanceIds删除 + * + * @param instanceIds 主键 + * @return 结果 + */ + int deleteByInsIds(List instanceIds); + + /** + * 根据任务id和协作类型查询 + * + * @param taskId + * @param cooperateTypes + * @return + */ + List listByTaskIdAndCooperateTypes(Long taskId, Integer[] cooperateTypes); +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/orm/dao/FlowInstanceDao.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/orm/dao/FlowInstanceDao.java new file mode 100644 index 0000000..e1e32be --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/orm/dao/FlowInstanceDao.java @@ -0,0 +1,37 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.orm.dao; + +import org.dromara.warm.flow.core.entity.Instance; + +import java.util.List; + +/** + * 流程实例Mapper接口 + * + * @author warm + * @since 2023-03-29 + */ +public interface FlowInstanceDao extends WarmDao { + + /** + * 根据流程定义ID,查询流程实例集合 + * + * @param defIds 流程定义ID集合 + * @return 流程实例集合 + */ + List getByDefIds(List defIds); +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/orm/dao/FlowNodeDao.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/orm/dao/FlowNodeDao.java new file mode 100644 index 0000000..0c02d20 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/orm/dao/FlowNodeDao.java @@ -0,0 +1,42 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.orm.dao; + +import org.dromara.warm.flow.core.entity.Node; + +import java.io.Serializable; +import java.util.Collection; +import java.util.List; + + +/** + * 流程节点Mapper接口 + * + * @author warm + * @since 2023-03-29 + */ +public interface FlowNodeDao extends WarmDao { + + List getByNodeCodes(List nodeCodes, Long definitionId); + + /** + * 批量删除流程节点 + * + * @param defIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteNodeByDefIds(Collection defIds); +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/orm/dao/FlowSkipDao.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/orm/dao/FlowSkipDao.java new file mode 100644 index 0000000..0833a17 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/orm/dao/FlowSkipDao.java @@ -0,0 +1,38 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.orm.dao; + +import org.dromara.warm.flow.core.entity.Skip; + +import java.io.Serializable; +import java.util.Collection; + +/** + * 节点跳转关联Mapper接口 + * + * @author warm + * @since 2023-03-29 + */ +public interface FlowSkipDao extends WarmDao { + + /** + * 批量删除节点跳转关联 + * + * @param defIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteSkipByDefIds(Collection defIds); +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/orm/dao/FlowTaskDao.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/orm/dao/FlowTaskDao.java new file mode 100644 index 0000000..77db6bd --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/orm/dao/FlowTaskDao.java @@ -0,0 +1,39 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.orm.dao; + +import org.dromara.warm.flow.core.entity.Task; + +import java.util.List; + +/** + * 待办任务Mapper接口 + * + * @author warm + * @since 2023-03-29 + */ +public interface FlowTaskDao extends WarmDao { + + /** + * 根据instanceIds删除 + * + * @param instanceIds 主键 + * @return 结果 + */ + int deleteByInsIds(List instanceIds); + + List getByInsIdAndNodeCodes(Long instanceId, List nodeCodes); +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/orm/dao/FlowUserDao.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/orm/dao/FlowUserDao.java new file mode 100644 index 0000000..5330886 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/orm/dao/FlowUserDao.java @@ -0,0 +1,58 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.orm.dao; + +import org.dromara.warm.flow.core.entity.User; + +import java.util.List; + +/** + * 流程用户Mapper接口 + * + * @author xiarg + * @since 2024/5/10 11:15 + */ +public interface FlowUserDao extends WarmDao { + + /** + * 根据taskId删除 + * + * @param taskIdList 待办任务主键集合 + * @return 结果 + * @author xiarg + * @since 2024/5/10 11:19 + */ + int deleteByTaskIds(List taskIdList); + + /** + * 根据(待办任务,实例,历史表,节点等)id查询权限人或者处理人 + * + * @param associatedList (待办任务,实例,历史表,节点等)id集合 + * @param types 用户表类型 + * @return 查询结果 + */ + List listByAssociatedAndTypes(List associatedList, String[] types); + + /** + * 根据办理人查询 + * + * @param associated 待办任务id + * @param processedBys 办理人id集合 + * @param types 用户表类型 + * @return 查询结果 + */ + List listByProcessedBys(Long associated, List processedBys, String[] types); +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/orm/dao/WarmDao.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/orm/dao/WarmDao.java new file mode 100644 index 0000000..de5f253 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/orm/dao/WarmDao.java @@ -0,0 +1,129 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.orm.dao; + +import org.dromara.warm.flow.core.orm.agent.WarmQuery; +import org.dromara.warm.flow.core.utils.page.Page; + +import java.io.Serializable; +import java.util.Collection; +import java.util.List; + +/** + * BaseMapper接口 + * + * @author warm + * @since 2023-03-17 + */ +public interface WarmDao { + + T newEntity(); + + /** + * 根据id查询 + * + * @param id 主键 + * @return 实体 + */ + T selectById(Serializable id); + + /** + * 根据ids查询 + * + * @param ids 主键 + * @return 实体 + */ + List selectByIds(Collection ids); + + /** + * 分页查询 + * + * @param entity 实体列表 + * @return 集合 + */ + Page selectPage(T entity, Page page); + + /** + * 分页查询 + * + * @param entity 实体列表 + * @param query query + * @return 集合 + */ + List selectList(T entity, WarmQuery query); + + /** + * 查询数量 + * + * @param entity 实体列表 + * @return 集合 + */ + long selectCount(T entity); + + /** + * 新增 + * + * @param entity 实体 + * @return 结果 + */ + int save(T entity); + + /** + * 根据id修改 + * + * @param entity 实体 + * @return 结果 + */ + int updateById(T entity); + + /** + * 根据entity删除 + * + * @param entity 实体 + * @return 结果 + */ + int delete(T entity); + + /** + * 根据id删除 + * + * @param id 主键 + * @return 结果 + */ + int deleteById(Serializable id); + + /** + * 根据ids批量删除 + * + * @param ids 需要删除的数据主键集合 + * @return 结果 + */ + int deleteByIds(Collection ids); + + /** + * 批量新增 + * + * @param list 集合 + */ + void saveBatch(List list); + + /** + * 批量修改 + * + * @param list 集合 + */ + void updateBatch(List list); +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/orm/service/IWarmService.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/orm/service/IWarmService.java new file mode 100644 index 0000000..11309d6 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/orm/service/IWarmService.java @@ -0,0 +1,209 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.orm.service; + + +import org.dromara.warm.flow.core.orm.agent.WarmQuery; +import org.dromara.warm.flow.core.orm.dao.WarmDao; +import org.dromara.warm.flow.core.utils.page.Page; + +import java.io.Serializable; +import java.util.Collection; +import java.util.List; + +/** + * Service接口 + * + * @author warm + * @since 2023-03-17 + */ +public interface IWarmService { + + > M getDao(); + + /** + * 根据id查询 + * + * @param id 主键 + * @return 实体 + */ + T getById(Serializable id); + + /** + * 根据ids查询 + * + * @param ids 主键 + * @return 实体 + */ + List getByIds(Collection ids); + + /** + * 分页查询 + * + * @param entity 查询实体 + * @param page 分页对象 + * @return 集合 + */ + Page page(T entity, Page page); + + /** + * 查询列表 + * + * @param entity 查询实体 + * @return 集合 + */ + List list(T entity); + + /** + * 查询列表,可排序 + * + * @param entity 查询实体 + * @param query + * @return 集合 + */ + List list(T entity, WarmQuery query); + + /** + * 查询一条记录 + * + * @param entity 查询实体 + * @return 结果 + */ + T getOne(T entity); + + /** + * 获取总数量 + * + * @param entity 查询实体 + * @return 结果 + */ + long selectCount(T entity); + + /** + * 判断是否存在 + * + * @param entity 查询实体 + * @return 结果 + */ + Boolean exists(T entity); + + /** + * 新增 + * + * @param entity 实体 + * @return 结果 + */ + boolean save(T entity); + + /** + * 根据id修改 + * + * @param entity 实体 + * @return 结果 + */ + boolean updateById(T entity); + + /** + * 根据id删除 + * + * @param id 主键 + * @return 结果 + */ + boolean removeById(Serializable id); + + /** + * 根据entity删除 + * + * @param entity 实体 + * @return 结果 + */ + boolean remove(T entity); + + /** + * 根据ids批量删除 + * + * @param ids 需要删除的数据主键集合 + * @return 结果 + */ + boolean removeByIds(Collection ids); + + /** + * 批量新增 + * + * @param list 实体集合 + */ + void saveBatch(List list); + + /** + * 批量新增 + * + * @param list 需要插入的集合数据 + * @param batchSize 插入大小 + */ + void saveBatch(List list, int batchSize); + + /** + * 批量更新 + * + * @param list 集合数据 + */ + void updateBatch(List list); + + /** + * id设置正序排列 + * + * @return 集合 + */ + WarmQuery orderById(); + + /** + * 创建时间设置正序排列 + * + * @return 集合 + */ + WarmQuery orderByCreateTime(); + + /** + * 更新时间设置正序排列 + * + * @return 集合 + */ + WarmQuery orderByUpdateTime(); + + /** + * 设置正序排列 + * + * @param orderByField 排序字段 + * @return 集合 + */ + WarmQuery orderByAsc(String orderByField); + + /** + * 设置倒序排列 + * + * @param orderByField 排序字段 + * @return 集合 + */ + WarmQuery orderByDesc(String orderByField); + + /** + * 用户自定义排序方案 + * + * @param orderByField 排序字段 + * @return 集合 + */ + WarmQuery orderBy(String orderByField); +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/orm/service/impl/WarmServiceImpl.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/orm/service/impl/WarmServiceImpl.java new file mode 100644 index 0000000..6b2d0a4 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/orm/service/impl/WarmServiceImpl.java @@ -0,0 +1,197 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.orm.service.impl; + + +import org.dromara.warm.flow.core.FlowEngine; +import org.dromara.warm.flow.core.handler.DataFillHandler; +import org.dromara.warm.flow.core.orm.agent.WarmQuery; +import org.dromara.warm.flow.core.orm.dao.WarmDao; +import org.dromara.warm.flow.core.orm.service.IWarmService; +import org.dromara.warm.flow.core.utils.CollUtil; +import org.dromara.warm.flow.core.utils.ObjectUtil; +import org.dromara.warm.flow.core.utils.SqlHelper; +import org.dromara.warm.flow.core.utils.page.Page; + +import java.io.Serializable; +import java.util.Collection; +import java.util.List; + +/** + * BaseService层处理 + * + * @author warm + * @since 2023-03-17 + */ +public abstract class WarmServiceImpl, T> implements IWarmService { + + protected M warmDao; + + @Override + public M getDao() { + return warmDao; + } + + protected abstract IWarmService setDao(M warmDao); + + @Override + public T getById(Serializable id) { + return getDao().selectById(id); + } + + @Override + public List getByIds(Collection ids) { + return getDao().selectByIds(ids); + } + + @Override + public Page page(T entity, Page page) { + return getDao().selectPage(entity, page); + } + + @Override + public List list(T entity) { + return getDao().selectList(entity, null); + } + + @Override + public List list(T entity, WarmQuery query) { + return getDao().selectList(entity, query); + } + + @Override + public T getOne(T entity) { + List list = getDao().selectList(entity, null); + return CollUtil.getOne(list); + } + + @Override + public long selectCount(T entity) { + return getDao().selectCount(entity); + } + + @Override + public Boolean exists(T entity) { + long count = selectCount(entity); + return count > 0; + } + + @Override + public boolean save(T entity) { + insertFill(entity); + return SqlHelper.retBool(getDao().save(entity)); + } + + @Override + public boolean updateById(T entity) { + updateFill(entity); + return SqlHelper.retBool(getDao().updateById(entity)); + } + + @Override + public boolean removeById(Serializable id) { + return SqlHelper.retBool(getDao().deleteById(id)); + } + + @Override + public boolean remove(T entity) { + return SqlHelper.retBool(getDao().delete(entity)); + } + + @Override + public boolean removeByIds(Collection ids) { + return SqlHelper.retBool(getDao().deleteByIds(ids)); + } + + @Override + public void saveBatch(List list) { + if (CollUtil.isEmpty(list)) { + return; + } + this.saveBatch(list, 1000); + } + + @Override + public void saveBatch(List list, int batchSize) { + if (CollUtil.isEmpty(list)) { + return; + } + + List> split = CollUtil.split(list, batchSize > 0 ? batchSize : 1000); + + for (List ts : split) { + ts.forEach(this::insertFill); + getDao().saveBatch(ts); + } + } + + @Override + public void updateBatch(List list) { + if (CollUtil.isEmpty(list)) { + return; + } + list.forEach(this::updateFill); + getDao().updateBatch(list); + } + + @Override + public WarmQuery orderById() { + return new WarmQuery<>(this).orderById(); + } + + @Override + public WarmQuery orderByCreateTime() { + return new WarmQuery<>(this).orderByCreateTime(); + } + + @Override + public WarmQuery orderByUpdateTime() { + return new WarmQuery<>(this).orderByUpdateTime(); + } + + @Override + public WarmQuery orderByAsc(String orderByField) { + return new WarmQuery<>(this).orderByAsc(orderByField); + } + + @Override + public WarmQuery orderByDesc(String orderByField) { + return new WarmQuery<>(this).orderByDesc(orderByField); + } + + @Override + public WarmQuery orderBy(String orderByField) { + return new WarmQuery<>(this).orderBy(orderByField); + } + + + public void insertFill(T entity) { + DataFillHandler dataFillHandler = FlowEngine.dataFillHandler(); + if (dataFillHandler == null) { + return; + } + dataFillHandler.idFill(entity); + dataFillHandler.insertFill(entity); + } + + public void updateFill(T entity) { + DataFillHandler dataFillHandler = FlowEngine.dataFillHandler(); + if (dataFillHandler == null) { + return; + } + dataFillHandler.updateFill(entity); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/ChartService.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/ChartService.java new file mode 100644 index 0000000..cb5befc --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/ChartService.java @@ -0,0 +1,53 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.service; + +import org.dromara.warm.flow.core.dto.PathWayData; + +import java.util.List; + +/** + * 流程图绘制Service接口 + * + * @author warm + * @since 2024-12-30 + */ +public interface ChartService { + + + /** + * 获取流程开启时的流程图元数据 + * + * @param pathWayData 办理过程中途径数据,用于渲染流程图 + * @return 流程图元数据json + */ + String startMetadata(PathWayData pathWayData); + + /** + * 获取流程运行时的流程图元数据 + * + * @param pathWayData 办理过程中途径数据,用于渲染流程图 + * @return 流程图元数据json + */ + String skipMetadata(PathWayData pathWayData); + + /** + * 获取流程图三原色 + * + * @param modelValue 流程模型 + */ + List getChartRgb(String modelValue); +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/DefService.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/DefService.java new file mode 100644 index 0000000..bace248 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/DefService.java @@ -0,0 +1,209 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.service; + +import org.dromara.warm.flow.core.dto.DefJson; +import org.dromara.warm.flow.core.dto.FlowCombine; +import org.dromara.warm.flow.core.entity.Definition; +import org.dromara.warm.flow.core.entity.Node; +import org.dromara.warm.flow.core.entity.Skip; +import org.dromara.warm.flow.core.orm.service.IWarmService; + +import java.io.InputStream; +import java.util.List; + +/** + * 流程定义Service接口 + * + * @author warm + * @since 2023-03-29 + */ +public interface DefService extends IWarmService { + + /** + * 导入流程定义、流程节点和流程跳转数据 + * + * @param is 流程定义的输入流 + */ + Definition importIs(InputStream is); + + /** + * 导入流程定义、流程节点和流程跳转数据 + * + * @param defStr 流程定义的json字符串 {@link DefJson 的json格式} + */ + Definition importJson(String defStr); + + /** + * 导入流程定义、流程节点和流程跳转数据 + * + * @param defJson 流程定义json对象,流程定义、流程节点和流程跳转按照主子集传递 + */ + Definition importDef(DefJson defJson); + + /** + * 新增工作流定义,并初始化流程节点和流程跳转数据 + * + * @param definition 流程定义 + * @param nodeList 流程节点 + * @param skipList 流程跳转 + */ + Definition insertFlow(Definition definition, List nodeList, List skipList); + + /** + * 只新增流程定义表数据 + * + * @param definition 流程定义对象 + * @return boolean + */ + boolean checkAndSave(Definition definition); + + /** + * 保存流程节点和跳转 + * + * @param defJson 流程定义json对象 + * @param onlyNodeSkip 是否只保存节点和跳转 + * @author xiarg + * @since 2024/10/29 16:30 + */ + void saveDef(DefJson defJson, boolean onlyNodeSkip) throws Exception; + + /** + * 导出流程定义(流程定义、流程节点和流程跳转数据)的json字符串 + * + * @param id 流程定义id + * @return json字符串 + */ + String exportJson(Long id); + + /** + * 获取流程定义全部数据(包含节点和跳转) + * + * @param id 流程定义id + * @return Definition + */ + Definition getAllDataDefinition(Long id); + + /** + * 流程数据集合 + * + * @param id 流程定义id + * @return FlowCombine + */ + FlowCombine getFlowCombine(Long id); + + /** + * 流程数据集合不包含流程定义 + * + * @param id 流程定义id + * @return FlowCombine + */ + FlowCombine getFlowCombineNoDef(Long id); + + /** + * 流程数据集合 + * + * @param definition 流程定义 + * @return FlowCombine + */ + FlowCombine getFlowCombine(Definition definition); + + /** + * 查询流程设计所需的数据,比如流程图渲染 + * + * @param id 流程定义id + * @return 流程定义json对象 + */ + DefJson queryDesign(Long id); + + /** + * 根据流程定义code列表查询流程定义 + * + * @param flowCodeList 流程定义code列表 + * @return List + */ + List queryByCodeList(List flowCodeList); + + /** + * 更新流程定义发布状态 + * + * @param defIds 流程定义id列表 + * @param publishStatus 流程定义发布状态 + */ + void updatePublishStatus(List defIds, Integer publishStatus); + + /** + * 删除流程定义相关数据 + * + * @param ids 流程定义id列表 + * @return boolean + */ + boolean removeDef(List ids); + + /** + * 发布流程定义 + * + * @param id 流程定义id + * @return boolean + */ + boolean publish(Long id); + + /** + * 取消发布流程定义 + * + * @param id 流程定义id + * @return boolean + */ + boolean unPublish(Long id); + + /** + * 复制流程定义 + * + * @param id 流程定义id + * @return boolean + */ + boolean copyDef(Long id); + + /** + * 激活流程 + * + * @param id 流程定义id + */ + boolean active(Long id); + + /** + * 挂起流程:流程定义挂起后,相关的流程实例都无法继续流转 + * + * @param id 流程定义id + */ + boolean unActive(Long id); + + /** + * 根据流程定义code查询流程定义 + * + * @param flowCode 流程定义code + * @return List + */ + List getByFlowCode(String flowCode); + + /** + * 根据流程定义code查询已发布的流程定义 + * + * @param flowCode 流程定义code + * @return Definition + */ + Definition getPublishByFlowCode(String flowCode); +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/FormService.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/FormService.java new file mode 100644 index 0000000..df47c5a --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/FormService.java @@ -0,0 +1,98 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.service; + +import org.dromara.warm.flow.core.entity.Form; +import org.dromara.warm.flow.core.orm.service.IWarmService; +import org.dromara.warm.flow.core.utils.page.Page; + +/** + * 流程表单Service接口 + * + * @author vanlin + * @since 2024/8/19 10:06 + */ +public interface FormService extends IWarmService { + + /** + * 保存流程表单 + * + * @param form form + * @return 保存情况 + */ + @Override + boolean save(Form form); + + /** + * 发布流程表单 + * + * @param id id + * @return 发布结果 + */ + boolean publish(Long id); + + + /** + * 取消发布流程表单 + * + * @param id id + * @return 取消发布 + */ + boolean unPublish(Long id); + + /** + * 复制流程表单 + * + * @param id id + * @return 复制表单结果 + */ + boolean copyForm(Long id); + + /** + * 读取流程表单 + * + * @param formCode 表单编码 + * @param formVersion 版本 + * @return 表单信息 + */ + Form getByCode(String formCode, String formVersion); + + /** + * + * @param id id + * @return 表单信息 + */ + Form getById(Long id); + + /** + * 已发布表单 + * + * @param formName 表单名 + * @param pageNum 页码 + * @param pageSize 每页记录 + * @return 已发布记录 + */ + Page publishedPage(String formName, Integer pageNum, Integer pageSize); + + /** + * 保存表单内容 + * + * @param id id + * @param formContent 表单内容 + * @return 保存结果 + */ + boolean saveContent(Long id, String formContent); +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/HisTaskService.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/HisTaskService.java new file mode 100644 index 0000000..798fabc --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/HisTaskService.java @@ -0,0 +1,134 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.service; + +import org.dromara.warm.flow.core.dto.FlowParams; +import org.dromara.warm.flow.core.entity.HisTask; +import org.dromara.warm.flow.core.entity.Node; +import org.dromara.warm.flow.core.entity.Task; +import org.dromara.warm.flow.core.entity.User; +import org.dromara.warm.flow.core.orm.service.IWarmService; + +import java.math.BigDecimal; +import java.util.List; + +/** + * 历史任务记录Service接口 + * + * @author warm + * @since 2023-03-29 + */ +public interface HisTaskService extends IWarmService { + + List listByTaskId(Long taskId); + + /** + * 根据任务id和协作类型查询 + * + * @param taskId 任务id + * @param cooperateTypes 协作类型集合 + * @return List + */ + List listByTaskIdAndCooperateTypes(Long taskId, Integer... cooperateTypes); + + /** + * 根据实例Id和节点编码查询 + * + * @param instanceId 流程实例id + * @param nodeCodes 节点编码集合 + * @return List + */ + List getByInsAndNodeCodes(Long instanceId, List nodeCodes); + + /** + * 根据instanceIds删除 + * + * @param instanceIds 流程实例id集合 + * @return boolean + */ + boolean deleteByInsIds(List instanceIds); + + /** + * 设置流程历史任务信息 + * + * @param task 当前任务 + * @param nextNodes 后续任务 + * @param flowParams 参数 + */ + HisTask setSkipInsHis(Task task, List nextNodes, FlowParams flowParams); + + /** + * 设置流程历史任务信息 + * + * @param taskList 当前任务集合 + * @param nextNodes 后续任务 + * @param flowParams 参数 + */ + List setSkipHisList(List taskList, List nextNodes, FlowParams flowParams); + + /** + * 设置协作历史记录 + * + * @param task 当前任务 + * @param node 当然任务节点 + * @param flowParams 参数 + * @param collaborators 协作人 + */ + HisTask setCooperateHis(Task task, Node node, FlowParams flowParams + , List collaborators); + + /** + * 委派历史任务 + * + * @param task 当前任务 + * @param flowParams 参数 + * @param entrustedUser 委托人 + * @return HisTask 历史任务 + */ + HisTask setDeputeHisTask(Task task, FlowParams flowParams, User entrustedUser); + + /** + * 设置会签票签历史任务 + * + * @param task 当前任务 + * @param flowParams 参数 + * @param nodeRatio 节点比率 + * @param isPass 是否通过 + * @return HisTask 历史任务 + */ + HisTask setSignHisTask(Task task, FlowParams flowParams, String nodeRatio, boolean isPass); + + /** + * 设置流程历史任务信息 + * + * @param task 当前任务 + * @param nextNode 跳转的节点 + * @param flowParams 流程参数 + * @return HisTask 历史任务 + * @author xiarg + * @since 2024/9/30 11:59 + */ + HisTask setSkipHisTask(Task task, Node nextNode, FlowParams flowParams); + + /** + * 根据流程实例id查询历史任务 + * + * @param instanceId 流程实例id + * @return 历史记录集合 + */ + List getByInsId(Long instanceId); + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/InsService.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/InsService.java new file mode 100644 index 0000000..aae0c34 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/InsService.java @@ -0,0 +1,93 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.service; + +import org.dromara.warm.flow.core.dto.FlowParams; +import org.dromara.warm.flow.core.entity.Instance; +import org.dromara.warm.flow.core.orm.service.IWarmService; + +import java.util.List; + +/** + * 流程实例Service接口 + * + * @author warm + * @since 2023-03-29 + */ +public interface InsService extends IWarmService { + + /** + * 传入业务id开启流程 + * + * @param businessId: 业务id[必传] + * @param flowParams: 包含流程相关参数的对象 + * - flowCode: 流程编码 [必传] + * - handler: 当前办理人唯一标识[必传] + * - variable: 流程变量[按需传输] + * - nextHandler: 执行的下个任务的办理人[按需传输] + * - nextHandlerAppend: 个任务处理人配置类型(true-追加,false-覆盖,默认false)[按需传输] + * - flowStatus: 流程状态,自定义流程状态[按需传输] + * - ext: 扩展字段,预留给业务系统使用[按需传输] + * @return 流程实例 + */ + Instance start(String businessId, FlowParams flowParams); + + /** + * 根据实例ids,删除流程 + * + * @param instanceIds: 流程实例集合 + */ + boolean remove(List instanceIds); + + /** + * 根据流程定义id,查询流程实例集合 + * + * @param definitionId 流程定义id + * @return List + */ + List getByDefId(Long definitionId); + + /** + * 激活实例 + * + * @param id 流程实例id + */ + boolean active(Long id); + + /** + * 挂起实例,流程实例挂起后,该流程实例无法继续流转 + * + * @param id 流程实例id + */ + boolean unActive(Long id); + + /** + * 根据流程定义id集合,查询流程实例集合 + * + * @param defIds 流程定义id集合 + * @return 流程实例集合 + */ + List listByDefIds(List defIds); + + + /** + * 按照流程变量key删除流程变量 + * + * @param instanceId 流程实例id + * @param keys 流程变量key + */ + void removeVariables(Long instanceId, String... keys); +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/NodeService.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/NodeService.java new file mode 100644 index 0000000..245f349 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/NodeService.java @@ -0,0 +1,221 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.service; + +import org.dromara.warm.flow.core.dto.FlowCombine; +import org.dromara.warm.flow.core.dto.PathWayData; +import org.dromara.warm.flow.core.entity.Node; +import org.dromara.warm.flow.core.orm.service.IWarmService; + +import java.io.Serializable; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * 流程节点Service接口 + * + * @author warm + * @since 2023-03-29 + */ +public interface NodeService extends IWarmService { + + /** + * 根据流程编码获取已发布流程节点集合 + * + * @param flowCode 流程编码 + * @return List + */ + List getPublishByFlowCode(String flowCode); + + /** + * 根据流程编码获取开启的唯一流程的流程节点集合 + * + * @param nodeCodes 流程节点code集合 + * @return List + */ + List getByNodeCodes(List nodeCodes, Long definitionId); + + /** + * 根据节点id获取所有的前置节点集合 + * + * @param nodeId 节点id + * @return 所有的前置节点集合 + */ + List previousNodeList(Long nodeId); + + /** + * 根据流程定义id和当前节点code获取所有的前置节点集合 + * + * @param definitionId 程定义id + * @param nowNodeCode 当前节点code + * @return 所有的前置节点集合 + */ + List previousNodeList(Long definitionId, String nowNodeCode); + + /** + * 根据节点id获取所有的后置节点集合 + * + * @param nodeId 节点id + * @return 所有的后置节点集合 + */ + List suffixNodeList(Long nodeId); + + /** + * 根据流程定义id和当前节点code获取所有的后置节点集合 + * + * @param definitionId 程定义id + * @param nowNodeCode 当前节点code + * @return 所有的后置点集合 + */ + List suffixNodeList(Long definitionId, String nowNodeCode); + + /** + * 流程数据集合和当前节点code获取所有的后置节点集合 + * + * @param nowNodeCode 当前节点code + * @param flowCombine 流程数据集合 + * @return 所有的后置点集合 + */ + List suffixNodeList(String nowNodeCode, FlowCombine flowCombine); + + /** + * 根据流程定义id获取流程节点集合 + * + * @param definitionId 流程定义id + * @return 所有的节点集合 + */ + List getByDefId(Long definitionId); + + /** + * 根据流程定义id和节点编码获取流程节点 + * + * @param definitionId 流程定义id + * @return 节点 + */ + Node getByDefIdAndNodeCode(Long definitionId, String nodeCode); + + /** + * 根据流程定义id获取开始节点 + * + * @param definitionId 流程定义id + * @return Node + */ + Node getStartNode(Long definitionId); + + /** + * 根据流程定义id获取中间节点集合 + * + * @param definitionId 流程定义id + * @return List + */ + List getBetweenNode(Long definitionId); + + /** + * 根据流程定义id和流程变量获取第一个中间节点 + * + * @param definitionId 流程定义id + * @param variable 流程变量 + * @return Node + */ + List getFirstBetweenNode(Long definitionId, Map variable); + + /** + * 根据流程定义id获取结束节点 + * + * @param definitionId 流程定义id + * @return Node + */ + Node getEndNode(Long definitionId); + + /** + * 根据流程定义和当前节点code获取下一节点集合,如是网关跳过取下一节点,并行网关返回多个节点 + * 不一定是后置节点,如果是通过就是后置,如果是驳回就取前置节点 + * + * @param definitionId 流程定义id + * @param nowNodeCode 当前节点code + * @param anyNodeCode anyNodeCode不为空,则可跳转anyNodeCode节点(优先级最高) + * @param skipType 跳转类型(PASS审批通过 REJECT退回) + * @param variable 流程变量,下一个节点是网关需要判断跳转条件,并行网关返回多个节点 + * @return List + * @author xiarg + * @since 2024/8/21 16:48 + */ + List getNextNodeList(Long definitionId, String nowNodeCode, String anyNodeCode, String skipType, + Map variable); + + /** + * 根据当前节点获取下一节点 + * 不一定是后置节点,如果是通过就是后置,如果是驳回就取前置节点 + * + * @param definitionId 流程定义id + * @param nowNodeCode 当前节点code + * @param anyNodeCode anyNodeCode不为空,则可跳转anyNodeCode节点(优先级最高) + * @param skipType 跳转类型(PASS审批通过 REJECT退回) + * @return Node + */ + Node getNextNode(Long definitionId, String nowNodeCode, String anyNodeCode, String skipType); + + + /** + * 当前节点获取下一节点集合,如是网关跳过取下一节点,并行网关返回多个节点 + * 不一定是后置节点,如果是通过就是后置,如果是驳回就取前置节点 + * + * @param nowNode 当前节点 + * @param anyNodeCode anyNodeCode不为空,则可跳转anyNodeCode节点(优先级最高) + * @param skipType 跳转类型(PASS审批通过 REJECT退回) + * @param variable 流程变量,下一个节点是网关需要判断跳转条件,并行网关返回多个节点 + * @param pathWayData 办理过程中途径数据,用于渲染流程图 + * @param flowCombine 流程数据集合 + * @return List + */ + List getNextNodeList(Node nowNode, String anyNodeCode, String skipType, Map variable, + PathWayData pathWayData, FlowCombine flowCombine); + + /** + * 根据当前节点获取下一节点 + * 不一定是后置节点,如果是通过就是后置,如果是驳回就取前置节点 + * + * @param nowNode 当前节点 + * @param anyNodeCode anyNodeCode不为空,则可跳转anyNodeCode节点(优先级最高) + * @param skipType 跳转类型(PASS审批通过 REJECT退回) + * @param pathWayData 办理过程中途径数据,用于渲染流程图 + * @param flowCombine 流程数据集合 + * @return Node + */ + Node getNextNode(Node nowNode, String anyNodeCode, String skipType, PathWayData pathWayData, FlowCombine flowCombine); + + /** + * 校验是否网关节点,如果是重新获取新的后面的节点 + * + * @param variable 流程变量 + * @param nextNode 下一个节点 + * @param pathWayData 办理过程中途径数据,用于渲染流程图 + * @param flowCombine 流程数据集合 + * @return List + */ + List getNextByCheckGateway(Map variable, Node nextNode, PathWayData pathWayData + , FlowCombine flowCombine); + + /** + * 批量删除流程节点 + * + * @param defIds 需要删除的数据主键集合 + * @return 结果 + */ + int deleteNodeByDefIds(Collection defIds); + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/SkipService.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/SkipService.java new file mode 100644 index 0000000..22ab488 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/SkipService.java @@ -0,0 +1,57 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.service; + +import org.dromara.warm.flow.core.entity.Skip; +import org.dromara.warm.flow.core.orm.service.IWarmService; + +import java.io.Serializable; +import java.util.Collection; +import java.util.List; + +/** + * 节点跳转关联Service接口 + * + * @author warm + * @since 2023-03-29 + */ +public interface SkipService extends IWarmService { + + /** + * 批量删除节点跳转关联 + * + * @param defIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteSkipByDefIds(Collection defIds); + + /** + * 根据流程定义id查询节点跳转线 + * + * @param definitionId 流程定义id + * @return List + */ + List getByDefId(Long definitionId); + + /** + * 根据流程定义id和节点编码查询节点跳转线 + * + * @param definitionId 流程定义id + * @param nowNodeCode 其实节点编码 + * @return List + */ + List getByDefIdAndNowNodeCode(Long definitionId, String nowNodeCode); +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/TaskService.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/TaskService.java new file mode 100644 index 0000000..f1e084c --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/TaskService.java @@ -0,0 +1,488 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.service; + +import org.dromara.warm.flow.core.dto.FlowDto; +import org.dromara.warm.flow.core.dto.FlowParams; +import org.dromara.warm.flow.core.entity.Definition; +import org.dromara.warm.flow.core.entity.Instance; +import org.dromara.warm.flow.core.entity.Node; +import org.dromara.warm.flow.core.entity.Task; +import org.dromara.warm.flow.core.handler.PermissionHandler; +import org.dromara.warm.flow.core.orm.service.IWarmService; + +import java.util.List; +import java.util.Map; + +/** + * 待办任务Service接口 + * + * @author warm + * @since 2023-03-29 + */ +public interface TaskService extends IWarmService { + + /** + * 流程通过 + * 使用前提是实现,{@link PermissionHandler#permissions()}和{@link PermissionHandler#getHandler()} + * 工作流内部会获取办理人权限标识(permissionFlag)和办理人唯一标识(handler) + * + * @param taskId 流程任务id[必传] + * @param message 审批意见 [按需传输] + * @param variable 流程变量 [按需传输] + */ + Instance pass(Long taskId, String message, Map variable); + + /** + * 流程任意通过 + * 使用前提是实现,{@link PermissionHandler#permissions()}和{@link PermissionHandler#getHandler()} + * 工作流内部会获取办理人权限标识(permissionFlag)和办理人唯一标识(handler) + * + * @param taskId 流程任务id[必传] + * @param nodeCode 如果指定节点,可[任意跳转]到对应节点 [必传] + * @param message 审批意见 [按需传输] + * @param variable 流程变量 [按需传输] + */ + Instance passAtWill(Long taskId, String nodeCode, String message, Map variable); + + + /** + * 流程通过,并且自定义流程状态 + * 使用前提是实现,{@link PermissionHandler#permissions()}和{@link PermissionHandler#getHandler()} + * 工作流内部会获取办理人权限标识(permissionFlag)和办理人唯一标识(handler) + * + * @param taskId 流程任务id[必传] + * @param message 审批意见 [按需传输] + * @param variable 流程变量 [按需传输] + * @param flowStatus 流程状态,自定义流程状态 [按需传输] + * @param hisStatus 历史任务表状态,自定义流程状态 [按需传输] + */ + Instance pass(Long taskId, String message, Map variable, String flowStatus, String hisStatus); + + /** + * 流程任意通过,并且自定义流程状态 + * 使用前提是实现,{@link PermissionHandler#permissions()}和{@link PermissionHandler#getHandler()} + * 工作流内部会获取办理人权限标识(permissionFlag)和办理人唯一标识(handler) + * + * @param taskId 流程任务id[必传] + * @param nodeCode 如果指定节点,可[任意跳转]到对应节点 [必传] + * @param message 审批意见 [按需传输] + * @param variable 流程变量 [按需传输] + * @param flowStatus 流程状态,自定义流程状态 [按需传输] + * @param hisStatus 历史任务表状态,自定义流程状态 [按需传输] + */ + Instance passAtWill(Long taskId, String nodeCode, String message, Map variable, String flowStatus + , String hisStatus); + + /** + * 流程退回 + * 使用前提是实现,{@link PermissionHandler#permissions()}和{@link PermissionHandler#getHandler()} + * 工作流内部会获取办理人权限标识(permissionFlag)和办理人唯一标识(handler) + * + * @param taskId 流程任务id[必传] + * @param message 审批意见 [按需传输] + * @param variable 流程变量 [按需传输] + */ + Instance reject(Long taskId, String message, Map variable); + + /** + * 流程任意退回 + * 使用前提是实现,{@link PermissionHandler#permissions()}和{@link PermissionHandler#getHandler()} + * 工作流内部会获取办理人权限标识(permissionFlag)和办理人唯一标识(handler) + * + * @param taskId 流程任务id[必传] + * @param nodeCode 如果指定节点,可[任意跳转]到对应节点,严禁任意退回选择后置节点 [必传] + * @param message 审批意见 [按需传输] + * @param variable 流程变量 [按需传输] + */ + Instance rejectAtWill(Long taskId, String nodeCode, String message, Map variable); + + /** + * 流程退回,并且自定义流程状态 + * 使用前提是实现,{@link PermissionHandler#permissions()}和{@link PermissionHandler#getHandler()} + * 工作流内部会获取办理人权限标识(permissionFlag)和办理人唯一标识(handler) + * + * @param taskId 流程任务id[必传] + * @param message 审批意见 [按需传输] + * @param variable 流程变量 [按需传输] + * @param flowStatus 流程状态,自定义流程状态 [按需传输] + * @param hisStatus 历史任务表状态,自定义流程状态 [按需传输] + */ + Instance reject(Long taskId, String message, Map variable, String flowStatus, String hisStatus); + + /** + * 流程任意退回,并且自定义流程状态 + * 使用前提是实现,{@link PermissionHandler#permissions()}和{@link PermissionHandler#getHandler()} + * 工作流内部会获取办理人权限标识(permissionFlag)和办理人唯一标识(handler) + * + * @param taskId 流程任务id[必传] + * @param nodeCode 如果指定节点,可[任意跳转]到对应节点,严禁任意退回选择后置节点 [必传] + * @param message 审批意见 [按需传输] + * @param variable 流程变量 [按需传输] + * @param flowStatus 流程状态,自定义流程状态 [按需传输] + * @param hisStatus 历史任务表状态,自定义流程状态 [按需传输] + */ + Instance rejectAtWill(Long taskId, String nodeCode, String message, Map variable, String flowStatus, String hisStatus); + + + /** + * 根据任务id,流程跳转 + * + * @param taskId 流程任务id[必传] + * @param flowParams 包含流程相关参 + * - skipType 跳转类型(PASS审批通过 REJECT退回) [必传] + * - nodeCode 如果指定节点,可[任意跳转]到对应节点,严禁任意退回选择后置节点 [按需传输] + * - permissionFlag 办理人权限标识,比如用户,角色,部门等,用于校验是否有权限办理 [按需传输];满足以下任一情况可以不传 + * 流程设计时未设置办理人、ignore为true、实现了办理人权限处理器{@link PermissionHandler#permissions()} + * - message 审批意见 [按需传输] + * - handler 当前办理人唯一标识,如用户id,用于记录历史表 [按需传输];满足以下情况可以不传 + * 实现了办理人权限处理器{@link PermissionHandler#getHandler()} + * - variable 流程变量 [按需传输,跳转条件放入流程变量<互斥网关必传>] + * - nextHandler: 执行的下个任务的办理人[按需传输] + * - nextHandlerAppend: 个任务处理人配置类型(true-追加,false-覆盖,默认false)[按需传输] + * - flowStatus 流程状态,自定义流程状态 [按需传输] + * - ignore 忽略权限校验(比如管理员不校验),默认不忽略 [按需传输] + */ + Instance skip(Long taskId, FlowParams flowParams); + + /** + * 根据实例id,流程跳转,一般是开始节点后第一个节点,用来提交申请,此时不可有同时两个待办任务 + * + * @param instanceId 流程实例id[必传] + * @param flowParams 包含流程相关参数的对象 + * - skipType 跳转类型(PASS审批通过 REJECT退回) [必传] + * - nodeCode 如果指定节点,可[任意跳转]到对应节点,严禁任意退回选择后置节点 [按需传输] + * - permissionFlag 办理人权限标识,比如用户,角色,部门等,用于校验是否有权限办理 [按需传输];满足以下任一情况可以不传 + * 流程设计时未设置办理人、ignore为true、实现了办理人权限处理器{@link PermissionHandler#permissions()} + * - message 审批意见[按需传输] + * - handler 当前办理人唯一标识,如用户id,用于记录历史表 [按需传输];满足以下情况可以不传 + * 实现了办理人权限处理器{@link PermissionHandler#getHandler()} + * - variable 流程变量[按需传输,跳转条件放入流程变量<互斥网关必传>] + * - nextHandler: 执行的下个任务的办理人[按需传输] + * - nextHandlerAppend: 个任务处理人配置类型(true-追加,false-覆盖,默认false)[按需传输] + * - flowStatus 流程状态,自定义流程状态[按需传输] + * - ignore 忽略权限校验(比如管理员不校验),默认不忽略 [按需传输] + * @return 流程实例 + */ + Instance skipByInsId(Long instanceId, FlowParams flowParams); + + /** + * 驳回上一个任务 + * + * @param instanceId 流程实例id[必传] + * @param flowParams 包含流程相关参数的对象 + * - permissionFlag 办理人权限标识,比如用户,角色,部门等,用于校验是否有权限办理 [按需传输];满足以下任一情况可以不传 + * 流程设计时未设置办理人、ignore为true、实现了办理人权限处理器{@link PermissionHandler#permissions()} + * - message 审批意见 [按需传输] + * - handler 当前办理人唯一标识,如用户id,用于记录历史表 [按需传输];满足以下情况可以不传 + * 实现了办理人权限处理器{@link PermissionHandler#getHandler()} + * - variable 流程变量 [按需传输,跳转条件放入流程变量<互斥网关必传>] + * - nextHandler: 执行的下个任务的办理人[按需传输] + * - nextHandlerAppend: 个任务处理人配置类型(true-追加,false-覆盖,默认false)[按需传输] + * - flowStatus 流程状态,自定义流程状态 [按需传输] + * - ignore 忽略权限校验(比如管理员不校验),默认不忽略 [按需传输] + */ + Instance rejectLastByInsId(Long instanceId, FlowParams flowParams); + + /** + * 驳回上一个任务 + * + * @param taskId 流程任务id[必传] + * @param flowParams 包含流程相关参数的对象 + * - permissionFlag 办理人权限标识,比如用户,角色,部门等,用于校验是否有权限办理 [按需传输];满足以下任一情况可以不传 + * 流程设计时未设置办理人、ignore为true、实现了办理人权限处理器{@link PermissionHandler#permissions()} + * - message 审批意见 [按需传输] + * - handler 当前办理人唯一标识,如用户id,用于记录历史表 [按需传输];满足以下情况可以不传 + * 实现了办理人权限处理器{@link PermissionHandler#getHandler()} + * - variable 流程变量 [按需传输,跳转条件放入流程变量<互斥网关必传>] + * - nextHandler: 执行的下个任务的办理人[按需传输] + * - nextHandlerAppend: 个任务处理人配置类型(true-追加,false-覆盖,默认false)[按需传输] + * - flowStatus 流程状态,自定义流程状态 [按需传输] + * - ignore 忽略权限校验(比如管理员不校验),默认不忽略 [按需传输] + */ + Instance rejectLast(Long taskId, FlowParams flowParams); + + /** + * 驳回上一个任务 + * + * @param task 流程任务[必传] + * @param flowParams 包含流程相关参数的对象 + * - permissionFlag 办理人权限标识,比如用户,角色,部门等,用于校验是否有权限办理 [按需传输];满足以下任一情况可以不传 + * 流程设计时未设置办理人、ignore为true、实现了办理人权限处理器{@link PermissionHandler#permissions()} + * - message 审批意见 [按需传输] + * - handler 当前办理人唯一标识,如用户id,用于记录历史表 [按需传输];满足以下情况可以不传 + * 实现了办理人权限处理器{@link PermissionHandler#getHandler()} + * - variable 流程变量 [按需传输,跳转条件放入流程变量<互斥网关必传>] + * - nextHandler: 执行的下个任务的办理人[按需传输] + * - nextHandlerAppend: 个任务处理人配置类型(true-追加,false-覆盖,默认false)[按需传输] + * - flowStatus 流程状态,自定义流程状态 [按需传输] + * - ignore 忽略权限校验(比如管理员不校验),默认不忽略 [按需传输] + */ + Instance rejectLast(Task task, FlowParams flowParams); + + /** + * 拿回到最近办理的任务 + * + * @param instanceId 流程实例id[必传] + * @param flowParams 包含流程相关参数的对象 + * - message 审批意见 [按需传输] + * - handler 当前办理人唯一标识,如用户id,用于记录历史表 [按需传输];满足以下情况可以不传 + * 实现了办理人权限处理器{@link PermissionHandler#getHandler()} + * - variable 流程变量 [按需传输,跳转条件放入流程变量<互斥网关必传>] + * - nextHandler: 执行的下个任务的办理人[按需传输] + * - nextHandlerAppend: 个任务处理人配置类型(true-追加,false-覆盖,默认false)[按需传输] + * - flowStatus 流程状态,自定义流程状态 [按需传输] + */ + Instance taskBackByInsId(Long instanceId, FlowParams flowParams); + + /** + * 拿回到最近办理的任务 + * + * @param taskId 流程任务id[必传] + * @param flowParams 包含流程相关参数的对象 + * - message 审批意见 [按需传输] + * - handler 当前办理人唯一标识,如用户id,用于记录历史表 [按需传输];满足以下情况可以不传 + * 实现了办理人权限处理器{@link PermissionHandler#getHandler()} + * - variable 流程变量 [按需传输,跳转条件放入流程变量<互斥网关必传>] + * - nextHandler: 执行的下个任务的办理人[按需传输] + * - nextHandlerAppend: 个任务处理人配置类型(true-追加,false-覆盖,默认false)[按需传输] + * - flowStatus 流程状态,自定义流程状态 [按需传输] + */ + Instance taskBack(Long taskId, FlowParams flowParams); + + /** + * 流程跳转 + * + * @param flowParams 包含流程相关参数的对象 + * - skipType 跳转类型(PASS审批通过 REJECT退回) [必传] + * - nodeCode 如果指定节点,可[任意跳转]到对应节点,严禁任意退回选择后置节点 [按需传输] + * - permissionFlag 办理人权限标识,比如用户,角色,部门等,用于校验是否有权限办理 [按需传输];满足以下任一情况可以不传 + * 流程设计时未设置办理人、ignore为true、实现了办理人权限处理器{@link PermissionHandler#permissions()} + * - message 审批意见 [按需传输] + * - handler 当前办理人唯一标识,如用户id,用于记录历史表 [按需传输];满足以下情况可以不传 + * 实现了办理人权限处理器{@link PermissionHandler#getHandler()} + * - variable 流程变量 [按需传输,跳转条件放入流程变量<互斥网关必传>] + * - nextHandler: 执行的下个任务的办理人[按需传输] + * - nextHandlerAppend: 个任务处理人配置类型(true-追加,false-覆盖,默认false)[按需传输] + * - flowStatus 流程状态,自定义流程状态 [按需传输] + * - ignore 忽略权限校验(比如管理员不校验),默认不忽略 [按需传输] + * @param task:流程任务[必传] + */ + Instance skip(FlowParams flowParams, Task task); + + /** + * 撤销 + * + * @param instanceId 实例id [必传] + * @param flowParams 包含流程相关参数的对象 + * - message 审批意见 [按需传输] + * - handler 当前办理人唯一标识,如用户id,用于记录历史表 [按需传输];满足以下情况可以不传 + * 实现了办理人权限处理器{@link PermissionHandler#getHandler()} + * - variable 流程变量 [按需传输,跳转条件放入流程变量<互斥网关必传>] + * - nextHandler: 执行的下个任务的办理人[按需传输] + * - nextHandlerAppend: 个任务处理人配置类型(true-追加,false-覆盖,默认false)[按需传输] + * - flowStatus 流程状态,自定义流程状态 [按需传输] + * - ignore 忽略权限校验(比如管理员不校验),默认不忽略 [按需传输] + */ + Instance revoke(Long instanceId, FlowParams flowParams); + + /** + * 终止流程,提前结束流程,将所有待办任务转历史 + * + * @param instanceId 流程实例id[必传] + * @param flowParams 包含流程相关参数的对象 + * - message 审批意见 [按需传输] + * - permissionFlag 办理人权限标识,比如用户,角色,部门等,用于校验是否有权限办理 [按需传输];满足以下任一情况可以不传 + * 流程设计时未设置办理人、ignore为true、实现了办理人权限处理器{@link PermissionHandler#permissions()} + * - handler 当前办理人唯一标识,如用户id,用于记录历史表 [按需传输];满足以下情况可以不传 + * 实现了办理人权限处理器{@link PermissionHandler#getHandler()} + * - flowStatus 流程状态,自定义流程状态[按需传输] + * - ignore 忽略权限校验(比如管理员不校验),默认不忽略 [按需传输] + */ + Instance terminationByInsId(Long instanceId, FlowParams flowParams); + + /** + * 终止流程,提前结束流程,将所有待办任务转历史 + * + * @param taskId 流程任务id [必传] + * @param flowParams 包含流程相关参数的对象 + * - message 审批意见 [按需传输] + * - permissionFlag 办理人权限标识,比如用户,角色,部门等,用于校验是否有权限办理 [按需传输];满足以下任一情况可以不传 + * 流程设计时未设置办理人、ignore为true、实现了办理人权限处理器{@link PermissionHandler#permissions()} + * - handler 当前办理人唯一标识,如用户id,用于记录历史表 [按需传输];满足以下情况可以不传 + * 实现了办理人权限处理器{@link PermissionHandler#getHandler()} + * - flowStatus 流程状态,自定义流程状态 [按需传输] + * - ignore 忽略权限校验(比如管理员不校验),默认不忽略 [按需传输] + */ + Instance termination(Long taskId, FlowParams flowParams); + + /** + * 终止流程,提前结束流程,将所有待办任务转历史 + * + * @param task 流程任务 + * @param flowParams 包含流程相关参数的对象 + * - message 审批意见 [按需传输] + * - permissionFlag 办理人权限标识,比如用户,角色,部门等,用于校验是否有权限办理 [按需传输];满足以下任一情况可以不传 + * 流程设计时未设置办理人、ignore为true、实现了办理人权限处理器{@link PermissionHandler#permissions()} + * - handler 当前办理人唯一标识,如用户id,用于记录历史表 [按需传输];满足以下情况可以不传 + * 实现了办理人权限处理器{@link PermissionHandler#getHandler()} + * - flowStatus 流程状态,自定义流程状态 [按需传输] + * - ignore 忽略权限校验(比如管理员不校验),默认不忽略 [按需传输] + */ + Instance termination(Task task, FlowParams flowParams); + + /** + * 根据instanceIds删除 + * + * @param instanceIds 流程实例id集合 + * @return boolean + */ + boolean deleteByInsIds(List instanceIds); + + /** + * 转办, 默认删除当前办理用户权限,转办后,当前办理不可办理 + * + * @param taskId 修改的任务id [必传] + * @param flowParams 包含流程相关参数的对象 + * - handler 当前办理人唯一标识,如用户id,用于记录历史表; 如果通过办理人权限处理器{@link PermissionHandler#getHandler()}传入了,就不需要传 [按需传输] + * - permissionFlag 办理人权限标识,比如用户,角色,部门等,用于校验是否有权限办理 [按需传输];满足以下任一情况可以不传 + * 流程设计时未设置办理人、ignore为true、实现了办理人权限处理器{@link PermissionHandler#permissions()} + * - addHandlers 转办对象 [必传] + * - message 审批意见 [按需传输] + * - ignore 忽略权限校验(比如管理员不校验),默认不忽略 [按需传输] + */ + boolean transfer(Long taskId, FlowParams flowParams); + + /** + * 委派, 默认删除当前办理用户权限,委派后审批完, 重新回到当前办理人 + * + * @param taskId 修改的任务id [必传] + * @param flowParams 包含流程相关参数的对象 + * - handler 当前办理人唯一标识,如用户id,用于记录历史表; 如果通过办理人权限处理器{@link PermissionHandler#getHandler()}传入了,就不需要传 [按需传输] + * - permissionFlag 办理人权限标识,比如用户,角色,部门等,用于校验是否有权限办理 [按需传输];满足以下任一情况可以不传 + * 流程设计时未设置办理人、ignore为true、实现了办理人权限处理器{@link PermissionHandler#permissions()} + * - addHandlers 委托对象 [必传] + * - message 审批意见 [按需传输] + * - ignore 忽略权限校验(比如管理员不校验),默认不忽略 [按需传输] + */ + boolean depute(Long taskId, FlowParams flowParams); + + /** + * 加签,增加办理人 + * + * @param taskId 修改的任务id [必传] + * @param flowParams 包含流程相关参数的对象 + * - handler 当前办理人唯一标识,如用户id,用于记录历史表; 如果通过办理人权限处理器{@link PermissionHandler#getHandler()}传入了,就不需要传 [按需传输] + * - permissionFlag 办理人权限标识,比如用户,角色,部门等,用于校验是否有权限办理 [按需传输];满足以下任一情况可以不传 + * 流程设计时未设置办理人、ignore为true、实现了办理人权限处理器{@link PermissionHandler#permissions()} + * - addHandlers 加签对象 [必传] + * - message 审批意见 [按需传输] + * - ignore 忽略权限校验(比如管理员不校验),默认不忽略 [按需传输] + */ + boolean addSignature(Long taskId, FlowParams flowParams); + + /** + * 减签,减少办理人 + * + * @param taskId 修改的任务id [必传] + * @param flowParams 包含流程相关参数的对象 + * - handler 当前办理人唯一标识,如用户id,用于记录历史表; 如果通过办理人权限处理器{@link PermissionHandler#getHandler()}传入了,就不需要传 [按需传输] + * - permissionFlag 办理人权限标识,比如用户,角色,部门等,用于校验是否有权限办理 [按需传输];满足以下任一情况可以不传 + * 流程设计时未设置办理人、ignore为true、实现了办理人权限处理器{@link PermissionHandler#permissions()} + * - reductionHandlers 减签对象 [必传] + * - message 审批意见 [按需传输] + * - ignore 忽略权限校验(比如管理员不校验),默认不忽略 [按需传输] + */ + boolean reductionSignature(Long taskId, FlowParams flowParams); + + /** + * 修改办理人 + * + * @param taskId 修改的任务id [必传] + * @param flowParams 包含流程相关参数的对象 + * - handler 当前办理人唯一标识,如用户id,用于记录历史表; 如果通过办理人权限处理器{@link PermissionHandler#getHandler()}传入了,就不需要传 [按需传输] + * - permissionFlag 办理人权限标识,比如用户,角色,部门等,用于校验是否有权限办理 [按需传输];满足以下任一情况可以不传 + * 流程设计时未设置办理人、ignore为true、实现了办理人权限处理器{@link PermissionHandler#permissions()} + * - addHandlers 增加办理人:加签,转办,委托[按需传输] + * - reductionHandlers 减签对象:减签,委托[按需传输] + * - message 审批意见[按需传输] + * - cooperateType 协作方式(2转办 3委派 6加签 7减签)[按需传输] + * - ignore 转办忽略权限校验,默认忽略(true:忽略,false:不忽略)[按需传输] + */ + boolean updateHandler(Long taskId, FlowParams flowParams); + + /** + * 设置流程待办任务对象 + * + * @param node 节点 + * @param instance 流程实例 + * @param flowParams 流程参数 + * @return Task + */ + Task addTask(Node node, Instance instance, Definition definition, FlowParams flowParams); + + /** + * 根据流程实例id获取流程任务集合 + * + * @param instanceId 流程实例id + * @return 任务集合 + */ + List getByInsId(Long instanceId); + + /** + * 根据流程实例id和节点code集合获取流程任务集合 + * + * @param instanceId 流程实例id + * @param nodeCodes 节点code集合 + * @return 任务集合 + */ + List getByInsIdAndNodeCodes(Long instanceId, List nodeCodes); + + /** + * 设置任务完成后的实例相关信息 + * + * @param instance 实例对象 + * @param addTasks 新增待办任务 + * @param flowParams 流程参数对象 + */ + void setInsFinishInfo(Instance instance, List addTasks, FlowParams flowParams); + + /** + * 合并流程变量到实例对象 + * + * @param instance 流程实例 + * @param variable 流程变量 + */ + void mergeVariable(Instance instance, Map variable); + + /** + * 获取表单及数据(使用表单场景) + * + * @param taskId + * @param flowParams + * @return + */ + FlowDto load(Long taskId, FlowParams flowParams); + + /** + * 获取表单及数据(使用表单场景) + * + * @param hisTaskId + * @param flowParams + * @return + */ + FlowDto hisLoad(Long hisTaskId, FlowParams flowParams); +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/UserService.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/UserService.java new file mode 100644 index 0000000..31f9452 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/UserService.java @@ -0,0 +1,165 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.service; + +import org.dromara.warm.flow.core.entity.Task; +import org.dromara.warm.flow.core.entity.User; +import org.dromara.warm.flow.core.orm.service.IWarmService; + +import java.util.List; + +/** + * 流程用户Service接口 + * + * @author xiarg + * @since 2024/5/10 13:55 + */ +public interface UserService extends IWarmService { + + /** + * 设置流程用户 + * + * @param addTasks 待办任务 + * @return List + * @author xiarg + * @since 2024/5/10 13:59 + */ + List taskAddUsers(List addTasks); + + /** + * 待办任务增加流程人员 + * + * @param task 待办任务任务信息 + * @return List + * @author xiarg + * @since 2024/5/10 15:45 + */ + List taskAddUser(Task task); + + /** + * 根据待办任务id删除流程用户 + * + * @param ids 待办任务id集合 + * @author xiarg + * @since 2024/5/10 13:59 + */ + void deleteByTaskIds(List ids); + + /** + * 根据(待办任务,实例,历史表,节点等)id查询权限人或者处理人 + * + * @param associated 待办任务id集合 + * @param type 用户表类型 + * @author xiarg + * @since 2024/5/120 13:59 + */ + List getPermission(Long associated, String... type); + + /** + * 根据(待办任务,实例,历史表,节点等)id查询权限人或者处理人 + * + * @param associated 待办任务id + * @param types 用户表类型 + * @return List + */ + List listByAssociatedAndTypes(Long associated, String... types); + + /** + * 根据(待办任务,实例,历史表,节点等)id查询权限人或者处理人 + * + * @param associateds (待办任务,实例,历史表,节点等)id集合 + * @param types 用户表类型 + * @return List + */ + List getByAssociateds(List associateds, String... types); + + /** + * 根据办理人查询, 返回集合 + * + * @param associated 待办任务id + * @param processedBy 办理人 + * @param types 用户表类型 + * @return List + */ + List listByProcessedBys(Long associated, String processedBy, String... types); + + /** + * 根据办理人查询 + * + * @param associated 待办任务id + * @param processedBys 办理人id集合 + * @param types 用户表类型 + * @return List + */ + List getByProcessedBys(Long associated, List processedBys, String... types); + + /** + * 根据关联id更新权限人 + * + * @param associated 关联人id + * @param permissions 权限人 + * @param type 权限人类型 + * @param clear 是否清空待办任务的计划审批人 + * @param handler 存储委派时的办理人 + * @return 结果 + * @author xiarg + * @since 2024/5/10 11:19 + */ + boolean updatePermission(Long associated, List permissions, String type, boolean clear, String handler); + + /** + * 构造用户比表信息 + * + * @param associated 关联id + * @param permissionList 权限标识集合 + * @param type 用户类型 + * @return 结果 + */ + List structureUser(Long associated, List permissionList, String type); + + /** + * 构造用户比表信息 + * + * @param associated 关联id + * @param permission 权限标识 + * @param type 用户类型 + * @return 结果 + */ + User structureUser(Long associated, String permission, String type); + + /** + * 构造用户比表信息 + * + * @param associated 关联id + * @param permissionList 权限标识集合 + * @param type 用户类型 + * @param handler 办理人(记录委派人) + * @return 结果 + */ + List structureUser(Long associated, List permissionList, String type, String handler); + + /** + * 构造用户比表信息 + * + * @param associated 关联id + * @param permission 权限标识 + * @param type 用户类型 + * @param handler 办理人(记录委派人) + * @return 结果 + */ + User structureUser(Long associated, String permission, String type, String handler); + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/impl/ChartServiceImpl.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/impl/ChartServiceImpl.java new file mode 100644 index 0000000..72a1798 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/impl/ChartServiceImpl.java @@ -0,0 +1,166 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.service.impl; + +import org.dromara.warm.flow.core.FlowEngine; +import org.dromara.warm.flow.core.dto.DefJson; +import org.dromara.warm.flow.core.dto.NodeJson; +import org.dromara.warm.flow.core.dto.PathWayData; +import org.dromara.warm.flow.core.dto.SkipJson; +import org.dromara.warm.flow.core.entity.Instance; +import org.dromara.warm.flow.core.entity.Skip; +import org.dromara.warm.flow.core.enums.ChartStatus; +import org.dromara.warm.flow.core.enums.NodeType; +import org.dromara.warm.flow.core.enums.SkipType; +import org.dromara.warm.flow.core.service.ChartService; +import org.dromara.warm.flow.core.utils.CollUtil; +import org.dromara.warm.flow.core.utils.ObjectUtil; +import org.dromara.warm.flow.core.utils.StreamUtils; +import org.dromara.warm.flow.core.utils.StringUtils; + +import java.awt.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 流程图绘制Service业务层处理 + * + * @author warm + * @since 2024-12-30 + */ +public class ChartServiceImpl implements ChartService { + + @Override + public String startMetadata(PathWayData pathWayData) { + + DefJson defJson = FlowEngine.defService().queryDesign(pathWayData.getDefId()); + List nodeList = defJson.getNodeList(); + + Map nodeMap = StreamUtils.toMap(nodeList, NodeJson::getNodeCode + , node -> node.setStatus(ChartStatus.NOT_DONE.getKey())); + Map skipMap = nodeList.stream().map(NodeJson::getSkipList).flatMap(List::stream) + .collect(Collectors.toMap(this::getSkipKey, skip -> skip.setStatus(ChartStatus.NOT_DONE.getKey()))); + + pathWayData.getPathWayNodes().forEach(node -> nodeMap.get(node.getNodeCode()).setStatus(ChartStatus.DONE.getKey())); + pathWayData.getPathWaySkips().forEach(skip -> skipMap.get(getSkipKey(skip)).setStatus(ChartStatus.DONE.getKey())); + pathWayData.getTargetNodes().forEach(node -> nodeMap.get(node.getNodeCode()).setStatus( + NodeType.isEnd(node.getNodeType()) ? ChartStatus.DONE.getKey() : ChartStatus.TO_DO.getKey() + )); + + return FlowEngine.jsonConvert.objToStr(defJson); + } + + @Override + public String skipMetadata(PathWayData pathWayData) { + Instance instance = FlowEngine.insService().getById(pathWayData.getInsId()); + DefJson defJson = FlowEngine.jsonConvert.strToBean(instance.getDefJson(), DefJson.class); + + List nodeList = defJson.getNodeList(); + List skipList = StreamUtils.toListAll(defJson.getNodeList(), NodeJson::getSkipList); + Map nodeMap = StreamUtils.toMap(nodeList, NodeJson::getNodeCode, node -> node); + Map skipMap = StreamUtils.toMap(skipList, this::getSkipKey, skip -> skip); + + pathWayData.getPathWayNodes().forEach(node -> { + NodeJson nodeJson = nodeMap.get(node.getNodeCode()); + if (SkipType.isPass(pathWayData.getSkipType())) { + nodeJson.setStatus(ChartStatus.DONE.getKey()); + } else if (SkipType.isReject(pathWayData.getSkipType())) { + nodeJson.setStatus(ChartStatus.NOT_DONE.getKey()); + } + }); + pathWayData.getPathWaySkips().forEach(skip -> { + SkipJson skipJson = skipMap.get(getSkipKey(skip)); + if (SkipType.isPass(pathWayData.getSkipType())) { + skipJson.setStatus(ChartStatus.DONE.getKey()); + } else if (SkipType.isReject(pathWayData.getSkipType())) { + skipJson.setStatus(ChartStatus.NOT_DONE.getKey()); + } + }); + pathWayData.getTargetNodes().forEach(node -> { + NodeJson nodeJson = nodeMap.get(node.getNodeCode()); + if (NodeType.isEnd(node.getNodeType())) { + nodeJson.setStatus(ChartStatus.DONE.getKey()); + } else { + nodeJson.setStatus(ChartStatus.TO_DO.getKey()); + } + }); + + if (SkipType.isReject(pathWayData.getSkipType())) { + Map> skipNextMap = StreamUtils.groupByKeyFilter(skip -> + !SkipType.isReject(skip.getSkipType()), skipList, SkipJson::getNowNodeCode); + pathWayData.getTargetNodes().forEach(node -> rejectReset(node.getNodeCode(), skipNextMap, nodeMap)); + } + + pathWayData.getTargetNodes().forEach(node -> { + if (NodeType.isEnd(node.getNodeType())) { + nodeList.forEach(nodeJson -> { + if (ChartStatus.isToDo(nodeJson.getStatus())) { + nodeJson.setStatus(ChartStatus.NOT_DONE.getKey()); + } + }); + } + }); + + + return FlowEngine.jsonConvert.objToStr(defJson); + } + + @Override + public List getChartRgb(String modelValue) { + List chartStatusColor = new ArrayList<>(); + Color done = ChartStatus.getDone(modelValue); + chartStatusColor.add(done.getRed() + "," + done.getGreen() + "," + done.getBlue()); + Color toDo = ChartStatus.getToDo(modelValue); + chartStatusColor.add(toDo.getRed() + "," + toDo.getGreen() + "," + toDo.getBlue()); + Color notDone = ChartStatus.getNotDone(modelValue); + chartStatusColor.add(notDone.getRed() + "," + notDone.getGreen() + "," + notDone.getBlue()); + return chartStatusColor; + } + + private String getSkipKey(SkipJson skip) { + return StringUtils.join(new String[]{ + skip.getNowNodeCode(), + skip.getSkipType(), + skip.getSkipCondition(), + skip.getNextNodeCode()}, ":"); + } + + private String getSkipKey(Skip skip) { + return StringUtils.join(new String[]{ + skip.getNowNodeCode(), + skip.getSkipType(), + skip.getSkipCondition(), + skip.getNextNodeCode()}, ":"); + } + + private void rejectReset(String nodeCode, Map> skipNextMap, Map nodeMap) { + List oneNextSkips = skipNextMap.get(nodeCode); + if (CollUtil.isNotEmpty(oneNextSkips)) { + oneNextSkips.forEach(oneNextSkip -> { + if (ObjectUtil.isNotNull(oneNextSkip) && !ChartStatus.isNotDone(oneNextSkip.getStatus())) { + oneNextSkip.setStatus(ChartStatus.NOT_DONE.getKey()); + NodeJson nodeJson = nodeMap.get(oneNextSkip.getNextNodeCode()); + if (ObjectUtil.isNotNull(nodeJson) && !ChartStatus.isNotDone(nodeJson.getStatus())) { + nodeJson.setStatus(ChartStatus.NOT_DONE.getKey()); + rejectReset(nodeJson.getNodeCode(), skipNextMap, nodeMap); + } + } + }); + } + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/impl/DefServiceImpl.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/impl/DefServiceImpl.java new file mode 100644 index 0000000..4d2ed7c --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/impl/DefServiceImpl.java @@ -0,0 +1,373 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.service.impl; + +import lombok.extern.slf4j.Slf4j; +import org.dromara.warm.flow.core.FlowEngine; +import org.dromara.warm.flow.core.constant.ExceptionCons; +import org.dromara.warm.flow.core.dto.DefJson; +import org.dromara.warm.flow.core.dto.FlowCombine; +import org.dromara.warm.flow.core.entity.Definition; +import org.dromara.warm.flow.core.entity.Instance; +import org.dromara.warm.flow.core.entity.Node; +import org.dromara.warm.flow.core.entity.Skip; +import org.dromara.warm.flow.core.enums.ActivityStatus; +import org.dromara.warm.flow.core.enums.NodeType; +import org.dromara.warm.flow.core.enums.PublishStatus; +import org.dromara.warm.flow.core.enums.SkipType; +import org.dromara.warm.flow.core.exception.FlowException; +import org.dromara.warm.flow.core.orm.dao.FlowDefinitionDao; +import org.dromara.warm.flow.core.orm.service.impl.WarmServiceImpl; +import org.dromara.warm.flow.core.service.DefService; +import org.dromara.warm.flow.core.utils.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.math.BigDecimal; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 流程定义Service业务层处理 + * + * @author warm + * @since 2023-03-29 + */ +@Slf4j +public class DefServiceImpl extends WarmServiceImpl, Definition> implements DefService { + + + @Override + public DefService setDao(FlowDefinitionDao warmDao) { + this.warmDao = warmDao; + return this; + } + + @Override + public Definition importIs(InputStream is) { + StringBuilder stringBuilder = new StringBuilder(); + try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is))) { + String line; + while ((line = bufferedReader.readLine()) != null) { + stringBuilder.append(line); + stringBuilder.append(System.lineSeparator()); + } + } catch (IOException e) { + throw new FlowException(ExceptionCons.READ_IS_ERROR); + } + return importJson(stringBuilder.toString()); + } + + @Override + public Definition importJson(String defJson) { + return importDef(FlowEngine.jsonConvert.strToBean(defJson, DefJson.class)); + } + + @Override + public Definition importDef(DefJson defJson) { + Definition definition = DefJson.copyDef(defJson); + FlowCombine flowCombine = FlowConfigUtil.structureFlow(definition); + return insertFlow(flowCombine.getDefinition(), flowCombine.getAllNodes(), flowCombine.getAllSkips()); + } + + @Override + public Definition insertFlow(Definition definition, List nodeList, List skipList) { + definition.setVersion(getNewVersion(definition)); + for (Node node : nodeList) { + node.setVersion(definition.getVersion()); + } + FlowEngine.defService().save(definition); + FlowEngine.nodeService().saveBatch(nodeList); + FlowEngine.skipService().saveBatch(skipList); + return definition; + } + + @Override + public boolean checkAndSave(Definition definition) { + return save(definition.setVersion(getNewVersion(definition))); + } + + @Override + public void saveDef(DefJson defJson, boolean onlyNodeSkip) { + if (ObjectUtil.isNull(defJson)) { + return; + } + FlowCombine flowCombine = DefJson.copyCombine(defJson); + Definition definition = flowCombine.getDefinition(); + Long id = definition.getId(); + // 如果是新增的流程定义 + if (ObjectUtil.isNull(id)) { + definition.setVersion(getNewVersion(definition)); + FlowEngine.dataFillHandler().idFill(definition); + } + + // 校验流程定义合法性 + checkFlowLegal(flowCombine); + + // 如果是新增的流程定义 + if (ObjectUtil.isNull(id)) { + FlowEngine.defService().save(definition); + } else { + if (!onlyNodeSkip) { + FlowEngine.defService().updateById(definition); + } + // 删除所有节点和连线 + FlowEngine.nodeService().remove(FlowEngine.newNode().setDefinitionId(id)); + FlowEngine.skipService().remove(FlowEngine.newSkip().setDefinitionId(id)); + } + + // 保存流程节点和跳转 + List allNodes = flowCombine.getAllNodes(); + allNodes.forEach(node -> { + if(StringUtils.isEmpty(node.getNodeRatio())) { + node.setNodeRatio(StringUtils.ZERO); + } + }); + // 所有的流程连线 + List allSkips = flowCombine.getAllSkips(); + + // 保存节点,流程连线,权利人 + FlowEngine.nodeService().saveBatch(allNodes); + FlowEngine.skipService().saveBatch(allSkips); + } + + @Override + public String exportJson(Long id) { + return FlowEngine.jsonConvert.objToStr(queryDesign(id).setIsPublish(null)); + } + + @Override + public Definition getAllDataDefinition(Long id) { + Definition definition = getDao().selectById(id); + List nodeList = FlowEngine.nodeService().getByDefId(id); + definition.setNodeList(nodeList); + List skips = FlowEngine.skipService().getByDefId(id); + Map> flowSkipMap = skips.stream() + .collect(Collectors.groupingBy(Skip::getNowNodeCode)); + nodeList.forEach(flowNode -> flowNode.setSkipList(flowSkipMap.get(flowNode.getNodeCode()))); + return definition; + } + + @Override + public FlowCombine getFlowCombine(Long id) { + return getFlowCombine(getDao().selectById(id)); + } + + @Override + public FlowCombine getFlowCombineNoDef(Long id) { + FlowCombine flowCombine = new FlowCombine(); + flowCombine.setAllNodes(FlowEngine.nodeService().getByDefId(id)); + flowCombine.setAllSkips(FlowEngine.skipService().getByDefId(id)); + return flowCombine; + } + + @Override + public FlowCombine getFlowCombine(Definition definition) { + FlowCombine flowCombine = getFlowCombineNoDef(definition.getId()); + flowCombine.setDefinition(definition); + return flowCombine; + } + + @Override + public DefJson queryDesign(Long id) { + return DefJson.copyDef(getAllDataDefinition(id)); + } + + @Override + public List queryByCodeList(List flowCodeList) { + return getDao().queryByCodeList(flowCodeList); + } + + @Override + public void updatePublishStatus(List ids, Integer publishStatus) { + getDao().updatePublishStatus(ids, publishStatus); + } + + /** + * 删除流程定义 + * + * @param ids 流程定义id + */ + @Override + public boolean removeDef(List ids) { + ids.forEach(id -> { + List instances = FlowEngine.insService().getByDefId(id); + AssertUtil.isNotEmpty(instances, ExceptionCons.EXIST_START_TASK); + }); + FlowEngine.nodeService().deleteNodeByDefIds(ids); + FlowEngine.skipService().deleteSkipByDefIds(ids); + return removeByIds(ids); + } + + @Override + public boolean publish(Long id) { + List nodeList = FlowEngine.nodeService().getByDefId(id); + AssertUtil.isEmpty(nodeList, ExceptionCons.NOT_DRAW_FLOW_ERROR); + Definition definition = getById(id); + List definitions = getByFlowCode(definition.getFlowCode()); + // 已发布流程定义,改为已失效或者未发布状态 + List otherDefIds = definitions.stream() + .filter(item -> !Objects.equals(definition.getId(), item.getId()) + && PublishStatus.PUBLISHED.getKey().equals(item.getIsPublish())) + .map(Definition::getId) + .collect(Collectors.toList()); + if (CollUtil.isNotEmpty(otherDefIds)) { + List instanceList = FlowEngine.insService().listByDefIds(otherDefIds); + if (CollUtil.isNotEmpty(instanceList)) { + // 已发布已使用过的流程定义 + Set useDefIds = StreamUtils.toSet(instanceList, Instance::getDefinitionId); + if (CollUtil.isNotEmpty(useDefIds)) { + // 已发布已使用过的流程定义,改为已失效 + updatePublishStatus(new ArrayList<>(useDefIds), PublishStatus.EXPIRED.getKey()); + // 过滤掉已发布已使用-->已发布未使用 + otherDefIds.removeIf(useDefIds::contains); + } + } + if (CollUtil.isNotEmpty(otherDefIds)) { + // 已发布未使用过的流程定义,改为未发布 + updatePublishStatus(otherDefIds, PublishStatus.UNPUBLISHED.getKey()); + } + } + + Definition flowDefinition = FlowEngine.newDef(); + flowDefinition.setId(id); + flowDefinition.setIsPublish(PublishStatus.PUBLISHED.getKey()); + return updateById(flowDefinition); + } + + @Override + public boolean unPublish(Long id) { + List instances = FlowEngine.insService().getByDefId(id); + AssertUtil.isNotEmpty(instances, ExceptionCons.EXIST_START_TASK); + Definition definition = FlowEngine.newDef().setId(id); + definition.setIsPublish(PublishStatus.UNPUBLISHED.getKey()); + return updateById(definition); + } + + @Override + public boolean copyDef(Long id) { + Definition definition = getById(id).copy(); + definition.setVersion(getNewVersion(definition)); + AssertUtil.isNull(definition, ExceptionCons.NOT_FOUNT_DEF); + + List nodeList = FlowEngine.nodeService().getByDefId(id).stream().map(Node::copy).collect(Collectors.toList()); + List skipList = FlowEngine.skipService().getByDefId(id).stream().map(Skip::copy).collect(Collectors.toList()); + FlowEngine.dataFillHandler().idFill(definition); + + nodeList.forEach(node -> node.setDefinitionId(definition.getId()).setVersion(definition.getVersion())); + FlowEngine.nodeService().saveBatch(nodeList); + + skipList.forEach(skip -> skip.setDefinitionId(definition.getId())); + FlowEngine.skipService().saveBatch(skipList); + return save(definition); + } + + @Override + public boolean active(Long id) { + Definition definition = getById(id); + AssertUtil.isNull(definition, ExceptionCons.NOT_FOUNT_DEF); + AssertUtil.isTrue(ActivityStatus.isActivity(definition.getActivityStatus()), ExceptionCons.DEFINITION_ALREADY_ACTIVITY); + definition.setActivityStatus(ActivityStatus.ACTIVITY.getKey()); + return updateById(definition); + } + + @Override + public boolean unActive(Long id) { + Definition definition = getById(id); + AssertUtil.isNull(definition, ExceptionCons.NOT_FOUNT_DEF); + AssertUtil.isTrue(ActivityStatus.isSuspended(definition.getActivityStatus()), ExceptionCons.DEFINITION_ALREADY_SUSPENDED); + definition.setActivityStatus(ActivityStatus.SUSPENDED.getKey()); + return updateById(definition); + } + + @Override + public List getByFlowCode(String flowCode) { + return list(FlowEngine.newDef().setFlowCode(flowCode)); + } + + @Override + public Definition getPublishByFlowCode(String flowCode) { + return FlowEngine.defService().getOne(FlowEngine.newDef() + .setFlowCode(flowCode).setIsPublish(PublishStatus.PUBLISHED.getKey())); + } + + private String getNewVersion(Definition definition) { + List flowCodeList = Collections.singletonList(definition.getFlowCode()); + List definitions = queryByCodeList(flowCodeList); + int highestVersion = 0; + String latestNonPositiveVersion = null; + long latestTimestamp = Long.MIN_VALUE; + + for (Definition otherDef : definitions) { + if (definition.getFlowCode().equals(otherDef.getFlowCode())) { + try { + int version = Integer.parseInt(otherDef.getVersion()); + if (version > highestVersion) { + highestVersion = version; + } + } catch (NumberFormatException e) { + long timestamp = otherDef.getCreateTime().getTime(); + if (timestamp > latestTimestamp) { + latestTimestamp = timestamp; + latestNonPositiveVersion = otherDef.getVersion(); + } + } + } + } + + String version = "1"; + if (highestVersion > 0) { + version = String.valueOf(highestVersion + 1); + } else if (latestNonPositiveVersion != null) { + version = latestNonPositiveVersion + "_1"; + } + + return version; + } + + private void checkFlowLegal(FlowCombine flowCombine) { + Definition definition = flowCombine.getDefinition(); + String flowName = definition.getFlowName(); + AssertUtil.isEmpty(definition.getFlowCode(), "【" + flowName + "】流程flowCode为空!"); + // 节点校验 + List allNodes = flowCombine.getAllNodes(); + List allSkips = flowCombine.getAllSkips(); + Map> skipMap = StreamUtils.groupByKey(allSkips, Skip::getNowNodeCode); + allNodes.forEach(node -> { + node.setSkipList(skipMap.get(node.getNodeCode())); + skipMap.remove(node.getNodeCode()); + }); + AssertUtil.isNotEmpty(skipMap, "[" + flowName + "]" + ExceptionCons.FLOW_HAVE_USELESS_SKIP); + // 每一个流程的开始节点个数 + Set nodeCodeSet = new HashSet<>(); + // 便利一个流程中的各个节点 + int startNum = 0; + for (Node node : allNodes) { + FlowConfigUtil.initNodeAndCondition(node, definition.getId(), definition.getVersion()); + startNum = FlowConfigUtil.checkStartAndSame(node, startNum, flowName, nodeCodeSet); + } + AssertUtil.isTrue(startNum == 0, "[" + flowName + "]" + ExceptionCons.LOST_START_NODE); + // 校验跳转节点的合法性 + FlowConfigUtil.checkSkipNode(allSkips); + // 校验所有目标节点是否都存在 + FlowConfigUtil.validaIsExistDestNode(allSkips, nodeCodeSet); + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/impl/FormServiceImpl.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/impl/FormServiceImpl.java new file mode 100644 index 0000000..6a094ec --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/impl/FormServiceImpl.java @@ -0,0 +1,146 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.service.impl; + +import org.dromara.warm.flow.core.FlowEngine; +import org.dromara.warm.flow.core.constant.ExceptionCons; +import org.dromara.warm.flow.core.entity.Definition; +import org.dromara.warm.flow.core.entity.Form; +import org.dromara.warm.flow.core.entity.Node; +import org.dromara.warm.flow.core.enums.PublishStatus; +import org.dromara.warm.flow.core.orm.dao.FlowFormDao; +import org.dromara.warm.flow.core.orm.service.impl.WarmServiceImpl; +import org.dromara.warm.flow.core.service.FormService; +import org.dromara.warm.flow.core.utils.AssertUtil; +import org.dromara.warm.flow.core.utils.ClassUtil; +import org.dromara.warm.flow.core.utils.CollUtil; +import org.dromara.warm.flow.core.utils.ObjectUtil; +import org.dromara.warm.flow.core.utils.page.Page; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collections; +import java.util.List; + +/** + * 流程表单Service业务层处理 + * + * @author vanlin + * @since 2024/8/19 10:07 + */ +public class FormServiceImpl extends WarmServiceImpl, Form> implements FormService { + + public static final Logger LOGGER = LoggerFactory.getLogger(FormServiceImpl.class); + + @Override + public FormService setDao(FlowFormDao warmDao) { + this.warmDao = warmDao; + return this; + } + + @Override + public boolean publish(Long id) { + Form form = getById(id); + AssertUtil.isTrue(form.getIsPublish().equals(PublishStatus.PUBLISHED.getKey()), ExceptionCons.FORM_ALREADY_PUBLISH); + form.setIsPublish(PublishStatus.PUBLISHED.getKey()); + return updateById(form); + } + + @Override + public boolean unPublish(Long id) { + Form form = getById(id); + List nodes = FlowEngine.nodeService().list(FlowEngine.newNode().setFormPath("" + form.getId())); + AssertUtil.isNotEmpty(nodes, ExceptionCons.EXIST_USE_FORM); + List definitions = FlowEngine.defService().list(FlowEngine.newDef().setFormPath("" + form.getId())); + AssertUtil.isNotEmpty(definitions, ExceptionCons.EXIST_USE_FORM); + AssertUtil.isTrue(form.getIsPublish().equals(PublishStatus.UNPUBLISHED.getKey()), ExceptionCons.FORM_ALREADY_UN_PUBLISH); + form.setIsPublish(PublishStatus.UNPUBLISHED.getKey()); + return updateById(form); + } + + @Override + public boolean save(Form form) { + form.setVersion(getNewVersion(form)); + return super.save(form); + } + + @Override + public boolean copyForm(Long id) { + Form form = ClassUtil.clone(getById(id)); + AssertUtil.isTrue(ObjectUtil.isNull(form), ExceptionCons.NOT_FOUNT_DEF); + FlowEngine.dataFillHandler().idFill(form.setId(null)); + form.setVersion(getNewVersion(form)) + .setIsPublish(PublishStatus.UNPUBLISHED.getKey()) + .setCreateTime(null) + .setUpdateTime(null); + return save(form); + } + + @Override + public Form getByCode(String formCode, String formVersion) { + List list = list(FlowEngine.newForm().setFormCode(formCode).setVersion(formVersion)); + AssertUtil.isTrue(CollUtil.isEmpty(list), ExceptionCons.NOT_FOUNT_TASK); + AssertUtil.isTrue(list.size() > 1, ExceptionCons.FORM_NOT_ONE); + return list.get(0); + } + + @Override + public Form getById(Long id) { + AssertUtil.isNull(id, ExceptionCons.ID_EMPTY); + return super.getById(id); + } + + @Override + public Page publishedPage(String formName, Integer pageNum, Integer pageSize) { + return page(FlowEngine.newForm().setFormName(formName).setIsPublish(1), + Page.pageOf(pageNum, pageSize)); + } + + @Override + public boolean saveContent(Long id, String formContent) { + Form form = getById(id); + AssertUtil.isTrue(form.getIsPublish().equals(PublishStatus.PUBLISHED.getKey()), ExceptionCons.FORM_ALREADY_PUBLISH); + + form.setFormContent(formContent); + return updateById(form); + } + + private String getNewVersion(Form form) { + List formCodeList = Collections.singletonList(form.getFormCode()); + List forms = getDao().queryByCodeList(formCodeList); + int highestVersion = 0; + + for (Form otherForm : forms) { + if (form.getFormCode().equals(otherForm.getFormCode())) { + try { + int version = Integer.parseInt(otherForm.getVersion()); + if (version > highestVersion) { + highestVersion = version; + } + } catch (NumberFormatException e) { + LOGGER.error("版本格式化异常 - {}", e.getLocalizedMessage()); + } + } + } + + String version = "1"; + if (highestVersion > 0) { + version = String.valueOf(highestVersion + 1); + } + + return version; + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/impl/HisTaskServiceImpl.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/impl/HisTaskServiceImpl.java new file mode 100644 index 0000000..5c7070d --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/impl/HisTaskServiceImpl.java @@ -0,0 +1,225 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.service.impl; + +import org.dromara.warm.flow.core.FlowEngine; +import org.dromara.warm.flow.core.dto.FlowParams; +import org.dromara.warm.flow.core.entity.HisTask; +import org.dromara.warm.flow.core.entity.Node; +import org.dromara.warm.flow.core.entity.Task; +import org.dromara.warm.flow.core.entity.User; +import org.dromara.warm.flow.core.enums.CooperateType; +import org.dromara.warm.flow.core.enums.FlowStatus; +import org.dromara.warm.flow.core.enums.SkipType; +import org.dromara.warm.flow.core.orm.dao.FlowHisTaskDao; +import org.dromara.warm.flow.core.orm.service.impl.WarmServiceImpl; +import org.dromara.warm.flow.core.service.HisTaskService; +import org.dromara.warm.flow.core.utils.*; + +import java.util.ArrayList; +import java.util.List; + +/** + * 历史任务记录Service业务层处理 + * + * @author warm + * @since 2023-03-29 + */ +public class HisTaskServiceImpl extends WarmServiceImpl, HisTask> implements HisTaskService { + + @Override + public HisTaskService setDao(FlowHisTaskDao warmDao) { + this.warmDao = warmDao; + return this; + } + + @Override + public List listByTaskId(Long taskId) { + return list(FlowEngine.newHisTask().setTaskId(taskId)); + } + + @Override + public List listByTaskIdAndCooperateTypes(Long taskId, Integer... cooperateTypes) { + if (ArrayUtil.isEmpty(cooperateTypes)) { + return listByTaskId(taskId); + } + if (cooperateTypes.length == 1) { + return list(FlowEngine.newHisTask().setTaskId(taskId).setCooperateType(cooperateTypes[0])); + } + return getDao().listByTaskIdAndCooperateTypes(taskId, cooperateTypes); + } + + @Override + public List getByInsAndNodeCodes(Long instanceId, List nodeCodes) { + return getDao().getByInsAndNodeCodes(instanceId, nodeCodes); + } + + @Override + public boolean deleteByInsIds(List instanceIds) { + return SqlHelper.retBool(getDao().deleteByInsIds(instanceIds)); + } + + @Override + public HisTask setSkipInsHis(Task task, List nextNodes, FlowParams flowParams) { + String flowStatus = getFlowStatus(flowParams); + return setSkipHis(task, nextNodes, flowParams, flowStatus); + } + + @Override + public List setSkipHisList(List taskList, List nextNodes, FlowParams flowParams) { + String flowStatus = getFlowStatus(flowParams); + List hisTasks = new ArrayList<>(); + for (Task task : taskList) { + HisTask hisTask = setSkipHis(task, nextNodes, flowParams, flowStatus); + hisTasks.add(hisTask); + } + return hisTasks; + } + + @Override + public HisTask setSkipHisTask(Task task, Node nextNode, FlowParams flowParams) { + String flowStatus = getFlowStatus(flowParams); + return setSkipHis(task, CollUtil.toList(nextNode), flowParams, flowStatus); + } + + + @Override + public HisTask setCooperateHis(Task task, Node node, FlowParams flowParams + , List collaborators) { + String flowStatus = getFlowStatus(flowParams); + HisTask hisTask = FlowEngine.newHisTask() + .setTaskId(task.getId()) + .setInstanceId(task.getInstanceId()) + .setCooperateType(ObjectUtil.isNotNull(flowParams.getCooperateType()) + ? flowParams.getCooperateType() : CooperateType.APPROVAL.getKey()) + .setCollaborator(StreamUtils.join(collaborators, c -> c)) + .setNodeCode(task.getNodeCode()) + .setNodeName(task.getNodeName()) + .setNodeType(task.getNodeType()) + .setDefinitionId(task.getDefinitionId()) + .setTargetNodeCode(node.getNodeCode()) + .setTargetNodeName(node.getNodeName()) + .setApprover(flowParams.getHandler()) + .setSkipType(flowParams.getSkipType()) + .setFlowStatus(StringUtils.isNotEmpty(flowStatus) + ? flowStatus : FlowStatus.APPROVAL.getKey()) + .setFormCustom(task.getFormCustom()) + .setFormPath(task.getFormPath()) + .setMessage(flowParams.getMessage()) + .setVariable(flowParams.getVariableStr()) + //业务详情添加至历史记录 + .setExt(flowParams.getHisTaskExt()) + .setCreateTime(task.getCreateTime()); + FlowEngine.dataFillHandler().idFill(hisTask); + return hisTask; + } + + @Override + public HisTask setDeputeHisTask(Task task, FlowParams flowParams, User entrustedUser) { + String flowStatus = getFlowStatus(flowParams); + HisTask hisTask = FlowEngine.newHisTask() + .setTaskId(task.getId()) + .setInstanceId(task.getInstanceId()) + .setCooperateType(CooperateType.DEPUTE.getKey()) + .setNodeCode(task.getNodeCode()) + .setNodeName(task.getNodeName()) + .setNodeType(task.getNodeType()) + .setDefinitionId(task.getDefinitionId()) + .setTargetNodeCode(task.getNodeCode()) + .setTargetNodeName(task.getNodeName()) + .setApprover(flowParams.getHandler()) + .setCollaborator(entrustedUser.getCreateBy()) + .setSkipType(flowParams.getSkipType()) + .setFlowStatus(StringUtils.isNotEmpty(flowStatus) + ? flowStatus : SkipType.isReject(flowParams.getSkipType()) + ? FlowStatus.REJECT.getKey() : FlowStatus.PASS.getKey()) + .setFormCustom(task.getFormCustom()) + .setFormPath(task.getFormPath()) + .setMessage(flowParams.getMessage()) + .setVariable(flowParams.getVariableStr()) + //业务详情添加至历史记录 + .setExt(flowParams.getHisTaskExt()) + .setCreateTime(task.getCreateTime()); + FlowEngine.dataFillHandler().idFill(hisTask); + return hisTask; + } + + @Override + public HisTask setSignHisTask(Task task, FlowParams flowParams, String nodeRatio, boolean isPass) { + String flowStatus = getFlowStatus(flowParams); + HisTask hisTask = FlowEngine.newHisTask() + .setTaskId(task.getId()) + .setInstanceId(task.getInstanceId()) + .setCooperateType(CooperateType.isCountersign(nodeRatio) + ? CooperateType.COUNTERSIGN.getKey() : CooperateType.VOTE.getKey()) + .setNodeCode(task.getNodeCode()) + .setNodeName(task.getNodeName()) + .setNodeType(task.getNodeType()) + .setDefinitionId(task.getDefinitionId()) + .setApprover(flowParams.getHandler()) + .setMessage(flowParams.getMessage()) + .setSkipType(isPass ? SkipType.PASS.getKey() : SkipType.REJECT.getKey()) + .setFlowStatus(StringUtils.isNotEmpty(flowStatus) + ? flowStatus : isPass + ? FlowStatus.PASS.getKey() : FlowStatus.REJECT.getKey()) + .setFormCustom(task.getFormCustom()) + .setFormPath(task.getFormPath()) + .setMessage(flowParams.getMessage()) + .setVariable(flowParams.getVariableStr()) + //业务详情添加至历史记录 + .setExt(flowParams.getHisTaskExt()) + .setCreateTime(task.getCreateTime()); + FlowEngine.dataFillHandler().idFill(hisTask); + return hisTask; + } + + @Override + public List getByInsId(Long instanceId) { + return FlowEngine.hisTaskService().list(FlowEngine.newHisTask().setInstanceId(instanceId)); + } + + private HisTask setSkipHis(Task task, List nextNodes, FlowParams flowParams, String flowStatus) { + HisTask hisTask = FlowEngine.newHisTask() + .setTaskId(task.getId()) + .setInstanceId(task.getInstanceId()) + .setCooperateType(ObjectUtil.isNotNull(flowParams.getCooperateType()) + ? flowParams.getCooperateType() : CooperateType.APPROVAL.getKey()) + .setNodeCode(task.getNodeCode()) + .setNodeName(task.getNodeName()) + .setNodeType(task.getNodeType()) + .setDefinitionId(task.getDefinitionId()) + .setTargetNodeCode(StreamUtils.join(nextNodes, Node::getNodeCode)) + .setTargetNodeName(StreamUtils.join(nextNodes, Node::getNodeName)) + .setApprover(flowParams.getHandler()) + .setSkipType(flowParams.getSkipType()) + .setFlowStatus(StringUtils.isNotEmpty(flowStatus) + ? flowStatus : SkipType.isReject(flowParams.getSkipType()) + ? FlowStatus.REJECT.getKey() : FlowStatus.PASS.getKey()) + .setFormCustom(task.getFormCustom()) + .setFormPath(task.getFormPath()) + .setMessage(flowParams.getMessage()) + .setVariable(flowParams.getVariableStr()) + //业务详情添加至历史记录 + .setExt(flowParams.getHisTaskExt()) + .setCreateTime(task.getCreateTime()); + FlowEngine.dataFillHandler().idFill(hisTask); + return hisTask; + } + + private String getFlowStatus(FlowParams flowParams) { + return StringUtils.emptyDefault(flowParams.getHisStatus(), flowParams.getFlowStatus()); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/impl/InsServiceImpl.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/impl/InsServiceImpl.java new file mode 100644 index 0000000..377ed87 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/impl/InsServiceImpl.java @@ -0,0 +1,245 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.service.impl; + +import org.dromara.warm.flow.core.FlowEngine; +import org.dromara.warm.flow.core.constant.ExceptionCons; +import org.dromara.warm.flow.core.dto.FlowCombine; +import org.dromara.warm.flow.core.dto.FlowParams; +import org.dromara.warm.flow.core.dto.PathWayData; +import org.dromara.warm.flow.core.entity.*; +import org.dromara.warm.flow.core.enums.ActivityStatus; +import org.dromara.warm.flow.core.enums.FlowStatus; +import org.dromara.warm.flow.core.enums.NodeType; +import org.dromara.warm.flow.core.enums.SkipType; +import org.dromara.warm.flow.core.listener.Listener; +import org.dromara.warm.flow.core.listener.ListenerVariable; +import org.dromara.warm.flow.core.orm.dao.FlowInstanceDao; +import org.dromara.warm.flow.core.orm.service.impl.WarmServiceImpl; +import org.dromara.warm.flow.core.service.InsService; +import org.dromara.warm.flow.core.utils.*; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 流程实例Service业务层处理 + * + * @author warm + * @since 2023-03-29 + */ +public class InsServiceImpl extends WarmServiceImpl, Instance> implements InsService { + + @Override + public InsService setDao(FlowInstanceDao warmDao) { + this.warmDao = warmDao; + return this; + } + + @Override + public Instance start(String businessId, FlowParams flowParams) { + AssertUtil.isNull(flowParams.getFlowCode(), ExceptionCons.NULL_FLOW_CODE); + AssertUtil.isEmpty(businessId, ExceptionCons.NULL_BUSINESS_ID); + // 获取已发布的流程节点 + Definition definition = FlowEngine.defService().getPublishByFlowCode(flowParams.getFlowCode()); + AssertUtil.isNull(definition, ExceptionCons.NOT_FOUNT_DEF); + FlowCombine flowCombine = FlowEngine.defService().getFlowCombine(definition); + // 获取开始节点 + Node startNode = StreamUtils.filterOne(flowCombine.getAllNodes(), t -> NodeType.isStart(t.getNodeType())); + AssertUtil.isNull(startNode, ExceptionCons.LOST_START_NODE); + + // 判断流程定义是否激活状态 + AssertUtil.isTrue(definition.getActivityStatus().equals(ActivityStatus.SUSPENDED.getKey()) + , ExceptionCons.NOT_DEFINITION_ACTIVITY); + flowParams.skipType(SkipType.PASS.getKey()); + + // 执行开始监听器 + ListenerUtil.executeListener(new ListenerVariable(definition, null, startNode, flowParams.getVariable()) + .setFlowParams(flowParams), Listener.LISTENER_START); + + + // 获取下一个节点,如果是网关节点,则重新获取后续节点 + PathWayData pathWayData = new PathWayData().setDefId(startNode.getDefinitionId()).setSkipType(flowParams.getSkipType()); + List nextNodes = FlowEngine.nodeService().getNextNodeList(startNode, null, flowParams.getSkipType(), + flowParams.getVariable(), pathWayData, flowCombine); + + // 设置流程实例对象 + Instance instance = setStartInstance(nextNodes.get(0), businessId, flowParams); + + // 设置历史任务 + HisTask hisTask = setHisTask(nextNodes, flowParams, startNode, instance.getId()); + + List addTasks = StreamUtils.toList(nextNodes, node -> FlowEngine.taskService() + .addTask(node, instance, definition, flowParams)); + + // 办理人变量替换 + if (CollUtil.isNotEmpty(addTasks)) { + ExpressionUtil.evalVariable(addTasks, flowParams); + } + + // 设置流程图元数据 + pathWayData.getTargetNodes().addAll(nextNodes); + instance.setDefJson(FlowEngine.chartService().startMetadata(pathWayData)); + + // 执行分派监听器 + ListenerUtil.executeListener(new ListenerVariable(definition, instance, startNode, flowParams.getVariable() + , null, nextNodes, addTasks).setFlowParams(flowParams), Listener.LISTENER_ASSIGNMENT); + + // 开启流程,保存流程信息 + saveFlowInfo(instance, addTasks, hisTask, flowParams); + + // 执行完成和创建监听器 + ListenerUtil.endCreateListener(new ListenerVariable(definition, instance, startNode, flowParams.getVariable() + , null, nextNodes, addTasks).setFlowParams(flowParams)); + + return instance; + } + + @Override + public List listByDefIds(List defIds) { + return getDao().getByDefIds(defIds); + } + + @Override + public boolean remove(List instanceIds) { + return toRemoveTask(instanceIds); + } + + @Override + public List getByDefId(Long definitionId) { + return list(FlowEngine.newIns().setDefinitionId(definitionId)); + } + + /** + * 设置历史任务 + * + * @param nextNodes 下一节点集合 + * @param flowParams 流程参数 + * @param startNode 开始节点 + * @param instanceId 流程实例id + */ + private HisTask setHisTask(List nextNodes, FlowParams flowParams, Node startNode, Long instanceId) { + Task startTask = FlowEngine.newTask() + .setInstanceId(instanceId) + .setDefinitionId(startNode.getDefinitionId()) + .setNodeCode(startNode.getNodeCode()) + .setNodeName(startNode.getNodeName()) + .setNodeType(startNode.getNodeType()); + FlowEngine.dataFillHandler().idFill(startTask); + // 开始任务转历史任务 + return FlowEngine.hisTaskService().setSkipInsHis(startTask, nextNodes, flowParams); + } + + /** + * 开启流程,保存流程信息 + * + * @param instance 流程实例 + * @param addTasks 新增任务 + * @param hisTask 历史任务 + */ + private void saveFlowInfo(Instance instance, List addTasks, HisTask hisTask, FlowParams flowParams) { + FlowEngine.taskService().setInsFinishInfo(instance, addTasks, flowParams); + // 待办任务设置处理人 + if (CollUtil.isNotEmpty(addTasks)) { + List users = FlowEngine.userService().taskAddUsers(addTasks); + FlowEngine.taskService().saveBatch(addTasks); + FlowEngine.userService().saveBatch(users); + } + FlowEngine.hisTaskService().save(hisTask); + save(instance); + } + + /** + * 设置流程实例对象 + * + * @param firstBetweenNode 第一个中间节点 + * @param businessId 业务id + * @return Instance + */ + private Instance setStartInstance(Node firstBetweenNode, String businessId + , FlowParams flowParams) { + Instance instance = FlowEngine.newIns(); + Date now = new Date(); + FlowEngine.dataFillHandler().idFill(instance); + // 关联业务id,其实后面可以不用到业务id,传业务id目前来看只是为了批量创建流程的时候能创建出有区别化的流程,也是为了后期需要用到businessId。 + instance.setDefinitionId(firstBetweenNode.getDefinitionId()) + .setBusinessId(businessId) + .setNodeType(firstBetweenNode.getNodeType()) + .setNodeCode(firstBetweenNode.getNodeCode()) + .setNodeName(firstBetweenNode.getNodeName()) + .setFlowStatus(StringUtils.emptyDefault(flowParams.getFlowStatus(), FlowStatus.TOBESUBMIT.getKey())) + .setActivityStatus(ActivityStatus.ACTIVITY.getKey()) + .setVariable(FlowEngine.jsonConvert.objToStr(flowParams.getVariable())) + .setCreateTime(now) + .setUpdateTime(now) + .setCreateBy(flowParams.getHandler()) + .setUpdateBy(flowParams.getHandler()) + .setExt(flowParams.getExt()); + return instance; + } + + private boolean toRemoveTask(List instanceIds) { + AssertUtil.isEmpty(instanceIds, ExceptionCons.NULL_INSTANCE_ID); + + List taskIds = new ArrayList<>(); + instanceIds.forEach(instanceId -> taskIds.addAll( + FlowEngine.taskService() + .list(FlowEngine.newTask().setInstanceId(instanceId)) + .stream() + .map(Task::getId) + .collect(Collectors.toList()))); + + if (CollUtil.isNotEmpty(taskIds)) { + FlowEngine.userService().deleteByTaskIds(taskIds); + } + + FlowEngine.taskService().deleteByInsIds(instanceIds); + FlowEngine.hisTaskService().deleteByInsIds(instanceIds); + return FlowEngine.insService().removeByIds(instanceIds); + } + + @Override + public boolean active(Long id) { + Instance instance = getById(id); + AssertUtil.isTrue(ActivityStatus.isActivity(instance.getActivityStatus()), ExceptionCons.INSTANCE_ALREADY_ACTIVITY); + instance.setActivityStatus(ActivityStatus.ACTIVITY.getKey()); + return updateById(instance); + } + + @Override + public boolean unActive(Long id) { + Instance instance = getById(id); + AssertUtil.isTrue(ActivityStatus.isSuspended(instance.getActivityStatus()), ExceptionCons.INSTANCE_ALREADY_ACTIVITY); + instance.setActivityStatus(ActivityStatus.SUSPENDED.getKey()); + return updateById(instance); + } + + @Override + public void removeVariables(Long instanceId, String... keys) { + Instance instance = FlowEngine.insService().getById(instanceId); + if (instance != null) { + Map variableMap = instance.getVariableMap(); + for (String key : keys) { + variableMap.remove(key); + } + instance.setVariable(FlowEngine.jsonConvert.objToStr(variableMap)); + FlowEngine.insService().updateById(instance); + } + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/impl/NodeServiceImpl.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/impl/NodeServiceImpl.java new file mode 100644 index 0000000..3423068 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/impl/NodeServiceImpl.java @@ -0,0 +1,347 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.service.impl; + +import lombok.extern.slf4j.Slf4j; +import org.dromara.warm.flow.core.FlowEngine; +import org.dromara.warm.flow.core.constant.ExceptionCons; +import org.dromara.warm.flow.core.constant.FlowCons; +import org.dromara.warm.flow.core.dto.FlowCombine; +import org.dromara.warm.flow.core.dto.PathWayData; +import org.dromara.warm.flow.core.entity.Definition; +import org.dromara.warm.flow.core.entity.Node; +import org.dromara.warm.flow.core.entity.Skip; +import org.dromara.warm.flow.core.enums.NodeType; +import org.dromara.warm.flow.core.enums.PublishStatus; +import org.dromara.warm.flow.core.enums.SkipType; +import org.dromara.warm.flow.core.exception.FlowException; +import org.dromara.warm.flow.core.orm.dao.FlowNodeDao; +import org.dromara.warm.flow.core.orm.service.impl.WarmServiceImpl; +import org.dromara.warm.flow.core.service.NodeService; +import org.dromara.warm.flow.core.utils.*; + +import java.io.Serializable; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 流程节点Service业务层处理 + * + * @author warm + * @since 2023-03-29 + */ +@Slf4j +public class NodeServiceImpl extends WarmServiceImpl, Node> implements NodeService { + + @Override + public NodeService setDao(FlowNodeDao warmDao) { + this.warmDao = warmDao; + return this; + } + + @Override + public List getPublishByFlowCode(String flowCode) { + Definition definition = FlowEngine.defService().getOne(FlowEngine.newDef() + .setFlowCode(flowCode).setIsPublish(PublishStatus.PUBLISHED.getKey())); + if (ObjectUtil.isNotNull(definition)) { + return list(FlowEngine.newNode().setDefinitionId(definition.getId())); + } + return Collections.emptyList(); + } + + @Override + public List getByNodeCodes(List nodeCodes, Long definitionId) { + return getDao().getByNodeCodes(nodeCodes, definitionId); + } + + @Override + public List previousNodeList(Long nodeId) { + Node nowNode = getById(nodeId); + return previousNodeList(nowNode.getDefinitionId(), nowNode.getNodeCode()); + } + + @Override + public List previousNodeList(Long definitionId, String nowNodeCode) { + return prefixOrSuffixNodes(definitionId, nowNodeCode, FlowCons.PREVIOUS); + } + + @Override + public List suffixNodeList(Long nodeId) { + Node nowNode = getById(nodeId); + return suffixNodeList(nowNode.getDefinitionId(), nowNode.getNodeCode()); + } + + @Override + public List suffixNodeList(Long definitionId, String nowNodeCode) { + return prefixOrSuffixNodes(definitionId, nowNodeCode, FlowCons.SUFFIX); + } + + @Override + public List suffixNodeList(String nowNodeCode, FlowCombine flowCombine) { + return prefixOrSuffixNodes(nowNodeCode, FlowCons.SUFFIX, flowCombine); + } + + @Override + public List getByDefId(Long definitionId) { + return list(FlowEngine.newNode().setDefinitionId(definitionId)); + } + + @Override + public Node getByDefIdAndNodeCode(Long definitionId, String nodeCode) { + return getOne(FlowEngine.newNode().setDefinitionId(definitionId).setNodeCode(nodeCode)); + } + + @Override + public Node getStartNode(Long definitionId) { + return getOne(FlowEngine.newNode().setDefinitionId(definitionId).setNodeType(NodeType.START.getKey())); + } + + @Override + public List getBetweenNode(Long definitionId) { + return list(FlowEngine.newNode().setDefinitionId(definitionId).setNodeType(NodeType.BETWEEN.getKey())); + } + + + @Override + public List getFirstBetweenNode(Long definitionId, Map variable) { + FlowCombine flowCombine = FlowEngine.defService().getFlowCombineNoDef(definitionId); + Node startNode = StreamUtils.filterOne(flowCombine.getAllNodes(), t -> NodeType.isStart(t.getNodeType())); + return getNextNodeList(startNode, null, SkipType.PASS.getKey(), + variable, null, flowCombine); + } + + @Override + public Node getEndNode(Long definitionId) { + return getOne(FlowEngine.newNode().setDefinitionId(definitionId).setNodeType(NodeType.END.getKey())); + } + + public List prefixOrSuffixNodes(Long definitionId, String nowNodeCode, String type) { + FlowCombine flowCombine = new FlowCombine(); + flowCombine.setAllNodes(FlowEngine.nodeService().getByDefId(definitionId)); + flowCombine.setAllSkips(FlowEngine.skipService().getByDefId(definitionId)); + return prefixOrSuffixNodes(nowNodeCode, type, flowCombine); + } + + public List prefixOrSuffixNodes(String nowNodeCode, String type, FlowCombine flowCombine) { + Map nodeMap = StreamUtils.toMap(flowCombine.getAllNodes(), Node::getNodeCode, node -> node); + Map> skipMap = flowCombine.getAllSkips().stream().filter(skip -> SkipType.isPass(skip.getSkipType())) + .collect(Collectors.groupingBy(FlowCons.PREVIOUS.equals(type) ? Skip::getNextNodeCode : Skip::getNowNodeCode + , LinkedHashMap::new, Collectors.toList())); + + List prefixOrSuffixNodes = new ArrayList<>(); + List prefixOrSuffixCode = prefixOrSuffixCodes(skipMap, nowNodeCode + , FlowCons.PREVIOUS.equals(type) ? Skip::getNowNodeCode : Skip::getNextNodeCode); + for (String nodeCode : prefixOrSuffixCode) { + Node node = nodeMap.get(nodeCode); + if (!NodeType.isGateWay(node.getNodeType())) { + prefixOrSuffixNodes.add(node); + } + } + Collections.reverse(prefixOrSuffixNodes); + Set sameCode = new HashSet<>(); + prefixOrSuffixNodes.removeIf(node -> { + if (sameCode.contains(node.getNodeCode())) { + return true; + } + sameCode.add(node.getNodeCode()); + return false; + }); + Collections.reverse(prefixOrSuffixNodes); + return prefixOrSuffixNodes; + } + + @Override + public List getNextNodeList(Long definitionId, String nowNodeCode, String anyNodeCode, String skipType, + Map variable) { + AssertUtil.isEmpty(nowNodeCode, ExceptionCons.LOST_NODE_CODE); + // 查询当前节点 + FlowCombine flowCombine = FlowEngine.defService().getFlowCombineNoDef(definitionId); + Node nowNode = StreamUtils.filterOne(flowCombine.getAllNodes(), t -> t.getNodeCode().equals(nowNodeCode)); + // 如果是网关节点,则根据条件判断 + return getNextByCheckGateway(variable, getNextNode(nowNode, anyNodeCode, skipType, null, flowCombine), + null, flowCombine); + } + + @Override + public Node getNextNode(Long definitionId, String nowNodeCode, String anyNodeCode, String skipType) { + // 查询当前节点 + FlowCombine flowCombine = FlowEngine.defService().getFlowCombineNoDef(definitionId); + Node nowNode = StreamUtils.filterOne(flowCombine.getAllNodes(), t -> t.getNodeCode().equals(nowNodeCode)); + return getNextNode(nowNode, anyNodeCode, skipType, null, flowCombine); + } + + @Override + public List getNextNodeList(Node nowNode, String anyNodeCode, String skipType, Map variable + , PathWayData pathWayData, FlowCombine flowCombine) { + // 如果是网关节点,则根据条件判断 + return getNextByCheckGateway(variable, getNextNode(nowNode, anyNodeCode, skipType + , pathWayData, flowCombine), pathWayData, flowCombine); + } + + @Override + public Node getNextNode(Node nowNode, String anyNodeCode, String skipType, PathWayData pathWayData, FlowCombine flowCombine) { + // 查询当前节点 + AssertUtil.isNull(nowNode, ExceptionCons.LOST_NODE_CODE); + AssertUtil.isNull(nowNode.getDefinitionId(), ExceptionCons.NOT_DEFINITION_ID); + AssertUtil.isEmpty(skipType, ExceptionCons.NULL_CONDITION_VALUE); + + if (pathWayData != null) { + pathWayData.getPathWayNodes().add(nowNode); + } + Node nextNode = null; + if (StringUtils.isNotEmpty(anyNodeCode)) { + // 如果指定了跳转节点,直接获取节点 + nextNode = StreamUtils.filterOne(flowCombine.getAllNodes(), node -> anyNodeCode.equals(node.getNodeCode())); + } else if (StringUtils.isNotEmpty(nowNode.getAnyNodeSkip()) && SkipType.isReject(skipType)) { + // 如果配置了任意跳转节点,直接获取节点 + nextNode = StreamUtils.filterOne(flowCombine.getAllNodes(), node -> nowNode.getAnyNodeSkip().equals(node.getNodeCode())); + } + + if (ObjectUtil.isNotNull(nextNode)) { + AssertUtil.isTrue(NodeType.isGateWay(nextNode.getNodeType()), ExceptionCons.TAR_NOT_GATEWAY); + return nextNode; + } + + // 获取跳转关系 + List skips = StreamUtils.filter(flowCombine.getAllSkips(), skip -> nowNode.getNodeCode().equals(skip.getNowNodeCode())); + AssertUtil.isNull(skips, ExceptionCons.NULL_DEST_NODE); + Skip nextSkip = getSkipByCheck(skips, skipType); + + // 根据跳转查询出跳转到的那个节点 + nextNode = StreamUtils.filterOne(flowCombine.getAllNodes(), node -> nextSkip != null && nextSkip.getNextNodeCode().equals(node.getNodeCode())); + AssertUtil.isNull(nextNode, ExceptionCons.NULL_NODE_CODE); + AssertUtil.isTrue(NodeType.isStart(nextNode.getNodeType()), ExceptionCons.FIRST_FORBID_BACK); + if (pathWayData != null) { + pathWayData.getPathWayNodes().add(nextNode); + pathWayData.getPathWaySkips().add(nextSkip); + } + return nextNode; + } + + @Override + public List getNextByCheckGateway(Map variable, Node nextNode, PathWayData pathWayData + , FlowCombine flowCombine) { + // 网关节点处理 + if (NodeType.isGateWay(nextNode.getNodeType())) { + List skipsGateway = StreamUtils.filter(flowCombine.getAllSkips() + , skip -> nextNode.getNodeCode().equals(skip.getNowNodeCode())); + if (CollUtil.isEmpty(skipsGateway)) { + return null; + } + + //如果是互斥网关,跳转条件匹配的,则取任意第一条,否则取跳转条件为空的任意一条 + if (NodeType.isGateWaySerial(nextNode.getNodeType())) { + Skip skipOne = null; + for (Skip skip : skipsGateway) { + if (StringUtils.isNotEmpty(skip.getSkipCondition())) { + if (ExpressionUtil.evalCondition(skip.getSkipCondition(), variable)) { + skipOne = skip; + break; + } + } else { + skipOne = skip; + } + } + skipsGateway = skipOne == null ? null : CollUtil.toList(skipOne); + } else if (NodeType.isGateWayInclusive(nextNode.getNodeType())) { + //如果是包含网关,有跳转条件的分支,但是跳转条件不匹配的不执行,没跳转条件为空的分支默认执行 + skipsGateway.removeIf(skip -> StringUtils.isNotEmpty(skip.getSkipCondition()) + && !ExpressionUtil.evalCondition(skip.getSkipCondition(), variable)); + } + + AssertUtil.isEmpty(skipsGateway, ExceptionCons.NULL_CONDITION_VALUE_NODE); + List nextNodeCodes = StreamUtils.toList(skipsGateway, Skip::getNextNodeCode); + List nextNodes = StreamUtils.filter(flowCombine.getAllNodes() + , node -> nextNodeCodes.contains(node.getNodeCode())); + AssertUtil.isEmpty(nextNodes, ExceptionCons.NOT_NODE_DATA); + if (pathWayData != null) { + pathWayData.getPathWayNodes().addAll(nextNodes); + pathWayData.getPathWaySkips().addAll(skipsGateway); + } + List newNextNodes = new ArrayList<>(); + for (Node node : nextNodes) { + List nodeList = getNextByCheckGateway(variable, node, pathWayData, flowCombine); + newNextNodes.addAll(nodeList); + } + return newNextNodes; + } + // 非网关节点直接返回 + if (pathWayData != null) { + pathWayData.getPathWayNodes().remove(nextNode); + } + AssertUtil.isTrue(NodeType.isStart(nextNode.getNodeType()), ExceptionCons.START_NODE_NOT_ALLOW_JUMP); + return CollUtil.toList(nextNode); + } + + + @Override + public int deleteNodeByDefIds(Collection defIds) { + return getDao().deleteNodeByDefIds(defIds); + } + + private List prefixOrSuffixCodes(Map> skipMap, String nodeCode, + Function supplier) { + // 记录已访问节点,防止循环 + Set visited = new HashSet<>(); + List result = new ArrayList<>(); + prefixOrSuffixCodesRecursive(skipMap, nodeCode, supplier, visited, result); + return result; + } + + private void prefixOrSuffixCodesRecursive(Map> skipMap, String nodeCode, + Function supplier, Set visited, List result) { + if (visited.contains(nodeCode)) { + // 防止循环访问 + return; + } + + visited.add(nodeCode); + List skipList = skipMap.get(nodeCode); + + if (CollUtil.isNotEmpty(skipList)) { + for (Skip skip : skipList) { + if (SkipType.isPass(skip.getSkipType())) { + String nextNodeCode = supplier.apply(skip); + // 避免重复添加 + if (!result.contains(nextNodeCode)) { + result.add(nextNodeCode); + } + prefixOrSuffixCodesRecursive(skipMap, nextNodeCode, supplier, visited, result); + } + } + } + } + + + /** + * 通过校验跳转类型获取跳转集合 + * + * @param skips 跳转集合 + * @param skipType 跳转类型 + * @return List + * @author xiarg + * @since 2024/8/21 11:32 + */ + private Skip getSkipByCheck(List skips, String skipType) { + return Optional.ofNullable(skips) + .orElse(Collections.emptyList()) + .stream() + .filter(t -> StringUtils.isEmpty(t.getSkipType()) || skipType.equals(t.getSkipType())) + .findFirst() + .orElseThrow(() -> new FlowException(ExceptionCons.NULL_SKIP_TYPE)); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/impl/SkipServiceImpl.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/impl/SkipServiceImpl.java new file mode 100644 index 0000000..6496625 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/impl/SkipServiceImpl.java @@ -0,0 +1,56 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.service.impl; + +import org.dromara.warm.flow.core.FlowEngine; +import org.dromara.warm.flow.core.entity.Skip; +import org.dromara.warm.flow.core.orm.dao.FlowSkipDao; +import org.dromara.warm.flow.core.orm.service.impl.WarmServiceImpl; +import org.dromara.warm.flow.core.service.SkipService; + +import java.io.Serializable; +import java.util.Collection; +import java.util.List; + +/** + * 节点跳转关联Service业务层处理 + * + * @author warm + * @since 2023-03-29 + */ +public class SkipServiceImpl extends WarmServiceImpl, Skip> implements SkipService { + + @Override + public SkipService setDao(FlowSkipDao warmDao) { + this.warmDao = warmDao; + return this; + } + + @Override + public int deleteSkipByDefIds(Collection defIds) { + return getDao().deleteSkipByDefIds(defIds); + } + + @Override + public List getByDefId(Long definitionId) { + return list(FlowEngine.newSkip().setDefinitionId(definitionId)); + } + + @Override + public List getByDefIdAndNowNodeCode(Long definitionId, String nodeCode) { + return list(FlowEngine.newSkip().setDefinitionId(definitionId).setNowNodeCode(nodeCode)); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/impl/TaskServiceImpl.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/impl/TaskServiceImpl.java new file mode 100644 index 0000000..c32b57b --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/impl/TaskServiceImpl.java @@ -0,0 +1,1004 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.service.impl; + +import org.dromara.warm.flow.core.FlowEngine; +import org.dromara.warm.flow.core.constant.ExceptionCons; +import org.dromara.warm.flow.core.constant.FlowCons; +import org.dromara.warm.flow.core.dto.*; +import org.dromara.warm.flow.core.entity.*; +import org.dromara.warm.flow.core.enums.*; +import org.dromara.warm.flow.core.listener.Listener; +import org.dromara.warm.flow.core.listener.ListenerVariable; +import org.dromara.warm.flow.core.orm.dao.FlowTaskDao; +import org.dromara.warm.flow.core.orm.service.impl.WarmServiceImpl; +import org.dromara.warm.flow.core.service.TaskService; +import org.dromara.warm.flow.core.utils.*; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +/** + * 待办任务Service业务层处理 + * + * @author warm + * @since 2023-03-29 + */ +public class TaskServiceImpl extends WarmServiceImpl, Task> implements TaskService { + + @Override + public TaskService setDao(FlowTaskDao warmDao) { + this.warmDao = warmDao; + return this; + } + + @Override + public Instance pass(Long taskId, String message, Map variable) { + return skip(taskId, new FlowParams(SkipType.PASS.getKey(), message, variable)); + } + + @Override + public Instance passAtWill(Long taskId, String nodeCode, String message, Map variable) { + return skip(taskId, new FlowParams(nodeCode, SkipType.PASS.getKey(), message, variable)); + } + + @Override + public Instance pass(Long taskId, String message, Map variable, String flowStatus, String hisStatus) { + return skip(taskId, new FlowParams(SkipType.PASS.getKey(), message, variable, flowStatus, hisStatus)); + } + + @Override + public Instance passAtWill(Long taskId, String nodeCode, String message, Map variable + , String flowStatus, String hisStatus) { + return skip(taskId, new FlowParams(nodeCode, SkipType.PASS.getKey(), message, variable, flowStatus, hisStatus)); + } + + + @Override + public Instance reject(Long taskId, String message, Map variable) { + return skip(taskId, new FlowParams(SkipType.REJECT.getKey(), message, variable)); + } + + @Override + public Instance rejectAtWill(Long taskId, String nodeCode, String message, Map variable) { + return skip(taskId, new FlowParams(nodeCode, SkipType.REJECT.getKey(), message, variable)); + } + + @Override + public Instance reject(Long taskId, String message, Map variable, String flowStatus, String hisStatus) { + return skip(taskId, new FlowParams(SkipType.REJECT.getKey(), message, variable, flowStatus, hisStatus)); + } + + @Override + public Instance rejectAtWill(Long taskId, String nodeCode, String message, Map variable + , String flowStatus, String hisStatus) { + return skip(taskId, new FlowParams(nodeCode, SkipType.REJECT.getKey(), message, variable, flowStatus, hisStatus)); + } + + + @Override + public Instance skip(Long taskId, FlowParams flowParams) { + // 获取待办任务 + Task task = getById(taskId); + return skip(flowParams, task); + } + + @Override + public Instance skipByInsId(Long instanceId, FlowParams flowParams) { + return skip(flowParams, getTask(instanceId)); + } + + @Override + public Instance rejectLastByInsId(Long instanceId, FlowParams flowParams) { + return rejectLast(getTask(instanceId), flowParams); + } + + @Override + public Instance rejectLast(Long taskId, FlowParams flowParams) { + return rejectLast(getById(taskId), flowParams); + } + + @Override + public Instance rejectLast(Task task, FlowParams flowParams) { + flowParams.skipType(SkipType.REJECT.getKey()); + AssertUtil.isNull(task, ExceptionCons.NOT_FOUNT_TASK); + // 获取当前任务的前置任务 + List hisTaskList = FlowEngine.hisTaskService().getByInsId(task.getInstanceId()); + // 获取hisTaskList中TargetNodeCod等于task.getNodeCode()的,并且id最大的 + HisTask lastHisTask = hisTaskList.stream() + .filter(hisTask -> StringUtils.isNotEmpty(hisTask.getTargetNodeCode())) + .filter(hisTask -> SkipType.isPass(hisTask.getSkipType())) + .filter(hisTask -> { + String targetCode = hisTask.getTargetNodeCode(); + if (targetCode.contains(",")) { + return Arrays.asList(targetCode.split(",")).contains(task.getNodeCode()); + } else { + return targetCode.equals(task.getNodeCode()); + } + }) + .max(Comparator.comparingLong(HisTask::getId)) + .orElse(null); + + AssertUtil.isNull(lastHisTask, ExceptionCons.NOT_FOUNT_LAST_TASK); + flowParams.nodeCode(lastHisTask.getNodeCode()); + return skip(flowParams, task); + } + + @Override + public Instance taskBackByInsId(Long instanceId, FlowParams flowParams) { + // 获取当前任务的前置任务 + HisTask lastHisTask = taskBack(flowParams, instanceId); + List suffixNodeList = FlowEngine.nodeService().suffixNodeList(lastHisTask.getDefinitionId() + , lastHisTask.getNodeCode()); + + List suffixNodeCodes = StreamUtils.toList(suffixNodeList, Node::getNodeCode); + List taskList = FlowEngine.taskService().getByInsIdAndNodeCodes(instanceId, suffixNodeCodes); + AssertUtil.isEmpty(taskList, ExceptionCons.NOT_FOUNT_HANDLED_TASK_HANDLER); + return skip(flowParams, taskList.get(0)); + } + + @Override + public Instance taskBack(Long taskId, FlowParams flowParams) { + Task task = getById(taskId); + AssertUtil.isNull(task, ExceptionCons.NOT_FOUNT_TASK); + taskBack(flowParams, task.getInstanceId()); + return skip(flowParams, task); + } + + @Override + public Instance skip(FlowParams flowParams, Task task) { + // TODO min 后续考虑并发问题,待办任务和实例表不同步,可给待办任务id加锁,抽取所接口,方便后续兼容分布式锁 + // 流程开启前正确性校验 + R r = getAndCheck(task); + flowParams.variable(MapUtil.mergeAll(r.instance.getVariableMap(), flowParams.getVariable())); + // 非第一个记得跳转类型必传 + if (!NodeType.isStart(task.getNodeType())) { + AssertUtil.isFalse(StringUtils.isNotEmpty(flowParams.getSkipType()), ExceptionCons.NULL_CONDITION_VALUE); + } + task.setUserList(FlowEngine.userService().listByAssociatedAndTypes(task.getId())); + FlowCombine flowCombine = FlowEngine.defService().getFlowCombineNoDef(r.definition.getId()); + + // 执行开始监听器 + ListenerUtil.executeListener(new ListenerVariable(r.definition, r.instance, r.nowNode, flowParams.getVariable() + , task).setFlowParams(flowParams), Listener.LISTENER_START); + + // 如果是受托人在处理任务,需要处理一条委派记录,并且更新委托人,回到计划审批人,然后直接返回流程实例 + if (!flowParams.isIgnoreDepute() && handleDepute(task, flowParams)) { + return r.instance; + } + + // 判断当前处理人是否有权限处理 + checkAuth(task, flowParams); + + //或签、会签、票签逻辑处理 + if (!flowParams.isIgnoreCooperate() && cooperate(r.nowNode, task, flowParams)) { + return r.instance; + } + + // 获取后续任务节点结合 + PathWayData pathWayData = new PathWayData().setInsId(task.getInstanceId()).setSkipType(flowParams.getSkipType()); + Node nextNode = FlowEngine.nodeService().getNextNode(r.nowNode, flowParams.getNodeCode() + , flowParams.getSkipType(), pathWayData, flowCombine); + List nextNodes = FlowEngine.nodeService().getNextByCheckGateway(flowParams.getVariable() + , nextNode, pathWayData, flowCombine); + + // 判断并行网关和包容网关节点只剩一个前置代办任务,才能生成新的代办任务 + isGenerateNewTask(pathWayData, r.instance, nextNodes); + pathWayData.getTargetNodes().addAll(nextNodes); + + // 设置流程图元数据 + r.instance.setDefJson(FlowEngine.chartService().skipMetadata(pathWayData)); + + // 构建增待办任务和设置结束任务历史记录 + List addTasks = StreamUtils.toList(nextNodes, node -> addTask(node, r.instance, r.definition, flowParams)); + + // 办理人变量替换 + ExpressionUtil.evalVariable(addTasks, flowParams.variable(MapUtil.mergeAll(r.instance.getVariableMap(), flowParams.getVariable()))); + + // 执行分派监听器 + ListenerUtil.executeListener(new ListenerVariable(r.definition, r.instance, r.nowNode, flowParams.getVariable() + , task, nextNodes, addTasks).setFlowParams(flowParams), Listener.LISTENER_ASSIGNMENT); + + // 更新流程信息 + updateFlowInfo(task, r.instance, addTasks, flowParams, nextNodes); + + // 一票否决(谨慎使用),如果退回,退回指向节点后还存在其他正在执行的待办任务,转历史任务,状态都为失效,重走流程。 + if (CollUtil.isNotEmpty(nextNodes) && SkipType.isReject(flowParams.getSkipType())) { + oneVoteVeto(task, nextNodes.get(0).getNodeCode(), flowCombine); + } + + // 处理未完成的任务,当流程完成,还存在待办任务未完成,转历史任务,状态完成。 + handUndoneTask(r.instance); + + // 执行完成和创建监听器 + ListenerUtil.endCreateListener(new ListenerVariable(r.definition, r.instance, r.nowNode + , flowParams.getVariable(), task, nextNodes, addTasks).setFlowParams(flowParams)); + + return r.instance; + } + + @Override + public Instance revoke(Long instanceId, FlowParams flowParams) { + flowParams.skipType(SkipType.REJECT.getKey()); + // 删除待办任务,保存历史,删除所有代办任务的权限人 + if (StringUtils.isEmpty(flowParams.getFlowStatus())) { + flowParams.flowStatus(FlowStatus.CANCEL.getKey()); + } + + Instance instance = FlowEngine.insService().getById(instanceId); + flowParams.variable(MapUtil.mergeAll(instance.getVariableMap(), flowParams.getVariable())); + AssertUtil.isNull(instance, ExceptionCons.NOT_FOUNT_INSTANCE); + Definition definition = FlowEngine.defService().getById(instance.getDefinitionId()); + AssertUtil.isFalse(judgeActivityStatus(definition, instance), ExceptionCons.NOT_ACTIVITY); + AssertUtil.isTrue(NodeType.isEnd(instance.getNodeType()), ExceptionCons.FLOW_FINISH); + flowParams.variable(MapUtil.mergeAll(instance.getVariableMap(), flowParams.getVariable())); + + List taskList = getByInsId(instanceId); + FlowCombine flowCombine = FlowEngine.defService().getFlowCombine(definition); + Map nodeMap = StreamUtils.toMap(flowCombine.getAllNodes(), Node::getNodeCode, node -> node); + // 执行开始监听器 + taskList.forEach(task -> ListenerUtil.executeListener(new ListenerVariable(definition, instance + , nodeMap.get(task.getNodeCode()), flowParams.getVariable(), task).setFlowParams(flowParams) + , Listener.LISTENER_START)); + + // 验证权限是不是当前任务的发起人 + if (!flowParams.isIgnore()) { + AssertUtil.isFalse(instance.getCreateBy().equals(flowParams.getHandler()) + , ExceptionCons.NOT_DEF_PROMOTER_NOT_CANCEL); + } + + // 获取开始节点 + Node startNode = StreamUtils.filterOne(flowCombine.getAllNodes(), node -> NodeType.isStart(node.getNodeType())); + // 获取下一个节点,如果是网关节点,则重新获取后续节点 + PathWayData pathWayData = new PathWayData().setInsId(instanceId).setSkipType(flowParams.getSkipType()); + Node nextNode = FlowEngine.nodeService().getNextNode(startNode, null, SkipType.PASS.getKey() + , null, flowCombine); + List nextNodes = FlowEngine.nodeService().getNextByCheckGateway(flowParams.getVariable(), nextNode + , pathWayData, flowCombine); + pathWayData.getTargetNodes().addAll(nextNodes); + // 设置流程图元数据 + instance.setDefJson(FlowEngine.chartService().skipMetadata(pathWayData)); + + // 查询任务,如果前一个节点是并行网关,可能任务表有多个任务,增加查询和判断 + List curTaskList = list(FlowEngine.newTask().setInstanceId(instance.getId())); + AssertUtil.isEmpty(curTaskList, ExceptionCons.NOT_FOUND_FLOW_TASK); + + // 给回退到的那个节点赋权限-给当前处理人权限 + List addTasks = StreamUtils.toList(nextNodes, node -> addTask(node, instance, definition, flowParams)); + + // 办理人变量替换 + ExpressionUtil.evalVariable(addTasks, flowParams.variable(MapUtil.mergeAll(instance.getVariableMap(), flowParams.getVariable()))); + + // 执行分派监听器 + taskList.forEach(task -> ListenerUtil.executeListener(new ListenerVariable(definition, instance, + nodeMap.get(task.getNodeCode()), flowParams.getVariable(), task, nextNodes, addTasks) + .setFlowParams(flowParams), Listener.LISTENER_ASSIGNMENT)); + + // 设置流程历史任务信息 + List insHisList = FlowEngine.hisTaskService().setSkipHisList(curTaskList, nextNodes, flowParams); + FlowEngine.hisTaskService().saveBatch(insHisList); + // 待办任务和处理人 + removeAndUser(curTaskList); + List users = FlowEngine.userService().taskAddUsers(addTasks); + + // 设置任务完成后的实例相关信息 + setInsFinishInfo(instance, addTasks, flowParams); + if (CollUtil.isNotEmpty(addTasks)) { + saveBatch(addTasks); + } + FlowEngine.insService().updateById(instance); + // 保存下一个待办任务的权限人 + FlowEngine.userService().saveBatch(users); + + // 执行完成和创建监听器 + taskList.forEach(task -> ListenerUtil.endCreateListener(new ListenerVariable(definition, instance, + nodeMap.get(task.getNodeCode()), flowParams.getVariable(), task, nextNodes, addTasks).setFlowParams(flowParams))); + return instance; + } + + @Override + public Instance terminationByInsId(Long instanceId, FlowParams flowParams) { + // 获取待办任务 + List taskList = FlowEngine.taskService().getByInsId(instanceId); + AssertUtil.isEmpty(taskList, ExceptionCons.NOT_FOUNT_TASK); + Task task = taskList.get(0); + return termination(task, flowParams); + } + + @Override + public Instance termination(Long taskId, FlowParams flowParams) { + return termination(getById(taskId), flowParams); + } + + @Override + public Instance termination(Task task, FlowParams flowParams) { + R r = getAndCheck(task); + flowParams.skipType(SkipType.PASS.getKey()); + flowParams.variable(MapUtil.mergeAll(r.instance.getVariableMap(), flowParams.getVariable())); + ListenerUtil.executeListener(new ListenerVariable(r.definition, r.instance, r.nowNode, flowParams.getVariable() + , task).setFlowParams(flowParams), Listener.LISTENER_START); + + // 判断当前处理人是否有权限处理 + task.setUserList(FlowEngine.userService().listByAssociatedAndTypes(task.getId())); + checkAuth(task, flowParams); + + // 所有待办转历史 + Node endNode = FlowEngine.nodeService().getEndNode(r.instance.getDefinitionId()); + + // 设置流程图元数据 + PathWayData pathWayData = new PathWayData() + .setInsId(task.getInstanceId()) + .setSkipType(flowParams.getSkipType()) + .setPathWayNodes(Collections.singletonList(r.nowNode)) + .setTargetNodes(Collections.singletonList(endNode)); + r.instance.setDefJson(FlowEngine.chartService().skipMetadata(pathWayData)); + + // 流程实例完成 + r.instance.setNodeType(endNode.getNodeType()) + .setNodeCode(endNode.getNodeCode()) + .setNodeName(endNode.getNodeName()) + .setFlowStatus(StringUtils.emptyDefault(flowParams.getFlowStatus(), FlowStatus.TERMINATE.getKey())); + + // 待办任务转历史 + flowParams.flowStatus(r.instance.getFlowStatus()); + HisTask insHis = FlowEngine.hisTaskService().setSkipInsHis(task, Collections.singletonList(endNode) + , flowParams); + FlowEngine.hisTaskService().save(insHis); + FlowEngine.insService().updateById(r.instance); + + // 删除流程相关办理人 + FlowEngine.userService().deleteByTaskIds(Collections.singletonList(task.getId())); + + // 处理未完成的任务,当流程完成,还存在待办任务未完成,转历史任务,状态完成。 + handUndoneTask(r.instance); + // 最后判断是否存在节点监听器,存在执行节点监听器 + ListenerUtil.executeListener(new ListenerVariable(r.definition, r.instance, r.nowNode, flowParams.getVariable() + , task).setFlowParams(flowParams), Listener.LISTENER_FINISH); + return r.instance; + } + + @Override + public boolean deleteByInsIds(List instanceIds) { + List instanceList = FlowEngine.insService().getByIds(instanceIds); + Definition definition; + for (Instance instance : instanceList) { + definition = FlowEngine.defService().getById(instance.getDefinitionId()); + AssertUtil.isFalse(judgeActivityStatus(definition, instance), ExceptionCons.NOT_ACTIVITY); + } + return SqlHelper.retBool(getDao().deleteByInsIds(instanceIds)); + } + + @Override + public boolean transfer(Long taskId, FlowParams flowParams) { + AssertUtil.isNull(taskId, ExceptionCons.NULL_TASK_ID); + AssertUtil.isNull(flowParams.getHandler(), ExceptionCons.HANDLER_NOT_EMPTY); + AssertUtil.isNull(flowParams.getAddHandlers(), ExceptionCons.NULL_TRANSFER_HANDLER); + List users = FlowEngine.userService().getByProcessedBys(taskId, flowParams.getAddHandlers(), UserType.TRANSFER.getKey()); + AssertUtil.isNotEmpty(users, ExceptionCons.IS_ALREADY_TRANSFER); + flowParams.cooperateType(CooperateType.TRANSFER.getKey()) + .reductionHandlers(Collections.singletonList(flowParams.getHandler())); + + return updateHandler(taskId, flowParams); + } + + @Override + public boolean depute(Long taskId, FlowParams flowParams) { + AssertUtil.isNull(taskId, ExceptionCons.NULL_TASK_ID); + AssertUtil.isNull(flowParams.getHandler(), ExceptionCons.HANDLER_NOT_EMPTY); + AssertUtil.isNull(flowParams.getAddHandlers(), ExceptionCons.NULL_DEPUTE_HANDLER); + List users = FlowEngine.userService().getByProcessedBys(taskId, flowParams.getAddHandlers(), UserType.DEPUTE.getKey()); + AssertUtil.isNotEmpty(users, ExceptionCons.IS_ALREADY_DEPUTE); + flowParams.cooperateType(CooperateType.DEPUTE.getKey()) + .reductionHandlers(Collections.singletonList(flowParams.getHandler())); + + return updateHandler(taskId, flowParams); + } + + @Override + public boolean addSignature(Long taskId, FlowParams flowParams) { + AssertUtil.isNull(taskId, ExceptionCons.NULL_TASK_ID); + AssertUtil.isNull(flowParams.getHandler(), ExceptionCons.HANDLER_NOT_EMPTY); + AssertUtil.isNull(flowParams.getAddHandlers(), ExceptionCons.NULL_ADD_SIGNATURE_HANDLER); + List users = FlowEngine.userService().getByProcessedBys(taskId, flowParams.getAddHandlers(), UserType.APPROVAL.getKey()); + AssertUtil.isNotEmpty(users, ExceptionCons.IS_ALREADY_SIGN); + flowParams.cooperateType(CooperateType.ADD_SIGNATURE.getKey()); + + return updateHandler(taskId, flowParams); + } + + @Override + public boolean reductionSignature(Long taskId, FlowParams flowParams) { + AssertUtil.isNull(taskId, ExceptionCons.NULL_TASK_ID); + AssertUtil.isNull(flowParams.getHandler(), ExceptionCons.HANDLER_NOT_EMPTY); + AssertUtil.isNull(flowParams.getReductionHandlers(), ExceptionCons.NULL_REDUCTION_SIGNATURE_HANDLER); + List users = FlowEngine.userService().listByAssociatedAndTypes(taskId + , UserType.APPROVAL.getKey(), UserType.TRANSFER.getKey()); + AssertUtil.isTrue(CollUtil.isEmpty(users) || users.size() == 1, ExceptionCons.REDUCTION_SIGN_ONE_ERROR); + flowParams.cooperateType(CooperateType.REDUCTION_SIGNATURE.getKey()); + + return updateHandler(taskId, flowParams); + } + + @Override + public boolean updateHandler(Long taskId, FlowParams flowParams) { + // 获取待办任务 + R r = getAndCheck(taskId); + flowParams.variable(MapUtil.mergeAll(r.instance.getVariableMap(), flowParams.getVariable())); + // 执行开始监听器 + ListenerUtil.executeListener(new ListenerVariable(r.definition, r.instance, r.nowNode, null, r.task) + , Listener.LISTENER_START); + + // 获取给谁的权限 + if (!flowParams.isIgnore()) { + // 判断当前处理人是否有权限,获取当前办理人的权限 + List permissions = flowParams.getPermissionFlag(); + // 获取任务权限人 + List taskPermissions = FlowEngine.userService().getPermission(taskId + , UserType.APPROVAL.getKey(), UserType.TRANSFER.getKey(), UserType.DEPUTE.getKey()); + AssertUtil.isTrue(CollUtil.isNotEmpty(taskPermissions) && (CollUtil.isEmpty(permissions) + || CollUtil.notContainsAny(permissions, taskPermissions)), ExceptionCons.NOT_AUTHORITY); + } + // 留存历史记录 + flowParams.skipType(SkipType.NONE.getKey()); + HisTask hisTask = null; + // 删除对应的操作人 + if (CollUtil.isNotEmpty(flowParams.getReductionHandlers())) { + for (String reductionHandler : flowParams.getReductionHandlers()) { + FlowEngine.userService().remove(FlowEngine.newUser().setAssociated(taskId) + .setProcessedBy(reductionHandler)); + } + hisTask = FlowEngine.hisTaskService().setCooperateHis(r.task, r.nowNode + , flowParams, flowParams.getReductionHandlers()); + } + + // 新增权限人 + if (CollUtil.isNotEmpty(flowParams.getAddHandlers())) { + String type; + if (CooperateType.TRANSFER.getKey().equals(flowParams.getCooperateType())) { + type = UserType.TRANSFER.getKey(); + } else if (CooperateType.DEPUTE.getKey().equals(flowParams.getCooperateType())) { + type = UserType.DEPUTE.getKey(); + } else { + type = UserType.APPROVAL.getKey(); + } + FlowEngine.userService().saveBatch(StreamUtils.toList(flowParams.getAddHandlers(), permission -> + FlowEngine.userService().structureUser(taskId, permission + , type, flowParams.getHandler()))); + hisTask = FlowEngine.hisTaskService().setCooperateHis(r.task, r.nowNode + , flowParams, flowParams.getAddHandlers()); + } + if (ObjectUtil.isNotNull(hisTask)) { + FlowEngine.hisTaskService().save(hisTask); + } + // 最后判断是否存在节点监听器,存在执行节点监听器 + ListenerUtil.executeListener(new ListenerVariable(r.definition, r.instance, r.nowNode, flowParams.getVariable() + , r.task), Listener.LISTENER_FINISH); + return true; + } + + @Override + public Task addTask(Node node, Instance instance, Definition definition, FlowParams flowParams) { + Task addTask = FlowEngine.newTask(); + Date now = new Date(); + FlowEngine.dataFillHandler().idFill(addTask); + addTask.setDefinitionId(instance.getDefinitionId()) + .setInstanceId(instance.getId()) + .setNodeCode(node.getNodeCode()) + .setNodeName(node.getNodeName()) + .setNodeType(node.getNodeType()) + .setFlowStatus(StringUtils.emptyDefault(flowParams.getFlowStatus(), + setFlowStatus(node.getNodeType(), flowParams.getSkipType()))) + .setCreateTime(now) + .setPermissionList(StringUtils.str2List(node.getPermissionFlag(), FlowCons.SPLIT_AT)); + + if (StringUtils.isNotEmpty(node.getFormCustom()) && StringUtils.isNotEmpty(node.getFormPath())) { + // 节点有自定义表单则使用 + addTask.setFormCustom(node.getFormCustom()).setFormPath(node.getFormPath()); + } else { + addTask.setFormCustom(definition.getFormCustom()).setFormPath(definition.getFormPath()); + } + + return addTask; + } + + @Override + public List getByInsId(Long instanceId) { + return list(FlowEngine.newTask().setInstanceId(instanceId)); + } + + @Override + public List getByInsIdAndNodeCodes(Long instanceId, List nodeCodes) { + return getDao().getByInsIdAndNodeCodes(instanceId, nodeCodes); + } + + @Override + public void setInsFinishInfo(Instance instance, List addTasks, FlowParams flowParams) { + instance.setUpdateTime(new Date()); + // 合并流程变量到实例对象 + mergeVariable(instance, flowParams.getVariable()); + if (CollUtil.isNotEmpty(addTasks)) { + AtomicReference finallyTask = new AtomicReference<>(); + addTasks.removeIf(addTask -> { + if (NodeType.isEnd(addTask.getNodeType())) { + finallyTask.set(addTask); + return true; + } + return false; + }); + if (ObjectUtil.isNull(finallyTask.get())) { + finallyTask.set(getNextTask(addTasks)); + } + instance.setNodeType(finallyTask.get().getNodeType()) + .setNodeCode(finallyTask.get().getNodeCode()) + .setNodeName(finallyTask.get().getNodeName()) + .setFlowStatus(finallyTask.get().getFlowStatus()); + } + } + + @Override + public void mergeVariable(Instance instance, Map variable) { + if (MapUtil.isNotEmpty(variable)) { + String variableStr = instance.getVariable(); + Map deserialize = FlowEngine.jsonConvert.strToMap(variableStr); + deserialize.putAll(variable); + instance.setVariable(FlowEngine.jsonConvert.objToStr(deserialize)); + } + } + + /** + * 根据流程实例id获取操作人最近的已办历史任务 + * + * @param flowParams 包含流程相关参数的对象 + * @param instanceId 流程实例id + * @return 最近的已办历史任务 + */ + private HisTask taskBack(FlowParams flowParams, Long instanceId) { + flowParams.skipType(SkipType.REJECT.getKey()) + .ignore(true) + .ignoreDepute(true) + .ignoreCooperate(true) + .flowStatus(StringUtils.emptyDefault(flowParams.getFlowStatus(), FlowStatus.TASK_BACK.getKey())); + // 获取当前任务的前置任务 + List hisTaskList = FlowEngine.hisTaskService().getByInsId(instanceId); + // 获取hisTaskList中TargetNodeCod等于task.getNodeCode()的,并且id最大的 + HisTask lastHisTask = hisTaskList.stream() + .filter(hisTask -> StringUtils.isNotEmpty(hisTask.getApprover())) + .filter(hisTask -> SkipType.isPass(hisTask.getSkipType())) + .filter(hisTask -> hisTask.getApprover().equals(flowParams.getHandler())) + .max(Comparator.comparingLong(HisTask::getId)) + .orElse(null); + AssertUtil.isNull(lastHisTask, ExceptionCons.NOT_FOUNT_HANDLED_TASK); + flowParams.nodeCode(lastHisTask.getNodeCode()); + return lastHisTask; + } + + /** + * 获取待办任务 + * + * @param instanceId 实例id + * @return 待办任务 + */ + private Task getTask(Long instanceId) { + List taskList = getByInsId(instanceId); + AssertUtil.isEmpty(taskList, ExceptionCons.NOT_FOUNT_TASK); + AssertUtil.isTrue(taskList.size() > 1, ExceptionCons.TASK_NOT_ONE); + return taskList.get(0); + } + + private String setFlowStatus(Integer nodeType, String skipType) { + // 根据审批动作确定流程状态 + if (NodeType.isStart(nodeType)) { + return FlowStatus.TOBESUBMIT.getKey(); + } else if (NodeType.isEnd(nodeType)) { + return FlowStatus.FINISHED.getKey(); + } else if (SkipType.isReject(skipType)) { + return FlowStatus.REJECT.getKey(); + } else { + return FlowStatus.APPROVAL.getKey(); + } + } + + private Task getNextTask(List tasks) { + if (tasks.size() == 1) { + return tasks.get(0); + } + for (Task task : tasks) { + if (NodeType.isEnd(task.getNodeType())) { + return task; + } + } + return tasks.stream().max(Comparator.comparingLong(Task::getId)).orElse(null); + } + + private void removeAndUser(List taskList) { + removeByIds(StreamUtils.toList(taskList, Task::getId)); + FlowEngine.userService().deleteByTaskIds(StreamUtils.toList(taskList, Task::getId)); + } + + private R getAndCheck(Long taskId) { + AssertUtil.isNull(taskId, ExceptionCons.NULL_TASK_ID); + return getAndCheck(getById(taskId)); + } + + private R getAndCheck(Task task) { + AssertUtil.isNull(task, ExceptionCons.NOT_FOUNT_TASK); + Instance instance = FlowEngine.insService().getById(task.getInstanceId()); + AssertUtil.isNull(instance, ExceptionCons.NOT_FOUNT_INSTANCE); + Definition definition = FlowEngine.defService().getById(instance.getDefinitionId()); + AssertUtil.isFalse(judgeActivityStatus(definition, instance), ExceptionCons.NOT_ACTIVITY); + AssertUtil.isTrue(NodeType.isEnd(instance.getNodeType()), ExceptionCons.FLOW_FINISH); + Node nowNode = FlowEngine.nodeService().getByDefIdAndNodeCode(task.getDefinitionId(), task.getNodeCode()); + AssertUtil.isNull(nowNode, ExceptionCons.LOST_CUR_NODE); + return new R(instance, definition, nowNode, task); + } + + private static class R { + public final Instance instance; + public final Definition definition; + public final Node nowNode; + public final Task task; + + public R(Instance instance, Definition definition, Node nowNode, Task task) { + this.instance = instance; + this.definition = definition; + this.nowNode = nowNode; + this.task = task; + } + } + + private boolean handleDepute(Task task, FlowParams flowParams) { + // 获取受托人 + List entrustedUserList = StreamUtils.filter(task.getUserList(), + user -> UserType.DEPUTE.getKey().equals(user.getType()) + && Objects.equals(flowParams.getHandler(), user.getProcessedBy())); + if (CollUtil.isEmpty(entrustedUserList)) { + return false; + } + + // 记录受托人处理任务记录 + User entrustedUser = entrustedUserList.get(0); + HisTask hisTask = FlowEngine.hisTaskService().setDeputeHisTask(task, flowParams, entrustedUser); + FlowEngine.hisTaskService().save(hisTask); + FlowEngine.userService().removeById(entrustedUser.getId()); + + // 查询委托人,如果在flow_user不存在,则给委托人新增待办记录 + User deputeUser = FlowEngine.userService().getOne(FlowEngine.newUser().setAssociated(task.getId()) + .setProcessedBy(entrustedUser.getCreateBy()).setType(UserType.APPROVAL.getKey())); + if (ObjectUtil.isNull(deputeUser)) { + User newUser = FlowEngine.userService().structureUser(entrustedUser.getAssociated() + , entrustedUser.getCreateBy() + , UserType.APPROVAL.getKey(), entrustedUser.getProcessedBy()); + FlowEngine.userService().save(newUser); + } + + return true; + } + + /** + * 会签,票签,协作处理,返回true;或签或者会签、票签结束返回false + * + * @param nowNode 当前节点 + * @param task 任务 + * @param flowParams 流程参数 + * @return boolean + */ + private boolean cooperate(Node nowNode, Task task, FlowParams flowParams) { + String nodeRatio = nowNode.getNodeRatio(); + // 或签,直接返回 + if (CooperateType.isOrSign(nodeRatio)) { + return false; + } + + // 办理人和转办人列表 + List todoList = FlowEngine.userService().listByAssociatedAndTypes(task.getId() + , UserType.APPROVAL.getKey(), UserType.TRANSFER.getKey(), UserType.DEPUTE.getKey()); + + // 判断办理人是否有办理权限 + AssertUtil.isEmpty(flowParams.getHandler(), ExceptionCons.SIGN_NULL_HANDLER); + User todoUser = CollUtil.getOne(StreamUtils.filter(todoList, u -> flowParams.getHandlerList().contains(u.getProcessedBy()))); + AssertUtil.isNull(todoUser, ExceptionCons.NOT_AUTHORITY); + + // 除当前办理人外剩余办理人列表 + List restList = StreamUtils.filter(todoList, u -> !Objects.equals(u.getProcessedBy(), flowParams.getHandler())); + + // 会签并且当前人退回直接返回 + if (CooperateType.isCountersign(nodeRatio) && SkipType.isReject(flowParams.getSkipType())) { + return removeRestList(restList); + } + + // 查询会签票签已办列表 + List doneList = FlowEngine.hisTaskService().listByTaskId(task.getId()); + doneList = CollUtil.emptyDefault(doneList); + + // 总人数 + int allNum = todoList.size() + doneList.size(); + + // 通过历史记录 + List donePassList = StreamUtils.filter(doneList + , hisTask -> Objects.equals(hisTask.getSkipType(), SkipType.PASS.getKey())); + + // 驳回历史记录 + List doneRejectList = StreamUtils.filter(doneList + , hisTask -> Objects.equals(hisTask.getSkipType(), SkipType.REJECT.getKey())); + + boolean isPass = SkipType.isPass(flowParams.getSkipType()); + // 如果是票签默认或者spel表达式策略,则执行表达式 + if (CooperateType.isVoteSignDefault(nodeRatio) || CooperateType.isVoteSignRejectSpel(nodeRatio)) { + Map variable = MapUtil.clone(flowParams.getVariable()); + variable.put("skipType", flowParams.getSkipType()); + variable.put("passNum", donePassList.size()); + variable.put("rejectNum", doneRejectList.size()); + variable.put("todoNum", todoList.size()); + variable.put("allNum", allNum); + variable.put("passList", donePassList); + variable.put("rejectList", doneRejectList); + variable.put("todoList", todoList); + if (ExpressionUtil.evalVoteSign(nodeRatio, variable)) { + return removeRestList(restList); + } + } else { + // 计算通过率 + BigDecimal passRatio = (isPass ? BigDecimal.ONE : BigDecimal.ZERO) + .add(BigDecimal.valueOf(donePassList.size())) + .divide(BigDecimal.valueOf(allNum), 4, RoundingMode.HALF_UP).multiply(MathUtil.ONE_HUNDRED); + // 计算驳回率 + BigDecimal rejectRatio = (isPass ? BigDecimal.ZERO : BigDecimal.ONE) + .add(BigDecimal.valueOf(doneRejectList.size())) + .divide(BigDecimal.valueOf(allNum), 4, RoundingMode.HALF_UP).multiply(MathUtil.ONE_HUNDRED); + + // 判断是否是票签中的固定通过人数,如果是则判断是否达到该人数 + if (CooperateType.isVoteSignPassCount(nodeRatio)) { + String passCount = StringUtils.substring(nodeRatio, nodeRatio.indexOf("=") + 1); + if ((isPass && donePassList.size() + 1 >= Integer.parseInt(passCount)) + || (!isPass && doneRejectList.size() + 1 > allNum - Integer.parseInt(passCount))) { + return removeRestList(restList); + } + } else if (CooperateType.isVoteSignRejectCount(nodeRatio)) { + // 判断是否是票签中的固定驳回人数,如果是则判断是否达到该人数 + String rejectCount = StringUtils.substring(nodeRatio, nodeRatio.indexOf("=") + 1); + if ((!isPass && doneRejectList.size() + 1 >= Integer.parseInt(rejectCount)) + || (isPass && donePassList.size() + 1 > allNum - Integer.parseInt(rejectCount))) { + return removeRestList(restList); + } + } else if ((!isPass && rejectRatio.compareTo(MathUtil.ONE_HUNDRED.subtract(new BigDecimal(nodeRatio))) > 0) + || (isPass && passRatio.compareTo(new BigDecimal(nodeRatio)) >= 0)) { + // 提前不满足通过率或者满足通过率,删除剩余办理人,流程正常流程流转 + return removeRestList(restList); + } + } + + // 当只剩一位待办用户时,由当前用户决定走向 + if (todoList.size() == 1) { + return false; + } + + // 添加历史任务 + HisTask hisTask = FlowEngine.hisTaskService().setSignHisTask(task, flowParams, nodeRatio, isPass); + FlowEngine.hisTaskService().save(hisTask); + + // 删掉待办用户 + FlowEngine.userService().removeById(todoUser.getId()); + return true; + } + + /** + * 删除剩余办理人 + * @param restList 待办用户列表 + * @return boolean + */ + private static boolean removeRestList(List restList) { + if (CollUtil.isNotEmpty(restList)) { + FlowEngine.userService().removeByIds(StreamUtils.toList(restList, User::getId)); + } + return false; + } + + /** + * 判断并行网关和包容网关节点只剩一个前置代办任务,才能生成新的代办任务 + * + * @param pathWayData 办理过程中途径数据 + * @param instance 实例 + */ + private void isGenerateNewTask(PathWayData pathWayData, Instance instance, List nextNodes) { + if (SkipType.isReject(pathWayData.getSkipType())) { + return; + } + + DefJson defJson = FlowEngine.jsonConvert.strToBean(instance.getDefJson(), DefJson.class); + Map nodeJsonMap = StreamUtils.toMap(defJson.getNodeList(), NodeJson::getNodeCode, node -> node); + // 遍历目标节点,获取目标节点中第一个互斥或者包含网关,并且判断只剩一个前置代办任务,才能生成新的代办任务 + List parallelOrInclusiveList = Optional.of(pathWayData) + .map(PathWayData::getPathWayNodes) + .orElse(Collections.emptyList()) + .stream() + .filter(t -> NodeType.isGateWayParallel(t.getNodeType()) || NodeType.isGateWayInclusive(t.getNodeType())) + .collect(Collectors.toList()); + if (CollUtil.isNotEmpty(parallelOrInclusiveList)) { + List previousNodeList = FlowEngine.nodeService().previousNodeList(instance.getDefinitionId() + , parallelOrInclusiveList.get(parallelOrInclusiveList.size() - 1).getNodeCode()); + // 获取前置节点中代办节点的数量 + long statusOneCount = previousNodeList.stream() + .map(Node::getNodeCode) + .map(nodeJsonMap::get) + .filter(Objects::nonNull) + .filter(nodeJson -> nodeJson.getStatus() == 1) + .count(); + // 并行网关和包容网关节点超过一个前置代办任务,说明可以不可生成新任务, + if (statusOneCount > 1) { + nextNodes.clear(); + AtomicBoolean flag = new AtomicBoolean(false); + pathWayData.getPathWayNodes().removeIf(nodeJson -> { + if (nodeJson.getNodeCode().equals(parallelOrInclusiveList.get(0).getNodeCode())) { + flag.set(true); + return false; + } + return flag.get(); + }); + flag.set(false); + pathWayData.getPathWaySkips().removeIf(nodeJson -> { + if (nodeJson.getNowNodeCode().equals(parallelOrInclusiveList.get(0).getNodeCode())) { + flag.set(true); + } + return flag.get(); + }); + } + } + } + + /** + * 判断当前处理人是否有权限处理 + * + * @param task 当前任务(任务id) + * @param flowParams:包含流程相关参数的对象 + */ + private void checkAuth(Task task, FlowParams flowParams) { + if (flowParams.isIgnore()) { + return; + } + // 查询审批人和转办人 + List permissions = StreamUtils.toList(task.getUserList(), User::getProcessedBy); + // 当前办理人拥有的权限和设计时候填的权限集合是否有交集,有说明有权限办理 + AssertUtil.isTrue(CollUtil.isNotEmpty(permissions) && (CollUtil.isEmpty(flowParams.getPermissionFlag()) + || CollUtil.notContainsAny(flowParams.getPermissionFlag(), permissions)), ExceptionCons.NULL_ROLE_NODE); + } + + + /** + * 一票否决(谨慎使用),如果退回,退回指向节点后还存在其他正在执行的待办任务,转历史任务,状态都为退回,重走流程。 + * + * @param task 当前任务 + * @param nextNodeCode 下一个节点编码 + * @param flowCombine 流程数据集合 + */ + private void oneVoteVeto(Task task, String nextNodeCode, FlowCombine flowCombine) { + // 一票否决(谨慎使用),如果退回,退回指向节点后还存在其他正在执行的待办任务,转历史任务,状态失效,重走流程。 + List tasks = list(FlowEngine.newTask().setInstanceId(task.getInstanceId())); + // 属于退回指向节点的后置未完成的任务 + List noDoneTasks = new ArrayList<>(); + List suffixNodeList = FlowEngine.nodeService().suffixNodeList(nextNodeCode, flowCombine); + List suffixCodes = StreamUtils.toList(suffixNodeList, Node::getNodeCode); + for (Task flowTask : tasks) { + if (suffixCodes.contains(flowTask.getNodeCode())) { + noDoneTasks.add(flowTask); + } + } + if (CollUtil.isNotEmpty(noDoneTasks)) { + removeAndUser(noDoneTasks); + } + } + + + /** + * 处理未完成的任务,当流程完成,还存在待办任务未完成,转历史任务,状态完成。 + * + * @param instance 流程实例 + */ + private void handUndoneTask(Instance instance) { + if (NodeType.isEnd(instance.getNodeType())) { + List taskList = list(FlowEngine.newTask().setInstanceId(instance.getId())); + if (CollUtil.isNotEmpty(taskList)) { + removeAndUser(taskList); + } + } + } + + /** + * 更新流程信息 + * + * @param task 当前任务 + * @param instance 流程实例 + * @param addTasks 新增待办任务 + * @param flowParams 包含流程相关参数的对象 + * @param nextNodes 下一个节点集合 + */ + private void updateFlowInfo(Task task, Instance instance, List addTasks, FlowParams flowParams + , List nextNodes) { + // 设置流程历史任务信息 + HisTask insHis = FlowEngine.hisTaskService().setSkipInsHis(task, nextNodes, flowParams); + FlowEngine.hisTaskService().save(insHis); + removeAndUser(Collections.singletonList(task)); + // 待办任务设置处理人 + List users = FlowEngine.userService().taskAddUsers(addTasks); + + // 设置任务完成后的实例相关信息 + setInsFinishInfo(instance, addTasks, flowParams); + if (CollUtil.isNotEmpty(addTasks)) { + saveBatch(addTasks); + } + FlowEngine.insService().updateById(instance); + // 保存下一个待办任务的权限人 + FlowEngine.userService().saveBatch(users); + } + + private boolean judgeActivityStatus(Definition definition, Instance instance) { + return ActivityStatus.isActivity(definition.getActivityStatus()) + && ActivityStatus.isActivity(instance.getActivityStatus()); + } + + + @Override + public FlowDto load(Long taskId, FlowParams flowParams) { + R r = getAndCheck(taskId); + + ListenerVariable listenerVariable = new ListenerVariable(r.definition, r.instance, r.nowNode + , flowParams.getVariable(), r.task); + + FlowDto flowDto = new FlowDto(); + if (FlowCons.FORM_CUSTOM_Y.equals(r.nowNode.getFormCustom())) { + ListenerUtil.execute(listenerVariable, Listener.LISTENER_FORM_LOAD, r.nowNode.getListenerPath() + , r.nowNode.getListenerType()); + Form form = FlowEngine.formService().getById(Long.valueOf(r.task.getFormPath())); + flowDto.setForm(form); + } else if (StringUtils.isEmpty(r.nowNode.getFormCustom()) && FlowCons.FORM_CUSTOM_Y.equals(r.definition.getFormCustom())) { + ListenerUtil.execute(listenerVariable, Listener.LISTENER_FORM_LOAD, r.definition.getListenerPath() + , r.definition.getListenerType()); + Form form = FlowEngine.formService().getById(Long.valueOf(r.definition.getFormPath())); + flowDto.setForm(form); + } + flowDto.setData(r.instance.getVariableMap().get(FlowCons.FORM_DATA)); + + return flowDto; + } + + @Override + public FlowDto hisLoad(Long hisTaskId, FlowParams flowParams) { + HisTask hisTask = FlowEngine.hisTaskService().getById(hisTaskId); + AssertUtil.isNull(hisTask, ExceptionCons.NOT_FOUND_FLOW_TASK); + + Definition definition = FlowEngine.defService().getById(hisTask.getDefinitionId()); + AssertUtil.isNull(definition, ExceptionCons.NOT_FOUNT_DEF); + + Node nowNode = CollUtil.getOne(FlowEngine.nodeService() + .getByNodeCodes(Collections.singletonList(hisTask.getNodeCode()), hisTask.getDefinitionId())); + AssertUtil.isNull(nowNode, ExceptionCons.LOST_CUR_NODE); + + FlowDto flowDto = new FlowDto(); + if (FlowCons.FORM_CUSTOM_Y.equals(nowNode.getFormCustom())) { + Form form = FlowEngine.formService().getById(Long.valueOf(hisTask.getFormPath())); + flowDto.setForm(form); + } else if (StringUtils.isEmpty(nowNode.getFormCustom()) && FlowCons.FORM_CUSTOM_Y.equals(definition.getFormCustom())) { + Form form = FlowEngine.formService().getById(Long.valueOf(definition.getFormPath())); + flowDto.setForm(form); + } + flowDto.setData(hisTask.getVariableMap().get(FlowCons.FORM_DATA)); + + return flowDto; + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/impl/UserServiceImpl.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..37f3dd0 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/service/impl/UserServiceImpl.java @@ -0,0 +1,162 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.service.impl; + +import org.dromara.warm.flow.core.FlowEngine; +import org.dromara.warm.flow.core.entity.Task; +import org.dromara.warm.flow.core.entity.User; +import org.dromara.warm.flow.core.enums.UserType; +import org.dromara.warm.flow.core.orm.dao.FlowUserDao; +import org.dromara.warm.flow.core.orm.service.impl.WarmServiceImpl; +import org.dromara.warm.flow.core.service.UserService; +import org.dromara.warm.flow.core.utils.ArrayUtil; +import org.dromara.warm.flow.core.utils.CollUtil; +import org.dromara.warm.flow.core.utils.StreamUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +/** + * 流程用户Service业务层处理 + * + * @author xiarg + * @since 2024/5/10 13:57 + */ +public class UserServiceImpl extends WarmServiceImpl, User> implements UserService { + + @Override + public UserService setDao(FlowUserDao warmDao) { + this.warmDao = warmDao; + return this; + } + + @Override + public List taskAddUsers(List addTasks) { + List taskUserList = new ArrayList<>(); + if (CollUtil.isNotEmpty(addTasks)) { + StreamUtils.toList(addTasks, task -> taskUserList.addAll(taskAddUser(task))); + } + return taskUserList; + } + + @Override + public List taskAddUser(Task task) { + // 遍历权限集合,生成流程节点的权限 + List userList = StreamUtils.toList(task.getPermissionList() + , permission -> structureUser(task.getId(), permission, UserType.APPROVAL.getKey())); + task.setUserList(userList); + return userList; + } + + @Override + public void deleteByTaskIds(List ids) { + getDao().deleteByTaskIds(ids); + } + + @Override + public List getPermission(Long associated, String... types) { + if (ArrayUtil.isEmpty(types)) { + return StreamUtils.toList(list(FlowEngine.newUser().setAssociated(associated)), User::getProcessedBy); + } + if (types.length == 1) { + return StreamUtils.toList(list(FlowEngine.newUser().setAssociated(associated).setType(types[0])) + , User::getProcessedBy); + } + return StreamUtils.toList(getDao().listByAssociatedAndTypes(Collections.singletonList(associated), types) + , User::getProcessedBy); + } + + @Override + public List listByAssociatedAndTypes(Long associated, String... types) { + if (ArrayUtil.isEmpty(types)) { + return list(FlowEngine.newUser().setAssociated(associated)); + } + if (types.length == 1) { + return list(FlowEngine.newUser().setAssociated(associated).setType(types[0])); + } + return getDao().listByAssociatedAndTypes(Collections.singletonList(associated), types); + } + + @Override + public List getByAssociateds(List associateds, String... types) { + if (CollUtil.isNotEmpty(associateds) && associateds.size() == 1) { + return listByAssociatedAndTypes(associateds.get(0), types); + } + return getDao().listByAssociatedAndTypes(associateds, types); + } + + @Override + public List listByProcessedBys(Long associated, String processedBy, String... types) { + if (ArrayUtil.isEmpty(types)) { + return list(FlowEngine.newUser().setAssociated(associated).setProcessedBy(processedBy)); + } + if (types.length == 1) { + return list(FlowEngine.newUser().setAssociated(associated).setProcessedBy(processedBy).setType(types[0])); + } + return getDao().listByProcessedBys(associated, Collections.singletonList(processedBy), types); + } + + @Override + public List getByProcessedBys(Long associated, List processedBys, String... types) { + if (CollUtil.isNotEmpty(processedBys) && processedBys.size() == 1) { + return listByProcessedBys(associated, processedBys.get(0), types); + } + return getDao().listByProcessedBys(associated, processedBys, types); + } + + + @Override + public boolean updatePermission(Long associated, List permissions, String type, boolean clear, + String handler) { + // 判断是否clear,如果是true,则先删除当前关联id用户数据 + if (clear) { + getDao().delete(FlowEngine.newUser().setAssociated(associated).setCreateBy(handler)); + } + // 再新增权限人 + saveBatch(StreamUtils.toList(permissions, permission -> structureUser(associated, permission, type, handler))); + return true; + } + + @Override + public List structureUser(Long associated, List permissionList, String type) { + return StreamUtils.toList(permissionList, permission -> structureUser(associated, permission, type, null)); + } + + @Override + public User structureUser(Long associated, String permission, String type) { + return structureUser(associated, permission, type, null); + } + + @Override + public List structureUser(Long associated, List permissionList, String type, String handler) { + return StreamUtils.toList(permissionList, permission -> structureUser(associated, permission, type, handler)); + } + + @Override + public User structureUser(Long associated, String permission, String type, String handler) { + Date now = new Date(); + User user = FlowEngine.newUser() + .setType(type) + .setProcessedBy(permission) + .setAssociated(associated) + .setCreateBy(handler); + FlowEngine.dataFillHandler().idFill(user); + return user; + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/strategy/ConditionStrategy.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/strategy/ConditionStrategy.java new file mode 100644 index 0000000..42eeac3 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/strategy/ConditionStrategy.java @@ -0,0 +1,44 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.strategy; + +import org.dromara.warm.flow.core.constant.FlowCons; + +import java.util.ArrayList; +import java.util.List; + +/** + * 条件表达式接口 + * + * @author warm + */ +public interface ConditionStrategy extends ExpressionStrategy { + + /** + * 条件表达式策略实现类集合 + */ + List> EXPRESSION_STRATEGY_LIST = new ArrayList<>(); + + @Override + default void setExpression(ExpressionStrategy expressionStrategy) { + EXPRESSION_STRATEGY_LIST.add(expressionStrategy); + } + + @Override + default String interceptStr() { + return FlowCons.SPLIT_AT; + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/strategy/ExpressionStrategy.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/strategy/ExpressionStrategy.java new file mode 100644 index 0000000..d39d9ca --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/strategy/ExpressionStrategy.java @@ -0,0 +1,60 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.strategy; + +import java.util.Map; + +/** + * 表达式策略类接口 + * + * @author warm + */ +public interface ExpressionStrategy { + + /** + * 获取策略类型 + * + * @return 类型 + */ + String getType(); + + /** + * 当选择截取,并且希望拼接上某些字符串,在进行截取 + * + * @return 类型 + */ + default String interceptStr() { + return ""; + } + + /** + * 执行表达式 + * + * @param expression 表达式 + * @param variable 流程变量 + * @return 执行结果 + */ + T eval(String expression, Map variable); + + + /** + * 设置表达式 + * + * @param expressionStrategy 表达式 + */ + void setExpression(ExpressionStrategy expressionStrategy); + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/strategy/HandlerStrategy.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/strategy/HandlerStrategy.java new file mode 100644 index 0000000..fda5069 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/strategy/HandlerStrategy.java @@ -0,0 +1,60 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.strategy; + +import org.dromara.warm.flow.core.utils.ObjectUtil; +import org.dromara.warm.flow.core.utils.StreamUtils; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * 办理人表达式策略接口 + * + * @author warm,battcn + */ +public interface HandlerStrategy extends ExpressionStrategy> { + + /** + * 办理人表达式策略实现类集合 + */ + List>> EXPRESSION_STRATEGY_LIST = new ArrayList<>(); + + @Override + default void setExpression(ExpressionStrategy> expressionStrategy) { + EXPRESSION_STRATEGY_LIST.add(expressionStrategy); + } + + Object preEval(String expression, Map variable); + + @Override + default List eval(String expression, Map variable) { + return afterEval(preEval(expression, variable)); + } + + default List afterEval(Object o) { + if (ObjectUtil.isNull(o)) { + return null; + } + if (o instanceof List) { + return StreamUtils.toList((List) o, Object::toString); + } + if (o instanceof Object[]) { + return Arrays.stream((Object[]) o).map(Object::toString).collect(Collectors.toList()); + } + return Collections.singletonList(o.toString()); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/strategy/ListenerStrategy.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/strategy/ListenerStrategy.java new file mode 100644 index 0000000..9a62b61 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/strategy/ListenerStrategy.java @@ -0,0 +1,38 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.strategy; + +import java.util.ArrayList; +import java.util.List; + +/** + * 监听器表达式策略接口 + * + * @author warm,battcn + */ +public interface ListenerStrategy extends ExpressionStrategy { + + /** + * 监听器表达式策略实现类集合 + */ + List> EXPRESSION_STRATEGY_LIST = new ArrayList<>(); + + @Override + default void setExpression(ExpressionStrategy expressionStrategy) { + EXPRESSION_STRATEGY_LIST.add(expressionStrategy); + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/strategy/VoteSignStrategy.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/strategy/VoteSignStrategy.java new file mode 100644 index 0000000..1becc9d --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/strategy/VoteSignStrategy.java @@ -0,0 +1,44 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.strategy; + +import org.dromara.warm.flow.core.constant.FlowCons; + +import java.util.ArrayList; +import java.util.List; + +/** + * 票签表达式接口 + * + * @author warm + */ +public interface VoteSignStrategy extends ExpressionStrategy { + + /** + * 票签表达式策略实现类集合 + */ + List> EXPRESSION_STRATEGY_LIST = new ArrayList<>(); + + @Override + default void setExpression(ExpressionStrategy expressionStrategy) { + EXPRESSION_STRATEGY_LIST.add(expressionStrategy); + } + + @Override + default String interceptStr() { + return FlowCons.SPLIT_AT; + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/AddCopyrightHeader.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/AddCopyrightHeader.java new file mode 100644 index 0000000..0082104 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/AddCopyrightHeader.java @@ -0,0 +1,102 @@ +package org.dromara.warm.flow.core.utils; + +import java.io.*; + +public class AddCopyrightHeader { + public static void main(String[] args) throws Exception { + //项目的绝对路径,也就是想修改的文件路径 + String filePath = "D:\\IdeaProjects\\min\\RuoYi-Vue-Warm-Flow\\warm-flow"; + File f = new File(filePath); + String content = "/*\n" + + " * Copyright 2024-2025, Warm-Flow (290631660@qq.com).\n" + + " *\n" + + " * Licensed under the Apache License, Version 2.0 (the \"License\");\n" + + " * you may not use this file except in compliance with the License.\n" + + " * You may obtain a copy of the License at\n" + + " *\n" + + " * https://www.apache.org/licenses/LICENSE-2.0\n" + + " *\n" + + " * Unless required by applicable law or agreed to in writing, software\n" + + " * distributed under the License is distributed on an \"AS IS\" BASIS,\n" + + " * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" + + " * See the License for the specific language governing permissions and\n" + + " * limitations under the License.\n" + + " */\n"; + fileTree(f, content); + } + + /** + * 取出所有的文件及文件夹 + * + * @param f 文件夹对象 + */ + public static void fileTree(File f, String content) throws Exception { + File[] t = f.listFiles(); + if (t != null) { + for (File file : t) { + if (file.isDirectory()) { + fileTree(file, content); + } else { + insert(file, content); + } + } + } + } + + /** + * 开始插入内容(仅在没有版权信息时插入) + * + * @param f 文件对象 + * @param content 要插入的版权内容 + */ + public static void insert(File f, String content) throws IOException { + // 只处理 .java 文件 + if (!f.getName().endsWith(".java")) { + return; + } + + // 创建临时文件用于内容备份 + File temp = File.createTempFile("temp", null); + temp.deleteOnExit(); + + try ( + RandomAccessFile raf = new RandomAccessFile(f, "rw"); + FileOutputStream tempOut = new FileOutputStream(temp); + FileInputStream tempInput = new FileInputStream(temp); + ) { + // 检查是否已经包含版权信息 + String copyrightLine = "Copyright 2024-2025, Warm-Flow"; + boolean hasCopyright = false; + + // 读取文件前 1024 字节,用于判断是否已包含版权信息 + byte[] buffer = new byte[1024]; + int bytesRead = raf.read(buffer); + if (bytesRead > 0) { + String fileStart = new String(buffer, 0, bytesRead); + if (fileStart.contains(copyrightLine)) { + hasCopyright = true; + } + } + + if (hasCopyright) { + System.out.println("跳过插入版权信息:" + f.getAbsolutePath()); + return; + } + + // 如果没有版权信息,则进行插入操作 + raf.seek(0); + while ((bytesRead = raf.read(buffer)) > 0) { + tempOut.write(buffer, 0, bytesRead); + } + + raf.seek(0); + raf.write(content.getBytes()); + + byte[] tempBuffer = new byte[64]; + int hasRead; + while ((hasRead = tempInput.read(tempBuffer)) > 0) { + raf.write(tempBuffer, 0, hasRead); + } + } + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/ArrayUtil.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/ArrayUtil.java new file mode 100644 index 0000000..3235909 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/ArrayUtil.java @@ -0,0 +1,76 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.utils; + +/** + * 数组工具类 + * + * @author warm + * @since 2023/5/18 9:45 + */ +public class ArrayUtil { + /** + * * 判断一个对象数组是否为空 + * + * @param objects 要判断的对象数组 + * * @return true:为空 false:非空 + */ + public static boolean isEmpty(Object[] objects) { + return ObjectUtil.isNull(objects) || (objects.length == 0); + } + + /** + * * 判断一个对象数组是否非空 + * + * @param objects 要判断的对象数组 + * @return true:非空 false:空 + */ + public static boolean isNotEmpty(Object[] objects) { + return !isEmpty(objects); + } + + /** + * 如果数组是空,则返回默认值 + * + * @param objects 数组 + * @param defaultArr 默认值 + * @return 结果 + */ + public static T[] emptyDefault(T[] objects, T[] defaultArr) { + return isEmpty(objects) ? defaultArr : objects; + } + + /** + * * 判断一个对象是否是数组类型(Java基本型别的数组) + * + * @param object 对象 + * @return true:是数组 false:不是数组 + */ + public static boolean isArray(Object object) { + return ObjectUtil.isNotNull(object) && object.getClass().isArray(); + } + + /** + * 字符串转数组 + * + * @param str 字符串 + * @param sep 分隔符 + * @return + */ + public static String[] strToArrAy(String str, String sep) { + return StringUtils.isEmpty(str) ? null : str.split(sep); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/AssertUtil.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/AssertUtil.java new file mode 100644 index 0000000..6b7fa71 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/AssertUtil.java @@ -0,0 +1,111 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.utils; + +import org.dromara.warm.flow.core.exception.FlowException; + +import java.util.Collection; +import java.util.Map; + +/** + * Assert类 + * + * @author warm + * @since 2023/3/30 14:05 + */ +public class AssertUtil { + private AssertUtil() { + + } + + public static void isNull(Object obj, String errorMsg) { + if (obj == null) { + throw new FlowException(errorMsg); + } + } + + public static void isNotNull(Object obj, String errorMsg) { + if (obj != null) { + throw new FlowException(errorMsg); + } + } + + /** + * 为true不抛异常 + * + * @param obj + * @param errorMsg + */ + public static void isFalse(boolean obj, String errorMsg) { + if (!obj) { + throw new FlowException(errorMsg); + } + } + + /** + * 为false不抛异常 + * + * @param obj + * @param errorMsg + */ + public static void isTrue(boolean obj, String errorMsg) { + if (obj) { + throw new FlowException(errorMsg); + } + } + + public static void isNotEmpty(Object obj, String errorMsg) { + if (obj != null) { + if (obj instanceof String) { + AssertUtil.isTrue(StringUtils.isNotEmpty((String) obj), errorMsg); + } else if (obj instanceof Collection) { + AssertUtil.isTrue(CollUtil.isNotEmpty((Collection) obj), errorMsg); + } else if (obj instanceof Map) { + AssertUtil.isTrue(MapUtil.isNotEmpty((Map) obj), errorMsg); + } else { + throw new FlowException("Unsupported type: " + obj.getClass().getName()); + } + } + } + + + public static void isEmpty(Object obj, String errorMsg) { + if (obj == null) { + throw new FlowException(errorMsg); + } else if (obj instanceof String) { + AssertUtil.isTrue(StringUtils.isEmpty((String) obj), errorMsg); + } else if (obj instanceof Collection) { + AssertUtil.isTrue(CollUtil.isEmpty((Collection) obj), errorMsg); + } else if (obj instanceof Map) { + AssertUtil.isTrue(MapUtil.isEmpty((Map) obj), errorMsg); + } else { + throw new FlowException("Unsupported type: " + obj.getClass().getName()); + } + } + + public static void contains(Collection a, T b, String errorMsg) { + if (CollUtil.isNotEmpty(a) && a.contains(b)) { + throw new FlowException(errorMsg); + } + } + + public static void notContains(Collection a, T b, String errorMsg) { + if (CollUtil.isEmpty(a) || !a.contains(b)) { + throw new FlowException(errorMsg); + } + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/Base64.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/Base64.java new file mode 100644 index 0000000..c723192 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/Base64.java @@ -0,0 +1,273 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.utils; + +/** + * Base64工具类 + * + * @author hh + */ +public final class Base64 { + static private final int BASELENGTH = 128; + static private final int LOOKUPLENGTH = 64; + static private final int TWENTYFOURBITGROUP = 24; + static private final int EIGHTBIT = 8; + static private final int SIXTEENBIT = 16; + static private final int FOURBYTE = 4; + static private final int SIGN = -128; + static private final char PAD = '='; + static final private byte[] base64Alphabet = new byte[BASELENGTH]; + static final private char[] lookUpBase64Alphabet = new char[LOOKUPLENGTH]; + + static { + for (int i = 0; i < BASELENGTH; ++i) { + base64Alphabet[i] = -1; + } + for (int i = 'Z'; i >= 'A'; i--) { + base64Alphabet[i] = (byte) (i - 'A'); + } + for (int i = 'z'; i >= 'a'; i--) { + base64Alphabet[i] = (byte) (i - 'a' + 26); + } + + for (int i = '9'; i >= '0'; i--) { + base64Alphabet[i] = (byte) (i - '0' + 52); + } + + base64Alphabet['+'] = 62; + base64Alphabet['/'] = 63; + + for (int i = 0; i <= 25; i++) { + lookUpBase64Alphabet[i] = (char) ('A' + i); + } + + for (int i = 26, j = 0; i <= 51; i++, j++) { + lookUpBase64Alphabet[i] = (char) ('a' + j); + } + + for (int i = 52, j = 0; i <= 61; i++, j++) { + lookUpBase64Alphabet[i] = (char) ('0' + j); + } + lookUpBase64Alphabet[62] = (char) '+'; + lookUpBase64Alphabet[63] = (char) '/'; + } + + private static boolean isWhiteSpace(char octect) { + return (octect == 0x20 || octect == 0xd || octect == 0xa || octect == 0x9); + } + + private static boolean isPad(char octect) { + return (octect == PAD); + } + + private static boolean isData(char octect) { + return (octect < BASELENGTH && base64Alphabet[octect] != -1); + } + + /** + * Encodes hex octects into Base64 + * + * @param binaryData Array containing binaryData + * @return Encoded Base64 array + */ + public static String encode(byte[] binaryData) { + if (binaryData == null) { + return null; + } + + int lengthDataBits = binaryData.length * EIGHTBIT; + if (lengthDataBits == 0) { + return ""; + } + + int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP; + int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP; + int numberQuartet = fewerThan24bits != 0 ? numberTriplets + 1 : numberTriplets; + char[] encodedData = null; + + encodedData = new char[numberQuartet * 4]; + + byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0; + + int encodedIndex = 0; + int dataIndex = 0; + + for (int i = 0; i < numberTriplets; i++) { + b1 = binaryData[dataIndex++]; + b2 = binaryData[dataIndex++]; + b3 = binaryData[dataIndex++]; + + l = (byte) (b2 & 0x0f); + k = (byte) (b1 & 0x03); + + byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0); + byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc); + + encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[(l << 2) | val3]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[b3 & 0x3f]; + } + + // form integral number of 6-bit groups + if (fewerThan24bits == EIGHTBIT) { + b1 = binaryData[dataIndex]; + k = (byte) (b1 & 0x03); + byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[k << 4]; + encodedData[encodedIndex++] = PAD; + encodedData[encodedIndex++] = PAD; + } else if (fewerThan24bits == SIXTEENBIT) { + b1 = binaryData[dataIndex]; + b2 = binaryData[dataIndex + 1]; + l = (byte) (b2 & 0x0f); + k = (byte) (b1 & 0x03); + + byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0); + + encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[l << 2]; + encodedData[encodedIndex++] = PAD; + } + return new String(encodedData); + } + + /** + * Decodes Base64 data into octects + * + * @param encoded string containing Base64 data + * @return Array containind decoded data. + */ + public static byte[] decode(String encoded) { + if (encoded == null) { + return null; + } + + char[] base64Data = encoded.toCharArray(); + // remove white spaces + int len = removeWhiteSpace(base64Data); + + if (len % FOURBYTE != 0) { + // should be divisible by four + return null; + } + + int numberQuadruple = (len / FOURBYTE); + + if (numberQuadruple == 0) { + return new byte[0]; + } + + byte[] decodedData = null; + byte b1 = 0, b2 = 0, b3 = 0, b4 = 0; + char d1 = 0, d2 = 0, d3 = 0, d4 = 0; + + int i = 0; + int encodedIndex = 0; + int dataIndex = 0; + decodedData = new byte[(numberQuadruple) * 3]; + + for (; i < numberQuadruple - 1; i++) { + + if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++])) + || !isData((d3 = base64Data[dataIndex++])) || !isData((d4 = base64Data[dataIndex++]))) { + return null; + } // if found "no data" just return null + + b1 = base64Alphabet[d1]; + b2 = base64Alphabet[d2]; + b3 = base64Alphabet[d3]; + b4 = base64Alphabet[d4]; + + decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); + decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + decodedData[encodedIndex++] = (byte) (b3 << 6 | b4); + } + + if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++]))) { + // if found "no data" just return null + return null; + } + + b1 = base64Alphabet[d1]; + b2 = base64Alphabet[d2]; + + d3 = base64Data[dataIndex++]; + d4 = base64Data[dataIndex++]; + // Check if they are PAD characters + if (!isData((d3)) || !isData((d4))) { + if (isPad(d3) && isPad(d4)) { + // last 4 bits should be zero + if ((b2 & 0xf) != 0) + { + return null; + } + byte[] tmp = new byte[i * 3 + 1]; + System.arraycopy(decodedData, 0, tmp, 0, i * 3); + tmp[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); + return tmp; + } else if (!isPad(d3) && isPad(d4)) { + b3 = base64Alphabet[d3]; + // last 2 bits should be zero + if ((b3 & 0x3) != 0) + { + return null; + } + byte[] tmp = new byte[i * 3 + 2]; + System.arraycopy(decodedData, 0, tmp, 0, i * 3); + tmp[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); + tmp[encodedIndex] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + return tmp; + } else { + return null; + } + } else { // No PAD e.g 3cQl + b3 = base64Alphabet[d3]; + b4 = base64Alphabet[d4]; + decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); + decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + decodedData[encodedIndex++] = (byte) (b3 << 6 | b4); + + } + return decodedData; + } + + /** + * remove WhiteSpace from MIME containing encoded Base64 data. + * + * @param data the byte array of base64 data (with WS) + * @return the new length + */ + private static int removeWhiteSpace(char[] data) { + if (data == null) { + return 0; + } + + // count characters that's not whitespace + int newSize = 0; + int len = data.length; + for (int i = 0; i < len; i++) { + if (!isWhiteSpace(data[i])) { + data[newSize++] = data[i]; + } + } + return newSize; + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/ClassUtil.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/ClassUtil.java new file mode 100644 index 0000000..cd1dda5 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/ClassUtil.java @@ -0,0 +1,164 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.utils; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.net.URL; +import java.net.URLDecoder; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * 类工具类 + * + * @author warm + */ +public class ClassUtil { + + public static final String RES_PROTOCOL_FILE = "file"; + public static final String RES_PROTOCOL_JAR = "jar"; + + /** + * 通过包名获取Class对象 + */ + public static Class getClazz(String className) { + try { + return Class.forName(className); + } catch (ClassNotFoundException e) { + return null; + } + } + + /** + * 通过反射实现对象克隆 + * + * @param origin 原始对象 + * @param 目标对象 + * @return 克隆结果 + */ + public static C clone(C origin) { + if (Objects.isNull(origin)) { + return null; + } + try { + // 获取对象的类信息 + Class clazz = (Class) origin.getClass(); + // 创建新的对象实例 + Constructor constructors = clazz.getConstructor(); + // 创建一个对象 + C instance = constructors.newInstance(); + // 获取对象的所有字段 + Field[] fields = clazz.getDeclaredFields(); + // 遍历字段进行赋值 + for (Field field : fields) { + // 设置可访问性 + makeAccessible(field); + // 跳过静态字段和常量字段 + int modifiers = field.getModifiers(); + if (Modifier.isStatic(modifiers) || Modifier.isFinal(modifiers)) { + continue; + } + // 获取字段的值,并设置到克隆对象中 + Object value = field.get(origin); + field.set(instance, value); + } + + return instance; + } catch (Exception e) { + return null; + } + } + + /** + * 让指定字段变为可访问 + * + * @param field field + */ + public static void makeAccessible(Field field) { + if ((!Modifier.isPublic(field.getModifiers()) + || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) + || Modifier.isFinal(field.getModifiers())) + && !field.isAccessible()) { + field.setAccessible(true); + } + } + + public static Set> findClasses(String packageName) throws IOException, ClassNotFoundException { + Set> classes = new HashSet<>(); + String packagePath = packageName.replace('.', '/'); + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + Enumeration resources = classLoader.getResources(packagePath); + while (resources.hasMoreElements()) { + URL resource = resources.nextElement(); + String resourcePath = URLDecoder.decode(resource.getPath(), "UTF-8"); + if (resourcePath.startsWith("file:")) { + resourcePath = resourcePath.substring(5); + } + if (resource.getProtocol().equals(RES_PROTOCOL_FILE)) { + findClassesInFile(packageName, resourcePath, classes); + } else if (resource.getProtocol().equals(RES_PROTOCOL_JAR)) { + // 处理 JAR 文件 + findClassesInJar(resourcePath, classes); + } + } + return classes; + } + + private static void findClassesInFile(String packageName, String resourcePath, Set> classes) throws ClassNotFoundException { + File dir = new File(resourcePath); + if (!dir.exists() || !dir.isDirectory()) { + return; + } + File[] files = dir.listFiles(); + if (files == null) { + return; + } + for (File file : files) { + if (file.isDirectory()) { + findClassesInFile(packageName + "." + file.getName(), file.getAbsolutePath(), classes); + } else if (file.getName().endsWith(".class")) { + String className = packageName + '.' + file.getName().substring(0, file.getName().length() - 6); + classes.add(Class.forName(className)); + } + } + } + + private static void findClassesInJar(String resourcePath, Set> classes) throws IOException, ClassNotFoundException { + // 提取 JAR 文件的路径 + int index = resourcePath.indexOf("!"); + String jarPath = resourcePath.substring(0, index); + String packageInJarPath = resourcePath.substring(index + 2); + try (JarFile jarFile = new JarFile(jarPath)) { + Enumeration entries = jarFile.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String entryName = entry.getName(); + if (entryName.startsWith(packageInJarPath) && entryName.endsWith(".class")) { + String className = entryName.replace('/', '.').substring(0, entryName.length() - 6); + classes.add(Class.forName(className)); + } + } + } + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/CollUtil.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/CollUtil.java new file mode 100644 index 0000000..203e2b7 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/CollUtil.java @@ -0,0 +1,242 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.utils; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +/** + * 集合工具类 + * + * @author warm + * @since 2023/5/18 9:39 + */ +public class CollUtil { + /** + * * 判断一个Collection是否为空, 包含List,Set,Queue + * + * @param coll 要判断的Collection + * @return true:为空 false:非空 + */ + public static boolean isEmpty(Collection coll) { + return ObjectUtil.isNull(coll) || coll.isEmpty(); + } + + /** + * 获取集合的第一个 + * + * @param list + */ + public static T getOne(List list) { + return CollUtil.isEmpty(list) ? null : list.get(0); + } + + /** + * * 判断一个Collection是否非空,包含List,Set,Queue + * + * @param coll 要判断的Collection + * @return true:非空 false:空 + */ + public static boolean isNotEmpty(Collection coll) { + return !isEmpty(coll); + } + + /** + * 如果集合是空,则返回空集合 + * + * @param list 集合 + * @return 结果 + */ + public static List emptyDefault(List list) { + return isEmpty(list) ? Collections.emptyList() : list; + } + + /** + * 如果集合是空,则返回默认值 + * + * @param list 集合 + * @param defaultList 默认值 + * @return 结果 + */ + public static List emptyDefault(List list, List defaultList) { + return isEmpty(list) ? defaultList : list; + } + + /** + * 判断给定的collection列表中是否包含数组array 判断给定的数组array中是否包含给定的元素value + * + * @param collection 给定的集合 + * @param array 给定的数组 + * @return boolean 结果 + */ + public static boolean containsAny(Collection collection, String... array) { + if (isEmpty(collection) || ArrayUtil.isEmpty(array)) { + return false; + } else { + for (String str : array) { + if (collection.contains(str)) { + return true; + } + } + return false; + } + } + + /** + * 判断给定的collection1列表中是否包含collection2 判断给定的collection2中是否包含给定的元素value + * + * @param collection1 给定的集合1 + * @param collection2 给定的集合2 + * @return boolean 结果 + */ + public static boolean containsAny(Collection collection1, Collection collection2) { + if (isEmpty(collection1) || isEmpty(collection2)) { + return false; + } else { + for (String str : collection2) { + if (collection1.contains(str)) { + return true; + } + } + return false; + } + } + + /** + * 判断给定的collection1列表中是否包含collection2 判断给定的collection2中是否完全不包含给定的元素value + * + * @param collection1 给定的集合1 + * @param collection2 给定的集合2 + * @return boolean 结果 + */ + public static boolean notContainsAny(Collection collection1, Collection collection2) { + return !containsAny(collection1, collection2); + } + + /** + * 字符串转数组 + * + * @param str 字符串 + * @param sep 分隔符 + * @return + */ + public static List strToColl(String str, String sep) { + return StringUtils.isEmpty(str) ? null : Arrays.asList(str.split(sep)); + } + + /** + * 集合add新的对象,返回新的集合 + * + * @param list 集合 + * @param t 对象 + * @return + */ + public static List listAddToNew(List list, T t) { + return listAddToNew(list, Collections.singletonList(t)); + } + + /** + * 集合add新的对象,返回新的集合 + * + * @param list 集合 + * @param listA 对象 + * @return + */ + public static List listAddToNew(List list, List listA) { + List newList = new ArrayList<>(); + newList.addAll(listA); + newList.addAll(list); + return newList; + } + + /** + * 几个元素生成一个集合 + * + * @param paramArr 对象数组 + * @param 泛型 + * @author xiar + * @since 2024/5/10 15:45 + */ + @SafeVarargs + public static List toList(T... paramArr) { + if (ArrayUtil.isEmpty(paramArr)) { + return new ArrayList<>(); + } + return Arrays.stream(paramArr).collect(Collectors.toList()); + } + + /** + * 将collection转化为List集合,其中一个List集合中包含多个集合
+ * {@code Collection和Collection> ------> List } + * + * @param list 需要合并得集合 + * @param lists 需要合并得包含多个集合得集合 + * @param List中的泛型 + * @return List + * @author xiarg + * @since 2024/5/10 15:45 + */ + public static List listAddListsToNew(List list, List> lists) { + List newList = new ArrayList<>(); + if (isNotEmpty(lists)) { + for (List ts : lists) { + if (isNotEmpty(ts)) { + newList.addAll(ts); + } + } + } + if (isNotEmpty(list)) { + newList.addAll(list); + } + return newList; + } + + /** + * 字符串集合拼接字符串 + * + * @param list 字符串集合 + * @param sep 分隔符 + * @return String + * @author xiar + * @since 2024/5/10 15:45 + */ + public static String strListToStr(List list, String sep) { + StringBuilder sb = new StringBuilder(); + if (isNotEmpty(list)) { + for (String str : list) { + sb.append(str).append(sep); + } + sb.deleteCharAt(sb.length() - 1); + } + return sb.toString(); + } + + /** + * 按照

batchSize

分割源集合,微批概念 + * + * @param list 源集合 + * @param batchSize 分批大小 + * @param obj + * @return 指定

batchSize

的全部list + */ + public static List> split(List list, int batchSize) { + final int N = (int) Math.ceil((double) list.size() / batchSize); + return IntStream.range(0, N).boxed() + .map(i -> list.subList(i * batchSize, Math.min((i + 1) * batchSize, list.size()))) + .collect(Collectors.toList()); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/ExceptionUtil.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/ExceptionUtil.java new file mode 100644 index 0000000..616537a --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/ExceptionUtil.java @@ -0,0 +1,47 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.utils; + + +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * 错误信息处理类。 + * + * @author warm + */ +public class ExceptionUtil { + /** + * 获取exception的详细错误信息。 + */ + public static String getExceptionMessage(Throwable e) { + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw, true)); + return sw.toString(); + } + + /** + * 处理消息是否显示中文 + */ + public static String handleMsg(String msg, Exception e) { + if (StringUtils.isEmpty(msg)) { + return e.getMessage(); + } else { + return msg + ": " + e.getMessage(); + } + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/ExpressionUtil.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/ExpressionUtil.java new file mode 100644 index 0000000..33d333a --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/ExpressionUtil.java @@ -0,0 +1,194 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.utils; + +import org.dromara.warm.flow.core.FlowEngine; +import org.dromara.warm.flow.core.condition.*; +import org.dromara.warm.flow.core.constant.ExceptionCons; +import org.dromara.warm.flow.core.dto.FlowParams; +import org.dromara.warm.flow.core.entity.Task; +import org.dromara.warm.flow.core.exception.FlowException; +import org.dromara.warm.flow.core.handler.PermissionHandler; +import org.dromara.warm.flow.core.strategy.*; +import org.dromara.warm.flow.core.handler.DefaultHandlerStrategy; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * 表达式工具类 + * + * @author warm + */ +public class ExpressionUtil { + + static { + // 注册条件表达式 + setExpression(new ConditionStrategyEq()); + setExpression(new ConditionStrategyGe()); + setExpression(new ConditionStrategyGt()); + setExpression(new ConditionStrategyLe()); + setExpression(new ConditionStrategyLike()); + setExpression(new ConditionStrategyLt()); + setExpression(new ConditionStrategyNe()); + setExpression(new ConditionStrategyNotLike()); + + // 注册办理人表达式 + setExpression(new DefaultHandlerStrategy()); + } + + /** + * 设置表达式 + * + * @param expressionStrategy 表达式策略 + * @param 表达式类型 + */ + public static void setExpression(ExpressionStrategy expressionStrategy) { + expressionStrategy.setExpression(expressionStrategy); + } + + /** + * 条件表达式替换 + * + * @param expression 条件表达式,比如“eq@@flag|4” ,或者自定义策略 + * @param variable 变量 + * @return boolean + */ + public static boolean evalCondition(String expression, Map variable) { + return Boolean.TRUE.equals(getValue(ConditionStrategy.EXPRESSION_STRATEGY_LIST, expression, variable + , ExceptionCons.NULL_CONDITION_STRATEGY)); + } + + /** + * 办理人表达式替换 + * + * @param addTasks 任务列表 + * @param flowParams 流程变量 + */ + public static void evalVariable(List addTasks, FlowParams flowParams) { + if (CollUtil.isEmpty(addTasks)) { + return; + } + Map variable = flowParams.getVariable(); + addTasks.forEach(addTask -> { + List permissions = addTask.getPermissionList().stream() + .map(s -> { + List result = evalVariable(s, variable); + if (CollUtil.isNotEmpty(result)) { + return result; + } + return Collections.singletonList(s); + }).filter(Objects::nonNull) + .flatMap(List::stream) + .distinct() + .collect(Collectors.toList()); + + // 转换办理人,比如设计器中预设了能办理的人,如果其中包含角色或者部门id等,可以通过此接口进行转换成用户id + PermissionHandler permissionHandler = FlowEngine.permissionHandler(); + if (permissionHandler != null) { + permissions = permissionHandler.convertPermissions(permissions); + } + // 自定义下个任务的处理人 下个任务处理人配置类型 和 执行的下个任务的办理人 + permissions = nextHandle(flowParams.isNextHandlerAppend(), flowParams.getNextHandler(), permissions); + + addTask.setPermissionList(permissions); + }); + } + + /** + * 办理人表达式替换 + * + * @param expression 表达式,比如“${flag}或者# { @user.notify(#listenerVariable) } ” ,或者自定义策略 + * @param variable 流程变量 + * @return List + */ + public static List evalVariable(String expression, Map variable) { + return getValue(HandlerStrategy.EXPRESSION_STRATEGY_LIST, expression, variable + , ExceptionCons.NULL_VARIABLE_STRATEGY); + } + + /** + * 监听器表达式替换 + * + * @param expression 条件表达式,比如“# { @user.notify(#listenerVariable) } ” ,或者自定义策略 + * @param variable 变量 + */ + public static boolean evalListener(String expression, Map variable) { + return Boolean.TRUE.equals(getValue(ListenerStrategy.EXPRESSION_STRATEGY_LIST, expression, variable + , ExceptionCons.NULL_LISTENER_STRATEGY)); + } + + /** + * 票签表达式替换 + * + * @param expression 表达式,比如“${flag}或者# { @user.notify(#listenerVariable) } ” ,或者自定义策略 + * @param variable 流程变量 + * @return boolean + */ + public static boolean evalVoteSign(String expression, Map variable) { + return Boolean.TRUE.equals(getValue(VoteSignStrategy.EXPRESSION_STRATEGY_LIST, expression, variable + , ExceptionCons.NULL_VOTESIGN_STRATEGY)); + } + + /** + * 获取表达式对应的值 + * + * @param strategyList 表达式策略列表 + * @param expression 变量表达式 + * @param variable 流程变量 + * @return 执行结果 + */ + private static T getValue(List> strategyList, String expression + , Map variable, String errMsg) { + if (StringUtils.isNotEmpty(expression)) { + // 倒叙遍历,优先匹配最后注入的策略实现类 + for (int i = strategyList.size() - 1; i >= 0; i--) { + ExpressionStrategy strategy = strategyList.get(i); + if (strategy == null) { + throw new FlowException(errMsg); + } + if (expression.startsWith(strategy.getType())) { + if (StringUtils.isNotEmpty(strategy.interceptStr())) { + expression = expression.replace(strategy.getType() + strategy.interceptStr(), ""); + } + return strategy.eval(expression, MapUtil.isEmpty(variable) ? new HashMap<>() : variable); + } + } + } + return null; + } + + /** + * 处理下个任务的处理人 + * + * @param nextHandlerAppend 下个任务处理人配置类型 + * @param nextHandler 下个任务的处理人 + * @param permissions 节点配置的原下个任务的处理人 + * @author xiarg + * @since 2025/06/03 15:33:38 + */ + private static List nextHandle(boolean nextHandlerAppend, String[] nextHandler, List permissions) { + if (ArrayUtil.isEmpty(nextHandler)) { + return permissions; + } + if (nextHandlerAppend) { + permissions.addAll(new ArrayList<>(Arrays.asList(nextHandler))); + } else { + permissions = new ArrayList<>(Arrays.asList(nextHandler)); + } + return permissions; + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/FlowConfigUtil.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/FlowConfigUtil.java new file mode 100644 index 0000000..03e930b --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/FlowConfigUtil.java @@ -0,0 +1,184 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.utils; + +import org.dromara.warm.flow.core.FlowEngine; +import org.dromara.warm.flow.core.constant.ExceptionCons; +import org.dromara.warm.flow.core.dto.FlowCombine; +import org.dromara.warm.flow.core.entity.Definition; +import org.dromara.warm.flow.core.entity.Node; +import org.dromara.warm.flow.core.entity.Skip; +import org.dromara.warm.flow.core.enums.NodeType; +import org.dromara.warm.flow.core.enums.SkipType; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 流程配置帮助类 + * + * @author zhoukai + */ +public class FlowConfigUtil { + /** + * 无参构造 + */ + private FlowConfigUtil() { + + } + + public static FlowCombine structureFlow(Definition definition) { + // 获取流程 + FlowCombine combine = new FlowCombine(); + // 流程定义 + combine.setDefinition(definition); + // 所有的流程节点 + List allNodes = combine.getAllNodes(); + // 所有的流程连线 + List allSkips = combine.getAllSkips(); + + String flowName = definition.getFlowName(); + AssertUtil.isEmpty(definition.getFlowCode(), "【" + flowName + "】流程flowCode为空!"); + // 发布 + definition.setIsPublish(0); + definition.setUpdateTime(new Date()); + FlowEngine.dataFillHandler().idFill(definition); + + List nodeList = definition.getNodeList(); + // 每一个流程的开始节点个数 + int startNum = 0; + Set nodeCodeSet = new HashSet<>(); + // 遍历一个流程中的各个节点 + for (Node node : nodeList) { + initNodeAndCondition(node, definition.getId(), definition.getVersion()); + startNum = checkStartAndSame(node, startNum, flowName, nodeCodeSet); + allNodes.add(node); + allSkips.addAll(node.getSkipList()); + } + Map skipMap = StreamUtils.toMap(allNodes, Node::getNodeCode, Node::getNodeType); + allSkips.forEach(allSkip -> allSkip.setNextNodeType(skipMap.get(allSkip.getNextNodeCode()))); + AssertUtil.isTrue(startNum == 0, "[" + flowName + "]" + ExceptionCons.LOST_START_NODE); + // 校验跳转节点的合法性 + checkSkipNode(allSkips); + // 校验所有目标节点是否都存在 + validaIsExistDestNode(allSkips, nodeCodeSet); + return combine; + } + + public static int checkStartAndSame(Node node, int startNum, String flowName, Set nodeCodeSet) { + if (NodeType.isStart(node.getNodeType())) { + startNum++; + AssertUtil.isTrue(startNum > 1, "[" + flowName + "]" + ExceptionCons.MUL_START_NODE); + } + // 保证不存在重复的nodeCode + AssertUtil.contains(nodeCodeSet, node.getNodeCode(), + "【" + flowName + "】" + ExceptionCons.SAME_NODE_CODE); + nodeCodeSet.add(node.getNodeCode()); + return startNum; + } + + /** + * 校验跳转节点的合法性 + * + * @param allSkips + */ + public static void checkSkipNode(List allSkips) { + Map> allSkipMap = StreamUtils.groupByKey(allSkips, Skip::getNowNodeCode); + // 不可同时通过或者退回到多个中间节点,必须先流转到网关节点 + allSkipMap.forEach((key, values) -> { + AtomicInteger passNum = new AtomicInteger(); + AtomicInteger rejectNum = new AtomicInteger(); + for (Skip value : values) { + if (NodeType.isBetween(value.getNowNodeType()) && NodeType.isBetween(value.getNextNodeType())) { + if (SkipType.isPass(value.getSkipType())) { + passNum.getAndIncrement(); + } else { + rejectNum.getAndIncrement(); + } + } + } + AssertUtil.isTrue(passNum.get() > 1 || rejectNum.get() > 1, ExceptionCons.MUL_SKIP_BETWEEN); + }); + } + + /** + * 校验所有的目标节点是否存在 + * + * @param allSkips + * @param nodeCodeSet + */ + public static void validaIsExistDestNode(List allSkips, Set nodeCodeSet) { + for (Skip allSkip : allSkips) { + String nextNodeCode = allSkip.getNextNodeCode(); + AssertUtil.isTrue(!nodeCodeSet.contains(nextNodeCode), "【" + nextNodeCode + "】" + ExceptionCons.NULL_NODE_CODE); + } + } + + + /** + * 读取工作节点和跳转条件 + * + * @param node node + * @param definitionId definitionId + * @param version version + */ + public static void initNodeAndCondition(Node node, Long definitionId, String version) { + String nodeName = node.getNodeName(); + String nodeCode = node.getNodeCode(); + List skipList = node.getSkipList(); + if (!NodeType.isEnd(node.getNodeType())) { + AssertUtil.isEmpty(skipList, ExceptionCons.MUST_SKIP); + } + AssertUtil.isEmpty(nodeCode, "[" + nodeName + "]" + ExceptionCons.LOST_NODE_CODE); + + node.setVersion(version); + node.setDefinitionId(definitionId); + + // 中间节点的集合, 跳转类型和目标节点不能重复 + Set betweenSet = new HashSet<>(); + // 网关的集合 跳转条件和下目标节点不能重复 + Set gateWaySet = new HashSet<>(); + int skipNum = 0; + // 遍历节点下的跳转条件 + if (CollUtil.isEmpty(skipList)) { + return; + } + for (Skip skip : skipList) { + if (NodeType.isStart(node.getNodeType())) { + skipNum++; + AssertUtil.isTrue(skipNum > 1, "[" + node.getNodeName() + "]" + ExceptionCons.MUL_START_SKIP); + } + AssertUtil.isEmpty(skip.getNextNodeCode(), "【" + nodeName + "】" + ExceptionCons.LOST_DEST_NODE); + // 流程id + skip.setDefinitionId(definitionId); + skip.setNowNodeType(node.getNodeType()); + if (NodeType.isGateWaySerial(node.getNodeType())) { + String target = skip.getSkipCondition() + ":" + skip.getNextNodeCode(); + AssertUtil.contains(gateWaySet, target, "[" + nodeName + "]" + ExceptionCons.SAME_CONDITION_NODE); + gateWaySet.add(target); + } else if (NodeType.isGateWayParallel(node.getNodeType())) { + String target = skip.getNextNodeCode(); + AssertUtil.contains(gateWaySet, target, "[" + nodeName + "]" + ExceptionCons.SAME_DEST_NODE); + gateWaySet.add(target); + } else { + String value = skip.getSkipType() + ":" + skip.getNextNodeCode(); + AssertUtil.contains(betweenSet, value, "[" + nodeName + "]" + ExceptionCons.SAME_CONDITION_VALUE); + betweenSet.add(value); + } + } + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/HttpStatus.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/HttpStatus.java new file mode 100644 index 0000000..4389d63 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/HttpStatus.java @@ -0,0 +1,108 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.utils; + +/** + * 返回状态码 + * + * @author warm + */ +public class HttpStatus { + /** + * 操作成功 + */ + public static final int SUCCESS = 200; + + /** + * 对象创建成功 + */ + public static final int CREATED = 201; + + /** + * 请求已经被接受 + */ + public static final int ACCEPTED = 202; + + /** + * 操作已经执行成功,但是没有返回数据 + */ + public static final int NO_CONTENT = 204; + + /** + * 资源已被移除 + */ + public static final int MOVED_PERM = 301; + + /** + * 重定向 + */ + public static final int SEE_OTHER = 303; + + /** + * 资源没有被修改 + */ + public static final int NOT_MODIFIED = 304; + + /** + * 参数列表错误(缺少,格式不匹配) + */ + public static final int BAD_REQUEST = 400; + + /** + * 未授权 + */ + public static final int UNAUTHORIZED = 401; + + /** + * 访问受限,授权过期 + */ + public static final int FORBIDDEN = 403; + + /** + * 资源,服务未找到 + */ + public static final int NOT_FOUND = 404; + + /** + * 不允许的http方法 + */ + public static final int BAD_METHOD = 405; + + /** + * 资源冲突,或者资源被锁 + */ + public static final int CONFLICT = 409; + + /** + * 不支持的数据,媒体类型 + */ + public static final int UNSUPPORTED_TYPE = 415; + + /** + * 系统内部错误 + */ + public static final int ERROR = 500; + + /** + * 接口未实现 + */ + public static final int NOT_IMPLEMENTED = 501; + + /** + * 系统警告消息 + */ + public static final int WARN = 601; +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/IdUtils.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/IdUtils.java new file mode 100644 index 0000000..7c17858 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/IdUtils.java @@ -0,0 +1,79 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.utils; + +import org.dromara.warm.flow.core.FlowEngine; +import org.dromara.warm.flow.core.constant.FlowCons; +import org.dromara.warm.flow.core.keygen.KenGen; +import org.dromara.warm.flow.core.keygen.SnowFlakeId14; +import org.dromara.warm.flow.core.keygen.SnowFlakeId15; +import org.dromara.warm.flow.core.keygen.SnowFlakeId19; + +/** + * 唯一id + * + * @author warm + * @since 2023/5/17 23:08 + */ +public class IdUtils { + + /** + * 内置id算法 + */ + private volatile static KenGen instance; + + /** + * orm框架配置了原生id算法 + */ + private static KenGen instanceNative; + + public static String nextIdStr() { + return nextId().toString(); + } + + public static Long nextId() { + return nextId(1, 1); + } + + public static Long nextId(long workerId, long datacenterId) { + if (instance == null) { + synchronized (IdUtils.class) { + if (instance == null) { + String keyType = FlowEngine.getFlowConfig().getKeyType(); + if (FlowCons.SNOWID14.equals(keyType)) { + instance = new SnowFlakeId14(workerId); + } else if (FlowCons.SNOWID15.equals(keyType)) { + instance = new SnowFlakeId15(workerId); + } + if (instance == null) { + // 如果orm框架配置了原生id算法,则使用原生id算法,否则默认使用19位内置雪花算法 + if (instanceNative != null) { + instance = instanceNative; + } else { + instance = new SnowFlakeId19(workerId, datacenterId); + } + } + } + } + } + return instance.nextId(); + } + + public static void setInstanceNative(KenGen instanceNative) { + IdUtils.instanceNative = instanceNative; + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/ListenerUtil.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/ListenerUtil.java new file mode 100644 index 0000000..8e40c88 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/ListenerUtil.java @@ -0,0 +1,143 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.utils; + +import org.dromara.warm.flow.core.FlowEngine; +import org.dromara.warm.flow.core.constant.FlowCons; +import org.dromara.warm.flow.core.entity.Definition; +import org.dromara.warm.flow.core.entity.Task; +import org.dromara.warm.flow.core.enums.NodeType; +import org.dromara.warm.flow.core.invoker.FrameInvoker; +import org.dromara.warm.flow.core.listener.GlobalListener; +import org.dromara.warm.flow.core.listener.Listener; +import org.dromara.warm.flow.core.listener.ListenerVariable; +import org.dromara.warm.flow.core.listener.ValueHolder; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; + +/** + * 监听器工具类 + * + * @author warm + */ +public class ListenerUtil { + + private ListenerUtil() { + + } + + /** + * 执行完成监听器和下一节点的开始监听器 + * + * @param listenerVariable 监听器变量 + */ + public static void endCreateListener(ListenerVariable listenerVariable) { + // 执行任务完成监听器 + executeListener(listenerVariable, Listener.LISTENER_FINISH); + // 执行任务创建监听器 + List tasks = listenerVariable.getNextTasks(); + listenerVariable.getNextNodes().forEach(node -> { + if (!NodeType.isEnd(node.getNodeType())) { + Task nextTask = StreamUtils.filterOne(tasks, task -> task.getNodeCode().equals(node.getNodeCode())); + listenerVariable.setNode(node) + .setNextNodes(null) + .setTask(nextTask) + .setNextTasks(null); + executeListener(listenerVariable, Listener.LISTENER_CREATE); + } + }); + } + + + public static void executeListener(ListenerVariable listenerVariable, String type) { + // 执行监听器 + //listenerPath({"name": "John Doe", "age": 30})@@listenerPath@@listenerPath + String listenerType = listenerVariable.getNode().getListenerType(); + execute(listenerVariable, type, listenerVariable.getNode().getListenerPath(), listenerType); + Definition definition = listenerVariable.getDefinition(); + execute(listenerVariable, type, definition.getListenerPath(), definition.getListenerType()); + GlobalListener globalListener = FlowEngine.globalListener(); + if (ObjectUtil.isNotNull(globalListener)) { + globalListener.notify(type, listenerVariable); + } + } + + public static void execute(ListenerVariable listenerVariable, String type, String listenerPaths, String listenerTypes) { + if (StringUtils.isNotEmpty(listenerTypes)) { + String[] listenerTypeArr = listenerTypes.split(","); + for (int i = 0; i < listenerTypeArr.length; i++) { + String listenerType = listenerTypeArr[i].trim(); + if (listenerType.equals(type)) { + if (StringUtils.isNotEmpty(listenerPaths)) { + String[] listenerPathArr = listenerPaths.split(FlowCons.SPLIT_AT); + String listenerPath = listenerPathArr[i].trim(); + ValueHolder valueHolder = new ValueHolder(); + //截取出path 和params + getListenerPath(listenerPath, valueHolder); + + Map expressionMap = MapUtil.newAndPut("listenerVariable", listenerVariable); + // 如果返回为true,说明配置的path是表达式,并且已经执行完,不需要执行后续加载类路径(优先执行表达式监听器) + if (ExpressionUtil.evalListener(listenerPath, expressionMap)) { + return; + } + + Class clazz = ClassUtil.getClazz(valueHolder.getPath()); + // 增加传入类路径校验Listener接口, 防止强制类型转换失败 + if (ObjectUtil.isNotNull(clazz) && Listener.class.isAssignableFrom(clazz)) { + Listener listener = (Listener) FrameInvoker.getBean(clazz); + if (ObjectUtil.isNotNull(listener)) { + Map variable = listenerVariable.getVariable(); + if (MapUtil.isEmpty(variable)) { + variable = new HashMap<>(); + } else { + variable.remove(FlowCons.WARM_LISTENER_PARAM); + } + if (StringUtils.isNotEmpty(valueHolder.getParams())) { + variable.put(FlowCons.WARM_LISTENER_PARAM, valueHolder.getParams()); + } + listener.notify(listenerVariable.setVariable(variable)); + } + + } + } + } + } + } + } + + /** + * 分别截取监听器path 和 监听器params + * String input = "listenerPath({\"name\": \"John Doe\", \"age\": 30})"; + * + * @param listenerStr + * @param valueHolder + */ + public static void getListenerPath(String listenerStr, ValueHolder valueHolder) { + String path; + String params; + + Matcher matcher = FlowCons.LISTENER_PATTERN.matcher(listenerStr); + if (matcher.find()) { + path = matcher.group(1).replaceAll("[\\(\\)]", ""); + params = matcher.group(2).replaceAll("[\\(\\)]", ""); + valueHolder.setPath(path); + valueHolder.setParams(params); + } + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/MapUtil.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/MapUtil.java new file mode 100644 index 0000000..cdd06a5 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/MapUtil.java @@ -0,0 +1,112 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.utils; + +import java.util.HashMap; +import java.util.Map; + +/** + * map工具类 + * + * @author warm + * @since 2023/5/18 9:46 + */ +public class MapUtil { + + /** + * * 判断一个Map是否为空 + * + * @param map 要判断的Map + * @return true:为空 false:非空 + */ + public static boolean isEmpty(Map map) { + return ObjectUtil.isNull(map) || map.isEmpty(); + } + + /** + * * 判断一个Map是否为空 + * + * @param map 要判断的Map + * @return true:非空 false:空 + */ + public static boolean isNotEmpty(Map map) { + return !isEmpty(map); + } + + /** + * 如果Map是空,则返回默认值 + * + * @param map Map + * @param defaultMap 默认值 + * @return 结果 + */ + public static Map emptyDefault(Map map, Map defaultMap) { + return isEmpty(map) ? defaultMap : map; + } + + /** + * 合并多个map + * + * @param maps 需要合并的map + */ + @SafeVarargs + public static Map mergeAll(Map... maps) { + Map map = new HashMap<>(); + for (Map m : maps) { + if (isNotEmpty(m)) { + map.putAll(m); + } + } + return map; + } + + /** + * 合并多个参数为 Map,每两个参数为一组 key-value + * + * @param values 参数 + */ + @SuppressWarnings("unchecked") + public static Map mergeAll(Object... values) { + Map map = new HashMap<>(); + if (ArrayUtil.isNotEmpty(values)) { + for (int i = 0; i < values.length - 1; i += 2) { + K key = (K) values[i]; + V value = (V) values[i + 1]; + map.put(key, value); + } + } + return map; + } + + public static Map newAndPut(K k, V v) { + Map map = new HashMap<>(); + map.put(k, v); + return map; + } + + /** + * 克隆一个Map + * + * @param map 要克隆的Map + * @return 克隆后的Map + */ + public static Map clone(Map map) { + if (isEmpty(map)) { + return new HashMap<>(); + } + return new HashMap<>(map); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/MathUtil.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/MathUtil.java new file mode 100644 index 0000000..4d87c9f --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/MathUtil.java @@ -0,0 +1,119 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.utils; + +import java.math.BigDecimal; + +/** + * 数字工具类 + * + * @author warm + */ +public class MathUtil { + + public final static BigDecimal ONE_HUNDRED = BigDecimal.valueOf(100); + + private MathUtil() { + } + + /** + * 判断是否为数字 + * + * @param str 字符串 + */ + public static boolean isNumeric(String str) { + // 检查字符串是否为空 + if (str == null || str.isEmpty()) { + return false; + } + try { + // 使用Double.parseDouble()方法尝试将字符串转换为double + Double.parseDouble(str.trim()); + // 转换成功,字符串是数字 + return true; + } catch (NumberFormatException e) { + // 转换失败,字符串不是数字 + return false; + } + } + + /** + * 判断数字大小 + * + * @param n1 字符串 + * @param n2 字符串 + */ + public static int determineSize(String n1, String n2) { + BigDecimal a = new BigDecimal(n1); + BigDecimal b = new BigDecimal(n2); + return a.compareTo(b); + } + + + /** + * 判断是否为0 + * + * @param str 字符串 + */ + public static boolean isZero(String str) { + if (StringUtils.isEmpty(str)) { + return false; + } + try { + BigDecimal value = new BigDecimal(str.trim()); + return value.compareTo(BigDecimal.ZERO) == 0; + } catch (NumberFormatException e) { + return false; + } + } + + /** + * 判断字符串是否等于 100 + * + * @param str 字符串 + * @return true:等于 100;false:不等于 + */ + public static boolean isHundred(String str) { + if (StringUtils.isEmpty(str)) { + return false; + } + try { + BigDecimal value = new BigDecimal(str.trim()); + return value.compareTo(ONE_HUNDRED) == 0; + } catch (NumberFormatException e) { + return false; + } + } + + /** + * 判断字符串表示的数值是否在 0 到 100 之间(含边界) + * + * @param str 字符串 + * @return true:在 [0, 100] 范围内;false:不在范围内 + */ + public static boolean isBetweenZeroAndHundred(String str) { + if (StringUtils.isEmpty(str)) { + return false; + } + try { + BigDecimal value = new BigDecimal(str.trim()); + return value.compareTo(BigDecimal.ZERO) >= 0 && value.compareTo(ONE_HUNDRED) <= 0; + } catch (NumberFormatException e) { + return false; + } + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/ObjectUtil.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/ObjectUtil.java new file mode 100644 index 0000000..9da12cb --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/ObjectUtil.java @@ -0,0 +1,87 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.utils; + +import java.util.function.Supplier; + +/** + * Object 工具类 + * + * @author warm + * @since 2023/5/18 9:42 + */ +public class ObjectUtil { + + /** + * 判断一个对象是否为空 + * + * @param object Object + * @return true:为空 false:非空 + */ + public static boolean isNull(Object object) { + return object == null; + } + + /** + * 判断一个对象是否非空 + * + * @param object Object + * @return true:非空 false:空 + */ + public static boolean isNotNull(Object object) { + return !isNull(object); + } + + /** + * 判断字符串是否为true + * + * @param str + * @return + */ + public static boolean isStrTrue(String str) { + return StringUtils.isNotEmpty(str) && "true".equals(str); + } + + /** + * 如果被检查对象为null, 返回默认值defaultValue;否则直接返回 + * + * @param source 被检查对象 + * @param defaultValue 默认值 + * @param 对象类型 + * @throws NullPointerException {@code defaultValueSupplier == null} 时,抛出 + */ + public static T defaultNull(T source, T defaultValue) { + if (isNull(source)) { + return defaultValue; + } + return source; + } + + /** + * 如果被检查对象为null, 返回默认值(由 defaultValueSupplier 提供);否则直接返回 + * + * @param source 被检查对象 + * @param defaultValueSupplier 默认值提供者 + * @param 对象类型 + * @throws NullPointerException {@code defaultValueSupplier == null} 时,抛出 + */ + public static T defaultIfNull(T source, Supplier defaultValueSupplier) { + if (isNull(source)) { + return defaultValueSupplier.get(); + } + return source; + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/ReflectionUtil.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/ReflectionUtil.java new file mode 100644 index 0000000..241aea6 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/ReflectionUtil.java @@ -0,0 +1,132 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.utils; + +import org.dromara.warm.flow.core.exception.FlowException; + +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.Supplier; + +public class ReflectionUtil { + + /** + * 扫描指定包路径下的所有接口,并获取每个接口的第一个直接实现类的对象 + * + * @param interfacePath 接口路径 + * @param implementPath 实现类路径 + * @return 实现了指定接口的类的对象集合 + */ + public static Map, Object> scanAndInstance(String interfacePath, String implementPath) { + Map, Object> instances = new HashMap<>(); + try { + Set> interfaces = ClassUtil.findClasses(interfacePath); + for (Class interfaceClass : interfaces) { + Set> implementSet = ClassUtil.findClasses(implementPath); + Class implementationClass = implementSet.stream().filter(clazz -> isDirectImplementation(clazz, interfaceClass) + && !clazz.isInterface() && !Modifier.isAbstract(clazz.getModifiers())) + .findFirst().orElse(null); + + if (implementationClass != null) { + Object instance = implementationClass.getDeclaredConstructor().newInstance(); + instances.put(interfaceClass, instance); + } + } + } catch (Exception e) { + throw new FlowException("扫描异常"); + } + + return instances; + } + + /** + * 扫描指定包路径下的所有接口,并获取每个接口的第一个直接实现类的对象 + * + * @param interfacePath 接口路径 + * @param implementPath 实现类路径 + * @return 实现了指定接口的类的对象集合 + */ + public static Map, Class> scanAndClass(String interfacePath, String implementPath) { + Map, Class> instances = new HashMap<>(); + try { + Set> interfaces = ClassUtil.findClasses(interfacePath); + for (Class interfaceClass : interfaces) { + Set> implementSet = ClassUtil.findClasses(implementPath); + implementSet.stream().filter(clazz -> isDirectImplementation(clazz, interfaceClass) + && !clazz.isInterface() && !Modifier.isAbstract(clazz.getModifiers())) + .findFirst().ifPresent(implementationClass -> instances.put(interfaceClass, implementationClass)); + + } + } catch (Exception e) { + throw new FlowException("扫描异常"); + } + + + return instances; + } + + /** + * 扫描指定包路径下的所有接口,并获取每个接口的第一个直接实现类的Supplier + * + * @param interfacePath 接口路径 + * @param implementPath 实现类路径 + * @return 实现了指定接口的类的Supplier集合 + */ + public static Map, Supplier> scanAndSupplier(String interfacePath, String implementPath) { + Map, Supplier> suppliers = new HashMap<>(); + try { + Set> interfaces = ClassUtil.findClasses(interfacePath); + for (Class interfaceClass : interfaces) { + Set> implementSet = ClassUtil.findClasses(implementPath); + implementSet.stream().filter(clazz -> isDirectImplementation(clazz, interfaceClass) + && !clazz.isInterface() && !Modifier.isAbstract(clazz.getModifiers())) + .findFirst().ifPresent(implementationClass -> suppliers.put(interfaceClass, () -> { + try { + return implementationClass.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + throw new FlowException("扫描异常"); + } + })); + + } + } catch (Exception e) { + throw new FlowException("扫描异常"); + } + + return suppliers; + } + + /** + * 检查 clazz 是否直接实现了 interfaceClass + * + * @param clazz 类 + * @param interfaceClass 接口 + * @return 是否直接实现了接口 + */ + private static boolean isDirectImplementation(Class clazz, Class interfaceClass) { + // 获取 clazz 直接实现的所有接口 + Class[] interfaces = clazz.getInterfaces(); + for (Class iface : interfaces) { + if (iface.equals(interfaceClass)) { + return true; + } + } + return false; + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/ServiceLoaderUtil.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/ServiceLoaderUtil.java new file mode 100644 index 0000000..8f5fbfc --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/ServiceLoaderUtil.java @@ -0,0 +1,149 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.utils; + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.*; + +/** + * SPI机制中的服务加载工具类 + * + * @author warm + */ +public class ServiceLoaderUtil { + + /** + * 。加载第一个可用服务,如果用户定义了多个接口实现类,只获取第一个不报错的服务 + * + * @param 接口类型 + * @param clazz 服务接口 + * @return 第一个服务接口实现对象,无实现返回{@code null} + */ + public static T loadFirst(Class clazz) { + final Iterator iterator = load(clazz).iterator(); + while (iterator.hasNext()) { + try { + return iterator.next(); + } catch (ServiceConfigurationError ignore) { + + } + } + return null; + } + + /** + * 加载服务 并已list列表返回 + * + * @param 接口类型 + * @param clazz 服务接口 + * @return 服务接口实现列表 + * @since 5.4.2 + */ + public static List loadList(Class clazz) { + List list = new ArrayList<>(); + ServiceLoader serviceLoader = load(clazz); + Iterator serviceIterator = serviceLoader.iterator(); + while (serviceIterator.hasNext()) { + try { + list.add(serviceIterator.next()); + } catch (ServiceConfigurationError ignore) { + + } + } + return list; + } + + /** + * 加载服务 + * + * @param 接口类型 + * @param clazz 服务接口 + * @return 服务接口实现列表 + */ + public static ServiceLoader load(Class clazz) { + return load(clazz, null); + } + + /** + * 加载服务 + * + * @param 接口类型 + * @param clazz 服务接口 + * @param loader {@link ClassLoader} + * @return 服务接口实现列表 + */ + public static ServiceLoader load(Class clazz, ClassLoader loader) { + return ServiceLoader.load(clazz, ObjectUtil.defaultIfNull(loader, ServiceLoaderUtil::getClassLoader)); + } + + /** + * 获取{@link ClassLoader}
+ * 获取顺序如下:
+ * + *
+     * 1、获取当前线程的ContextClassLoader
+     * 2、获取当前类对应的ClassLoader
+     * 3、获取系统ClassLoader({@link ClassLoader#getSystemClassLoader()})
+     * 
+ * + * @return 类加载器 + */ + public static ClassLoader getClassLoader() { + ClassLoader classLoader = getContextClassLoader(); + if (classLoader == null) { + classLoader = ServiceLoaderUtil.class.getClassLoader(); + if (null == classLoader) { + classLoader = getSystemClassLoader(); + } + } + return classLoader; + } + + /** + * 获取当前线程的{@link ClassLoader} + * + * @return 当前线程的class loader + * @see Thread#getContextClassLoader() + */ + public static ClassLoader getContextClassLoader() { + if (System.getSecurityManager() == null) { + return Thread.currentThread().getContextClassLoader(); + } else { + // 绕开权限检查 + return AccessController.doPrivileged( + (PrivilegedAction) () -> Thread.currentThread().getContextClassLoader()); + } + } + + /** + * 获取系统{@link ClassLoader} + * + * @return 系统{@link ClassLoader} + * @see ClassLoader#getSystemClassLoader() + * @since 5.7.0 + */ + public static ClassLoader getSystemClassLoader() { + if (System.getSecurityManager() == null) { + return ClassLoader.getSystemClassLoader(); + } else { + // 绕开权限检查 + return AccessController.doPrivileged( + (PrivilegedAction) ClassLoader::getSystemClassLoader); + } + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/SqlHelper.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/SqlHelper.java new file mode 100644 index 0000000..b663f82 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/SqlHelper.java @@ -0,0 +1,57 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.utils; + +/** + * SQL 辅助类 + * + * @author warm + * @since 2023/5/3 15:22 + */ +public final class SqlHelper { + + + /** + * 判断数据库操作是否成功 + * + * @param result 数据库操作返回影响条数 + * @return boolean + */ + public static boolean retBool(Integer result) { + return null != result && result >= 1; + } + + /** + * 判断数据库操作是否成功 + * + * @param result 数据库操作返回影响条数 + * @return boolean + */ + public static boolean retBool(Long result) { + return null != result && result >= 1; + } + + /** + * 返回SelectCount执行结果 + * + * @param result ignore + * @return int + */ + public static long retCount(Long result) { + return (null == result) ? 0 : result; + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/StreamUtils.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/StreamUtils.java new file mode 100644 index 0000000..13fe5d4 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/StreamUtils.java @@ -0,0 +1,331 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.utils; + + +import java.util.*; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * stream 流工具类 + * + * @author warm + */ +public class StreamUtils { + + private StreamUtils() { + } + + /** + * 将collection过滤 + * + * @param collection 需要转化的集合 + * @param function 过滤方法 + * @return 过滤后的list + */ + public static List filter(Collection collection, Predicate function) { + if (CollUtil.isEmpty(collection)) { + return new ArrayList<>(); + } + // 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题 + return collection.stream().filter(function).collect(Collectors.toList()); + } + + /** + * 将collection过滤 + * + * @param collection 需要转化的集合 + * @param function 过滤方法 + * @return 过滤后的list + */ + public static E filterOne(Collection collection, Predicate function) { + if (CollUtil.isEmpty(collection)) { + return null; + } + // 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题 + List collect = collection.stream().filter(function).collect(Collectors.toList()); + if (collect.isEmpty()) { + return null; + } + return CollUtil.getOne(collect); + } + + /** + * 将collection拼接 + * + * @param collection 需要转化的集合 + * @param function 拼接方法 + * @return 拼接后的list + */ + public static String join(Collection collection, Function function) { + return join(collection, function, ","); + } + + /** + * 将collection拼接 + * + * @param collection 需要转化的集合 + * @param function 拼接方法 + * @param delimiter 拼接符 + * @return 拼接后的list + */ + public static String join(Collection collection, Function function, CharSequence delimiter) { + if (CollUtil.isEmpty(collection)) { + return ""; + } + return collection.stream().map(function).filter(Objects::nonNull).collect(Collectors.joining(delimiter)); + } + + /** + * 将collection排序 + * + * @param collection 需要转化的集合 + * @param comparing 排序方法 + * @return 排序后的list + */ + public static List sorted(Collection collection, Comparator comparing) { + if (CollUtil.isEmpty(collection)) { + return new ArrayList<>(); + } + // 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题 + return collection.stream().filter(Objects::nonNull).sorted(comparing).collect(Collectors.toList()); + } + + /** + * 将collection转化为类型不变的map
+ * {@code Collection ----> Map} + * + * @param collection 需要转化的集合 + * @param key V类型转化为K类型的lambda方法 + * @param collection中的泛型 + * @param map中的key类型 + * @return 转化后的map + */ + public static Map toIdentityMap(Collection collection, Function key) { + if (CollUtil.isEmpty(collection)) { + return new HashMap<>(); + } + return collection.stream().filter(Objects::nonNull) + .collect(Collectors.toMap(key, Function.identity(), (l, r) -> l)); + } + + /** + * 将Collection转化为map(value类型与collection的泛型不同)
+ * {@code Collection -----> Map } + * + * @param collection 需要转化的集合 + * @param key E类型转化为K类型的lambda方法 + * @param value E类型转化为V类型的lambda方法 + * @param collection中的泛型 + * @param map中的key类型 + * @param map中的value类型 + * @return 转化后的map + */ + public static Map toMap(Collection collection, Function key, Function value) { + if (CollUtil.isEmpty(collection)) { + return new HashMap<>(); + } + return collection.stream().filter(Objects::nonNull).collect(Collectors.toMap(key, value, (l, r) -> l)); + } + + /** + * 将collection按照规则(比如有相同的班级id)分类成map
+ * {@code Collection -------> Map> } + * + * @param collection 需要分类的集合 + * @param key 分类的规则 + * @param collection中的泛型 + * @param map中的key类型 + * @return 分类后的map + */ + public static Map> groupByKey(Collection collection, Function key) { + if (CollUtil.isEmpty(collection)) { + return new HashMap<>(); + } + return collection.stream().filter(Objects::nonNull) + .collect(Collectors.groupingBy(key, LinkedHashMap::new, Collectors.toList())); + } + + /** + * 将collection按照规则(比如有相同的班级id)分类成map
+ * {@code Collection -------> Map> } + * + * @param collection 需要分类的集合 + * @param key 分类的规则 + * @param collection中的泛型 + * @param map中的key类型 + * @return 分类后的map + */ + public static Map> groupByKeyFilter(Predicate predicate, Collection collection, Function key) { + if (CollUtil.isEmpty(collection)) { + return new HashMap<>(); + } + return collection.stream().filter(predicate) + .collect(Collectors.groupingBy(key, LinkedHashMap::new, Collectors.toList())); + } + + /** + * 将collection按照两个规则(比如有相同的年级id,班级id)分类成双层map
+ * {@code Collection ---> Map>> } + * + * @param collection 需要分类的集合 + * @param key1 第一个分类的规则 + * @param key2 第二个分类的规则 + * @param 集合元素类型 + * @param 第一个map中的key类型 + * @param 第二个map中的key类型 + * @return 分类后的map + */ + public static Map>> groupBy2Key(Collection collection, Function key1, + Function key2) { + if (CollUtil.isEmpty(collection)) { + return new HashMap<>(); + } + return collection.stream().filter(Objects::nonNull).collect(Collectors.groupingBy(key1, LinkedHashMap::new, + Collectors.groupingBy(key2, LinkedHashMap::new, Collectors.toList()))); + } + + /** + * 将collection按照两个规则(比如有相同的年级id,班级id)分类成双层map
+ * {@code Collection ---> Map> } + * + * @param collection 需要分类的集合 + * @param key1 第一个分类的规则 + * @param key2 第二个分类的规则 + * @param 第一个map中的key类型 + * @param 第二个map中的key类型 + * @param collection中的泛型 + * @return 分类后的map + */ + public static Map> group2Map(Collection collection, Function key1, + Function key2) { + if (CollUtil.isEmpty(collection) || key1 == null || key2 == null) { + return new HashMap<>(); + } + return collection.stream().filter(Objects::nonNull).collect( + Collectors.groupingBy(key1, LinkedHashMap::new, Collectors.toMap(key2, Function.identity(), (l, r) -> l))); + } + + /** + * 将collection转化为List集合,但是两者的泛型不同
+ * {@code Collection ------> List } + * + * @param collection 需要转化的集合 + * @param function collection中的泛型转化为list泛型的lambda表达式 + * @param collection中的泛型 + * @param List中的泛型 + * @return 转化后的list + */ + public static List toList(Collection collection, Function function) { + if (CollUtil.isEmpty(collection)) { + return new ArrayList<>(); + } + return collection.stream().map(function).filter(Objects::nonNull) + // 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题 + .collect(Collectors.toList()); + } + + /** + * 将collection转化为List集合,但是两者的泛型不同
+ * {@code Collection ------> List } + * + * @param collection 需要转化的集合 + * @param function collection中的泛型转化为list泛型的lambda表达式 + * @param collection中的泛型 + * @param List中的泛型 + * @return 转化后的list + */ + public static List toListAll(Collection collection, Function> function) { + if (CollUtil.isEmpty(collection)) { + return new ArrayList<>(); + } + return collection.stream().map(function).flatMap(List::stream) + .collect(Collectors.toList()); + } + + /** + * 将collection转化为Set集合,但是两者的泛型不同
+ * {@code Collection ------> Set } + * + * @param collection 需要转化的集合 + * @param function collection中的泛型转化为set泛型的lambda表达式 + * @param collection中的泛型 + * @param Set中的泛型 + * @return 转化后的Set + */ + public static Set toSet(Collection collection, Function function) { + if (CollUtil.isEmpty(collection) || function == null) { + return new HashSet<>(); + } + return collection.stream().map(function).filter(Objects::nonNull).collect(Collectors.toSet()); + } + + /** + * 将collection转化为List集合,但是两者的泛型不同
+ * {@code Collection ------> List } + * + * @param collection 需要转化的集合 + * @param generator collection中的泛型转化为list泛型的lambda表达式 + * @param collection中的泛型 + * @return 转化后的list + */ + public static E[] toArray(Collection collection, IntFunction generator) { + if (CollUtil.isEmpty(collection)) { + return generator.apply(0); + } + return collection.stream().toArray(generator); + } + + /** + * 合并两个相同key类型的map + * + * @param map1 第一个需要合并的 map + * @param map2 第二个需要合并的 map + * @param merge 合并的lambda,将key value1 value2合并成最终的类型,注意value可能为空的情况 + * @param map中的key类型 + * @param 第一个 map的value类型 + * @param 第二个 map的value类型 + * @param 最终map的value类型 + * @return 合并后的map + */ + public static Map merge(Map map1, Map map2, BiFunction merge) { + if (MapUtil.isEmpty(map1) && MapUtil.isEmpty(map2)) { + return new HashMap<>(16); + } else if (MapUtil.isEmpty(map1)) { + map1 = new HashMap<>(16); + } else if (MapUtil.isEmpty(map2)) { + map2 = new HashMap<>(16); + } + Set key = new HashSet<>(); + key.addAll(map1.keySet()); + key.addAll(map2.keySet()); + Map map = new HashMap<>(16); + for (K t : key) { + X x = map1.get(t); + Y y = map2.get(t); + V z = merge.apply(x, y); + if (z != null) { + map.put(t, z); + } + } + return map; + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/StringUtils.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/StringUtils.java new file mode 100644 index 0000000..e6ce56f --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/StringUtils.java @@ -0,0 +1,461 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.utils; + +import java.util.*; + +/** + * 字符串工具类 + * + * @author warm + */ +public class StringUtils { + /** + * 空字符串 + */ + public static final String NULLPTR = ""; + + /** + * 下划线 + */ + public static final char SEPARATOR = '_'; + + /** + * 字符串0 + */ + public static final String ZERO = "0"; + + /** + * 获取参数不为空值 + * + * @param value defaultValue 要判断的value + * @return value 返回值 + */ + public static T nvl(T value, T defaultValue) { + return value != null ? value : defaultValue; + } + + /** + * * 判断一个字符串是否为空串 + * + * @param str String + * @return true:为空 false:非空 + */ + public static boolean isEmpty(String str) { + return str == null || str.trim().isEmpty(); + } + + /** + * * 判断一个字符串是否为非空串 + * + * @param str String + * @return true:非空串 false:空串 + */ + public static boolean isNotEmpty(String str) { + return !isEmpty(str); + } + + /** + * 如果字符串是空,则返回默认值 + * + * @param str 字符串 + * @param defaultStr 默认值 + * @return 结果 + */ + public static String emptyDefault(String str, String defaultStr) { + return isEmpty(str) ? defaultStr : str; + } + + /** + * 指定字符串数组中,是否包含空字符串 + * + * @param args args + * @return 是否为空 + */ + public static boolean hasEmpty(String... args) { + if (ArrayUtil.isEmpty(args)) { + return true; + } + for (String str : args) { + if (isEmpty(str)) { + return true; + } + } + return false; + } + + /** + * 是否存都不为null或空对象或空白符的对象 + * + * @param args args + * @return 全都不为空 = true; + */ + public static boolean isAllNotEmpty(String... args) { + return !hasEmpty(args); + } + + /** + * 去空格 + */ + public static String trim(String str) { + return (str == null ? "" : str.trim()); + } + + /** + * 判断给定的collection列表中是否包含数组array 判断给定的数组array中是否包含给定的元素value + * + * @param collection 给定的集合 + * @param array 给定的数组 + * @return boolean 结果 + */ + public static boolean containsAny(Collection collection, String... array) { + if (CollUtil.isEmpty(collection) || ArrayUtil.isEmpty(array)) { + return false; + } + for (String str : array) { + if (collection.contains(str)) { + return true; + } + } + return false; + } + + /** + * 截取字符串 + * + * @param str 字符串 + * @param start 开始 + * @return 结果 + */ + public static String substring(final String str, int start) { + if (str == null) { + return NULLPTR; + } + + if (start < 0) { + start = str.length() + start; + } + + if (start < 0) { + start = 0; + } + if (start > str.length()) { + return NULLPTR; + } + + return str.substring(start); + } + + /** + * 截取字符串 + * + * @param str 字符串 + * @param start 开始 + * @param end 结束 + * @return 结果 + */ + public static String substring(final String str, int start, int end) { + if (str == null) { + return NULLPTR; + } + + if (end < 0) { + end = str.length() + end; + } + if (start < 0) { + start = str.length() + start; + } + + if (end > str.length()) { + end = str.length(); + } + + if (start > end) { + return NULLPTR; + } + + if (start < 0) { + start = 0; + } + if (end < 0) { + end = 0; + } + + return str.substring(start, end); + } + + /** + * 字符串转set + * + * @param str 字符串 + * @param sep 分隔符 + * @return set集合 + */ + public static Set str2Set(String str, String sep) { + return new HashSet<>(str2List(str, sep, true, false)); + } + + /** + * 字符串转list + * + * @param str 字符串 + * @param sep 分隔符 + * @return list集合 + */ + public static List str2List(String str, String sep) { + return str2List(str, sep, true, true); + } + + /** + * 字符串转list + * + * @param str 字符串 + * @param sep 分隔符 + * @param filterBlank 过滤纯空白 + * @param trim 去掉首尾空白 + * @return list集合 + */ + public static List str2List(String str, String sep, boolean filterBlank, boolean trim) { + List list = new ArrayList(); + if (isEmpty(str)) { + return list; + } + + // 过滤空白字符串 + if (filterBlank && isEmpty(str)) { + return list; + } + String[] split = str.split(sep); + for (String string : split) { + if (filterBlank && isEmpty(string)) { + continue; + } + if (trim) { + string = string.trim(); + } + list.add(string); + } + + return list; + } + + /** + * 驼峰转下划线命名 + */ + public static String toUnderScoreCase(String str) { + if (str == null) { + return null; + } + StringBuilder sb = new StringBuilder(); + // 前置字符是否大写 + boolean preCharIsUpperCase = true; + // 当前字符是否大写 + boolean curreCharIsUpperCase = true; + // 下一字符是否大写 + boolean nexteCharIsUpperCase = true; + for (int i = 0; i < str.length(); i++) { + char c = str.charAt(i); + if (i > 0) { + preCharIsUpperCase = Character.isUpperCase(str.charAt(i - 1)); + } else { + preCharIsUpperCase = false; + } + + curreCharIsUpperCase = Character.isUpperCase(c); + + if (i < (str.length() - 1)) { + nexteCharIsUpperCase = Character.isUpperCase(str.charAt(i + 1)); + } + + if (preCharIsUpperCase && curreCharIsUpperCase && !nexteCharIsUpperCase) { + sb.append(SEPARATOR); + } else if ((i != 0 && !preCharIsUpperCase) && curreCharIsUpperCase) { + sb.append(SEPARATOR); + } + sb.append(Character.toLowerCase(c)); + } + + return sb.toString(); + } + + /** + * 是否包含字符串 + * + * @param str 验证字符串 + * @param strs 字符串组 + * @return 包含返回true + */ + public static boolean inStringIgnoreCase(String str, String... strs) { + if (str != null && strs != null) { + for (String s : strs) { + if (str.equalsIgnoreCase(trim(s))) { + return true; + } + } + } + return false; + } + + /** + * 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 例如:HELLO_WORLD->HelloWorld + * + * @param name 转换前的下划线大写方式命名的字符串 + * @return 转换后的驼峰式命名的字符串 + */ + public static String convertToCamelCase(String name) { + StringBuilder result = new StringBuilder(); + // 快速检查 + if (name == null || name.isEmpty()) { + // 没必要转换 + return ""; + } else if (!name.contains("_")) { + // 不含下划线,仅将首字母大写 + return name.substring(0, 1).toUpperCase() + name.substring(1); + } + // 用下划线将原始字符串分割 + String[] camels = name.split("_"); + for (String camel : camels) { + // 跳过原始字符串中开头、结尾的下换线或双重下划线 + if (camel.isEmpty()) { + continue; + } + // 首字母大写 + result.append(camel.substring(0, 1).toUpperCase()); + result.append(camel.substring(1).toLowerCase()); + } + return result.toString(); + } + + /** + * 驼峰式命名法 + * 例如:user_name->userName + */ + public static String toCamelCase(String s) { + if (s == null) { + return null; + } + if (s.indexOf(SEPARATOR) == -1) { + return s; + } + s = s.toLowerCase(); + StringBuilder sb = new StringBuilder(s.length()); + boolean upperCase = false; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + + if (c == SEPARATOR) { + upperCase = true; + } else if (upperCase) { + sb.append(Character.toUpperCase(c)); + upperCase = false; + } else { + sb.append(c); + } + } + return sb.toString(); + } + + /** + * 数字左边补齐0,使之达到指定长度。注意,如果数字转换为字符串后,长度大于size,则只保留 最后size个字符。 + * + * @param num 数字对象 + * @param size 字符串指定长度 + * @return 返回数字的字符串格式,该字符串为指定长度。 + */ + public static String padLeft(final Number num, final int size) { + return padLeft(num.toString(), size, '0'); + } + + /** + * 字符串左补齐。如果原始字符串s长度大于size,则只保留最后size个字符。 + * + * @param s 原始字符串 + * @param size 字符串指定长度 + * @param c 用于补齐的字符 + * @return 返回指定长度的字符串,由原字符串左补齐或截取得到。 + */ + public static String padLeft(final String s, final int size, final char c) { + final StringBuilder sb = new StringBuilder(size); + if (s != null) { + final int len = s.length(); + if (s.length() <= size) { + for (int i = size - len; i > 0; i--) { + sb.append(c); + } + sb.append(s); + } else { + return s.substring(len - size, len); + } + } else { + for (int i = size; i > 0; i--) { + sb.append(c); + } + } + return sb.toString(); + } + + /** + * 字符串拼接 + */ + @SafeVarargs + public static String join(T... elements) { + return join(elements, null); + } + + /** + * 自定义分隔符的拼接 + * + * @param array 待拼接对象数组 + * @param delimiter 分隔符 + * @return 拼接后的字符串 + */ + public static String join(Object[] array, String delimiter) { + return array == null ? null : join(array, delimiter, 0, array.length); + } + + /** + * 指定位置拼接 + * + * @param array 待拼接对象数组 + * @param delimiter 分隔符 + * @param startIndex 起始位置 + * @param endIndex 终止位置 + * @return 拼接后的字符串 + */ + public static String join(Object[] array, String delimiter, int startIndex, int endIndex) { + if (array == null) { + return null; + } else if (endIndex - startIndex <= 0) { + return ""; + } else { + StringJoiner joiner = new StringJoiner(toStringOrEmpty(delimiter)); + for (int i = startIndex; i < endIndex; ++i) { + joiner.add(toStringOrEmpty(array[i])); + } + return joiner.toString(); + } + } + + /** + * 字符串默认值 + * + * @param obj 原始对象 + * @return 对象转成字符串Result + */ + private static String toStringOrEmpty(Object obj) { + return Objects.toString(obj, ""); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/page/OrderBy.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/page/OrderBy.java new file mode 100644 index 0000000..d56491a --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/page/OrderBy.java @@ -0,0 +1,39 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.utils.page; + +/** + * @author vanlin + * @since 2024/5/12 0:00 + */ +public interface OrderBy { + + /** + * 获取排序字段 + * + * @return 排序字段 + */ + String getOrderBy(); + + /** + * 排序策略,是否 Asc + * + * @return 是的话返回 asc,不是 desc + */ + String getIsAsc(); + + String ASC = "asc"; +} diff --git a/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/page/Page.java b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/page/Page.java new file mode 100644 index 0000000..dd9a004 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-core/src/main/java/org/dromara/warm/flow/core/utils/page/Page.java @@ -0,0 +1,127 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.core.utils.page; + +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * 分页 + * + * @author warm + * @since 2023/5/17 1:28 + */ +@Getter +@Setter +public class Page implements OrderBy, Serializable { + + private static final long serialVersionUID = -1615974051898019272L; + + /** + * 当前记录起始索引 + */ + private int pageNum = 1; + + /** + * 每页显示记录数 + */ + private int pageSize = 10; + + /** + * 数据 + */ + private List list; + + /** + * 总量 + */ + private long total; + + /** + * 排序列 + */ + private String orderBy; + + /** + * 排序的方向desc或者asc + */ + private String isAsc = "ASC"; + + public Page() { + } + + public Page(int pageNum, int pageSize) { + this.pageNum = pageNum; + this.pageSize = pageSize; + } + + public Page(int pageNum, int pageSize, String orderBy, String isAsc) { + this.pageNum = pageNum; + this.pageSize = pageSize; + this.orderBy = orderBy; + this.isAsc = isAsc; + } + + public Page(List list, long total) { + this.list = list; + this.total = total; + } + + public Page(long total) { + this.list = new ArrayList<>(); + this.total = total; + } + + public static Page empty() { + return new Page<>(0); + } + + /** + * 计算分页起始页 + * + * @param pageNum 当前页码 + * @param size 每页显示记录数 + * @return 分页结果 + */ + public static Page pageOf(Integer pageNum, Integer size) { + return new Page<>(pageNum, size); + } + + @Override + public String getOrderBy() { + return orderBy; + } + + public Page setOrderBy(String orderBy) { + this.orderBy = orderBy; + return this; + } + + @Override + public String getIsAsc() { + return isAsc; + } + + public Page setIsAsc(String isAsc) { + this.isAsc = isAsc; + return this; + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/pom.xml b/smart-flow-api/warm-flow/warm-flow-orm/pom.xml new file mode 100644 index 0000000..126ce8a --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + org.dromara.warm + warm-flow + 1.8.4 + + + warm-flow-orm + warm-flow-orm + pom + https://github.com/dromara/warm-flow + + + warm-flow-mybatis + warm-flow-mybatis-plus + + + diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/pom.xml b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/pom.xml new file mode 100644 index 0000000..723d242 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/pom.xml @@ -0,0 +1,24 @@ + + + 4.0.0 + + org.dromara.warm + warm-flow-orm + 1.8.4 + + + warm-flow-mybatis-plus + warm-flow-mybatis-plus + pom + https://github.com/dromara/warm-flow + + + warm-flow-mybatis-plus-core + warm-flow-mybatis-plus-sb-starter + warm-flow-mybatis-plus-sb3-starter + warm-flow-mybatis-plus-solon-plugin + + + diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/pom.xml b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/pom.xml new file mode 100644 index 0000000..42515af --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + org.dromara.warm + warm-flow-mybatis-plus + 1.8.4 + + + warm-flow-mybatis-plus-core + warm-flow-mybatis-plus-core + https://github.com/dromara/warm-flow + + + + org.dromara.warm + warm-flow-core + + + com.baomidou + mybatis-plus-extension + + + org.projectlombok + lombok + provided + + + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowDefinitionDaoImpl.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowDefinitionDaoImpl.java new file mode 100644 index 0000000..e5eb5cf --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowDefinitionDaoImpl.java @@ -0,0 +1,58 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.dao; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.dromara.warm.flow.core.invoker.FrameInvoker; +import org.dromara.warm.flow.core.orm.dao.FlowDefinitionDao; +import org.dromara.warm.flow.orm.entity.FlowDefinition; +import org.dromara.warm.flow.orm.mapper.FlowDefinitionMapper; + +import java.util.List; + +/** + * 流程定义Mapper接口 + * + * @author warm + * @since 2023-03-29 + */ +public class FlowDefinitionDaoImpl extends WarmDaoImpl implements FlowDefinitionDao { + + @Override + public FlowDefinitionMapper getMapper() { + return FrameInvoker.getBean(FlowDefinitionMapper.class); + } + + @Override + public FlowDefinition newEntity() { + return new FlowDefinition(); + } + + @Override + public List queryByCodeList(List flowCodeList) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(FlowDefinition::getFlowCode, flowCodeList); + return getMapper().selectList(queryWrapper); + } + + @Override + public void updatePublishStatus(List ids, Integer publishStatus) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(FlowDefinition::getId, ids); + getMapper().update(new FlowDefinition().setIsPublish(publishStatus), queryWrapper); + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowFormDaoImpl.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowFormDaoImpl.java new file mode 100644 index 0000000..0c1d871 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowFormDaoImpl.java @@ -0,0 +1,51 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.dao; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.dromara.warm.flow.core.invoker.FrameInvoker; +import org.dromara.warm.flow.core.orm.dao.FlowFormDao; +import org.dromara.warm.flow.orm.entity.FlowForm; +import org.dromara.warm.flow.orm.mapper.FlowFormMapper; +import org.dromara.warm.flow.orm.mapper.WarmMapper; + +import java.util.List; + +/** + * @author vanlin + * @className FlowFormDaoImpl + * @description + * @since 2024-12-9 15:54 + */ +public class FlowFormDaoImpl extends WarmDaoImpl implements FlowFormDao { + @Override + public WarmMapper getMapper() { + return FrameInvoker.getBean(FlowFormMapper.class); + } + + @Override + public List queryByCodeList(List formCodeList) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(FlowForm::getFormCode, formCodeList); + return getMapper().selectList(queryWrapper); + } + + + @Override + public FlowForm newEntity() { + return new FlowForm(); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowHisTaskDaoImpl.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowHisTaskDaoImpl.java new file mode 100644 index 0000000..916b05a --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowHisTaskDaoImpl.java @@ -0,0 +1,77 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.dao; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.dromara.warm.flow.core.enums.SkipType; +import org.dromara.warm.flow.core.invoker.FrameInvoker; +import org.dromara.warm.flow.core.orm.dao.FlowHisTaskDao; +import org.dromara.warm.flow.core.utils.CollUtil; +import org.dromara.warm.flow.orm.entity.FlowHisTask; +import org.dromara.warm.flow.orm.mapper.FlowHisTaskMapper; + +import java.util.Arrays; +import java.util.List; + +/** + * 历史任务记录Mapper接口 + * + * @author warm + * @since 2023-03-29 + */ +public class FlowHisTaskDaoImpl extends WarmDaoImpl implements FlowHisTaskDao { + + @Override + public FlowHisTaskMapper getMapper() { + return FrameInvoker.getBean(FlowHisTaskMapper.class); + } + + @Override + public FlowHisTask newEntity() { + return new FlowHisTask(); + } + + @Override + public List getNoReject(Long instanceId) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(FlowHisTask::getInstanceId, instanceId) + .eq(FlowHisTask::getSkipType, SkipType.PASS.getKey()) + .orderByDesc(FlowHisTask::getCreateTime); + return getMapper().selectList(queryWrapper); + } + + @Override + public List getByInsAndNodeCodes(Long instanceId, List nodeCodes) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(FlowHisTask::getInstanceId, instanceId) + .in(CollUtil.isNotEmpty(nodeCodes), FlowHisTask::getNodeCode, nodeCodes) + .orderByDesc(FlowHisTask::getCreateTime); + return getMapper().selectList(queryWrapper); + } + + @Override + public int deleteByInsIds(List instanceIds) { + return getMapper().delete(new LambdaQueryWrapper().in(FlowHisTask::getInstanceId, instanceIds)); + } + + @Override + public List listByTaskIdAndCooperateTypes(Long taskId, Integer[] cooperateTypes) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(FlowHisTask::getTaskId, taskId).in(FlowHisTask::getCooperateType, Arrays.asList(cooperateTypes)); + return getMapper().selectList(queryWrapper); + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowInstanceDaoImpl.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowInstanceDaoImpl.java new file mode 100644 index 0000000..c735d92 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowInstanceDaoImpl.java @@ -0,0 +1,50 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.dao; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.dromara.warm.flow.core.invoker.FrameInvoker; +import org.dromara.warm.flow.core.orm.dao.FlowInstanceDao; +import org.dromara.warm.flow.orm.entity.FlowInstance; +import org.dromara.warm.flow.orm.mapper.FlowInstanceMapper; + +import java.util.List; + +/** + * 流程实例Mapper接口 + * + * @author warm + * @since 2023-03-29 + */ +public class FlowInstanceDaoImpl extends WarmDaoImpl implements FlowInstanceDao { + + @Override + public FlowInstanceMapper getMapper() { + return FrameInvoker.getBean(FlowInstanceMapper.class); + } + + @Override + public FlowInstance newEntity() { + return new FlowInstance(); + } + + @Override + public List getByDefIds(List defIds) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(FlowInstance::getDefinitionId, defIds); + return getMapper().selectList(queryWrapper); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowNodeDaoImpl.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowNodeDaoImpl.java new file mode 100644 index 0000000..640dd6f --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowNodeDaoImpl.java @@ -0,0 +1,67 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.dao; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.dromara.warm.flow.core.invoker.FrameInvoker; +import org.dromara.warm.flow.core.orm.dao.FlowNodeDao; +import org.dromara.warm.flow.core.utils.CollUtil; +import org.dromara.warm.flow.orm.entity.FlowNode; +import org.dromara.warm.flow.orm.mapper.FlowNodeMapper; + +import java.io.Serializable; +import java.util.Collection; +import java.util.List; + + +/** + * 流程节点Mapper接口 + * + * @author warm + * @since 2023-03-29 + */ +public class FlowNodeDaoImpl extends WarmDaoImpl implements FlowNodeDao { + + @Override + public FlowNodeMapper getMapper() { + return FrameInvoker.getBean(FlowNodeMapper.class); + } + + @Override + public FlowNode newEntity() { + return new FlowNode(); + } + + @Override + public List getByNodeCodes(List nodeCodes, Long definitionId) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(CollUtil.isNotEmpty(nodeCodes), FlowNode::getNodeCode, nodeCodes) + .eq(FlowNode::getDefinitionId, definitionId); + return getMapper().selectList(queryWrapper); + } + + /** + * 批量删除流程节点 + * + * @param defIds 需要删除的数据主键集合 + * @return 结果 + */ + @Override + public int deleteNodeByDefIds(Collection defIds) { + return getMapper().delete(new LambdaQueryWrapper().in(FlowNode::getDefinitionId, defIds)); + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowSkipDaoImpl.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowSkipDaoImpl.java new file mode 100644 index 0000000..65b4914 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowSkipDaoImpl.java @@ -0,0 +1,55 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.dao; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.dromara.warm.flow.core.invoker.FrameInvoker; +import org.dromara.warm.flow.core.orm.dao.FlowSkipDao; +import org.dromara.warm.flow.orm.entity.FlowSkip; +import org.dromara.warm.flow.orm.mapper.FlowSkipMapper; + +import java.io.Serializable; +import java.util.Collection; + +/** + * 节点跳转关联Mapper接口 + * + * @author warm + * @since 2023-03-29 + */ +public class FlowSkipDaoImpl extends WarmDaoImpl implements FlowSkipDao { + + @Override + public FlowSkipMapper getMapper() { + return FrameInvoker.getBean(FlowSkipMapper.class); + } + + @Override + public FlowSkip newEntity() { + return new FlowSkip(); + } + + /** + * 批量删除节点跳转关联 + * + * @param defIds 需要删除的数据主键集合 + * @return 结果 + */ + @Override + public int deleteSkipByDefIds(Collection defIds) { + return getMapper().delete(new LambdaQueryWrapper().in(FlowSkip::getDefinitionId, defIds)); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowTaskDaoImpl.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowTaskDaoImpl.java new file mode 100644 index 0000000..cf15f81 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowTaskDaoImpl.java @@ -0,0 +1,62 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.dao; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.dromara.warm.flow.core.invoker.FrameInvoker; +import org.dromara.warm.flow.core.orm.dao.FlowTaskDao; +import org.dromara.warm.flow.orm.entity.FlowTask; +import org.dromara.warm.flow.orm.mapper.FlowTaskMapper; + +import java.util.List; + +/** + * 待办任务Mapper接口 + * + * @author warm + * @since 2023-03-29 + */ +public class FlowTaskDaoImpl extends WarmDaoImpl implements FlowTaskDao { + + @Override + public FlowTaskMapper getMapper() { + return FrameInvoker.getBean(FlowTaskMapper.class); + } + + @Override + public FlowTask newEntity() { + return new FlowTask(); + } + + /** + * 根据instanceIds删除 + * + * @param instanceIds 主键 + * @return 结果 + */ + @Override + public int deleteByInsIds(List instanceIds) { + return getMapper().delete(new LambdaQueryWrapper().in(FlowTask::getInstanceId, instanceIds)); + } + + @Override + public List getByInsIdAndNodeCodes(Long instanceId, List nodeCodes) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(FlowTask::getInstanceId, instanceId); + queryWrapper.in(FlowTask::getNodeCode, nodeCodes); + return getMapper().selectList(queryWrapper); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowUserDaoImpl.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowUserDaoImpl.java new file mode 100644 index 0000000..8c6acd8 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowUserDaoImpl.java @@ -0,0 +1,81 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.dao; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.dromara.warm.flow.core.invoker.FrameInvoker; +import org.dromara.warm.flow.core.orm.dao.FlowUserDao; +import org.dromara.warm.flow.core.utils.ArrayUtil; +import org.dromara.warm.flow.core.utils.CollUtil; +import org.dromara.warm.flow.core.utils.ObjectUtil; +import org.dromara.warm.flow.orm.entity.FlowUser; +import org.dromara.warm.flow.orm.mapper.FlowUserMapper; + +import java.util.Arrays; +import java.util.List; + +/** + * 流程用户Mapper接口 + * + * @author warm + * @since 2023-03-29 + */ +public class FlowUserDaoImpl extends WarmDaoImpl implements FlowUserDao { + + @Override + public FlowUserMapper getMapper() { + return FrameInvoker.getBean(FlowUserMapper.class); + } + + @Override + public FlowUser newEntity() { + return new FlowUser(); + } + + @Override + public int deleteByTaskIds(List taskIdList) { + return getMapper().delete(new LambdaQueryWrapper().in(FlowUser::getAssociated, taskIdList)); + } + + @Override + public List listByAssociatedAndTypes(List associatedList, String[] types) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + if (CollUtil.isNotEmpty(associatedList)) { + if (associatedList.size() == 1) { + queryWrapper.eq(FlowUser::getAssociated, associatedList.get(0)); + } else { + queryWrapper.in(FlowUser::getAssociated, associatedList); + } + } + queryWrapper.in(ArrayUtil.isNotEmpty(types), FlowUser::getType, Arrays.asList(types)); + return getMapper().selectList(queryWrapper); + } + + @Override + public List listByProcessedBys(Long associated, List processedBys, String[] types) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(ObjectUtil.isNotNull(associated), FlowUser::getAssociated, associated); + if (CollUtil.isNotEmpty(processedBys)) { + if (processedBys.size() == 1) { + queryWrapper.eq(FlowUser::getProcessedBy, processedBys.get(0)); + } else { + queryWrapper.in(FlowUser::getProcessedBy, processedBys); + } + } + queryWrapper.in(ArrayUtil.isNotEmpty(types), FlowUser::getType, types); + return getMapper().selectList(queryWrapper); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/dao/WarmDaoImpl.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/dao/WarmDaoImpl.java new file mode 100644 index 0000000..0ca5aed --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/dao/WarmDaoImpl.java @@ -0,0 +1,145 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.dao; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.enums.SqlKeyword; +import org.dromara.warm.flow.core.entity.RootEntity; +import org.dromara.warm.flow.core.orm.agent.WarmQuery; +import org.dromara.warm.flow.core.orm.dao.WarmDao; +import org.dromara.warm.flow.core.utils.ObjectUtil; +import org.dromara.warm.flow.core.utils.StringUtils; +import org.dromara.warm.flow.core.utils.page.Page; +import org.dromara.warm.flow.orm.mapper.WarmMapper; + +import java.io.Serializable; +import java.util.Collection; +import java.util.List; + +/** + * BaseMapper接口 + * + * @author warm + * @since 2023-03-17 + */ +public abstract class WarmDaoImpl implements WarmDao { + + public abstract WarmMapper getMapper(); + + /** + * 根据id查询 + * + * @param id 主键 + * @return 实体 + */ + @Override + public T selectById(Serializable id) { + return getMapper().selectById(id); + } + + /** + * 根据ids查询 + * + * @param ids 主键 + * @return 实体 + */ + @Override + public List selectByIds(Collection ids) { + return getMapper().selectBatchIds(ids); + } + + @Override + public Page selectPage(T entity, Page page) { + com.baomidou.mybatisplus.extension.plugins.pagination.Page pagePlus = + new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(page.getPageNum(), page.getPageSize()); + + QueryWrapper queryWrapper = new QueryWrapper<>(entity); + queryWrapper.orderBy(StringUtils.isNotEmpty(page.getOrderBy()) + , page.getIsAsc().equals(SqlKeyword.ASC.getSqlSegment()), page.getOrderBy()); + + com.baomidou.mybatisplus.extension.plugins.pagination.Page tPage = getMapper().selectPage(pagePlus, queryWrapper); + + if (ObjectUtil.isNotNull(tPage)) { + Page rPage = new Page<>(tPage.getRecords(), tPage.getTotal()); + rPage.setPageNum(page.getPageNum()); + rPage.setPageSize(page.getPageSize()); + return rPage; + } + return Page.empty(); + } + + @Override + public List selectList(T entity, WarmQuery query) { + QueryWrapper queryWrapper = new QueryWrapper<>(entity); + if (ObjectUtil.isNotNull(query)) { + queryWrapper.orderBy(StringUtils.isNotEmpty(query.getOrderBy()) + , query.getIsAsc().equals(SqlKeyword.ASC.getSqlSegment()), query.getOrderBy()); + } + return getMapper().selectList(queryWrapper); + } + + @Override + public long selectCount(T entity) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(entity); + queryWrapper.setEntityClass((Class) entity.getClass()); + return getMapper().selectCount(queryWrapper); + } + + @Override + public int save(T entity) { + return getMapper().insert(entity); + } + + @Override + public int updateById(T entity) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.setEntityClass((Class) entity.getClass()); + queryWrapper.eq(T::getId, entity.getId()); + return getMapper().update(entity, queryWrapper); + } + + @Override + public int delete(T entity) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(entity); + return getMapper().delete(queryWrapper); + } + + @Override + public int deleteById(Serializable id) { + return getMapper().deleteById(id); + } + + @Override + public int deleteByIds(Collection ids) { + return getMapper().deleteBatchIds(ids); + } + + @Override + public void saveBatch(List list) { + for (T record : list) { + save(record); + } + } + + @Override + public void updateBatch(List list) { + for (T record : list) { + updateById(record); + } + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowDefinition.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowDefinition.java new file mode 100644 index 0000000..699e02b --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowDefinition.java @@ -0,0 +1,145 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.experimental.Accessors; +import org.dromara.warm.flow.core.entity.Definition; +import org.dromara.warm.flow.core.entity.Node; +import org.dromara.warm.flow.core.entity.User; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * 流程定义对象 flow_definition + * + * @author warm + * @since 2023-03-29 + */ +@Data +@Accessors(chain = true) +@TableName("flow_definition") +public class FlowDefinition implements Definition { + + /** + * 主键 + */ + @TableId + private Long id; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private Date createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private Date updateTime; + + /** + * 创建人 + */ + private String createBy; + + /** + * 更新人 + */ + private String updateBy; + + /** + * 租户ID + */ + private String tenantId; + + /** + * 删除标记 + */ + @TableLogic(value = "0", delval = "1") + private String delFlag; + + /** + * 流程编码 + */ + private String flowCode; + + /** + * 流程名称 + */ + private String flowName; + + /** + * 设计器模型(CLASSICS经典模型 MIMIC仿钉钉模型) + */ + private String modelValue; + + /** + * 流程类别 + */ + private String category; + + /** + * 流程版本 + */ + private String version; + + /** + * 是否发布(0未开启 1开启) + */ + private Integer isPublish; + + /** + * 审批表单是否自定义(Y是 N否) + */ + private String formCustom; + + /** + * 审批表单路径 + */ + private String formPath; + + /** + * 流程激活状态(0挂起 1激活) + */ + private Integer activityStatus; + + /** + * 监听器类型 + */ + private String listenerType; + + /** + * 监听器路径 + */ + private String listenerPath; + + /** + * 扩展字段,预留给业务系统使用 + */ + private String ext; + + @TableField(exist = false) + private List nodeList = new ArrayList<>(); + + @TableField(exist = false) + private List userList = new ArrayList<>(); + +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowForm.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowForm.java new file mode 100644 index 0000000..ad4c2b0 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowForm.java @@ -0,0 +1,115 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.experimental.Accessors; +import org.dromara.warm.flow.core.entity.Form; + +import java.util.Date; + + +/** + * @author vanlin + * @since 2024-9-23 16:15 + */ +@Data +@Accessors(chain = true) +@TableName("flow_form") +public class FlowForm implements Form { + + /** + * 主键 + */ + @TableId + private Long id; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private Date createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private Date updateTime; + + /** + * 创建人 + */ + private String createBy; + + /** + * 更新人 + */ + private String updateBy; + + /** + * 删除标记 + */ + @TableLogic(value = "0", delval = "1") + private String delFlag; + + /** + * 表单编码 + */ + private String formCode; + + /** + * 表单名称 + */ + private String formName; + + /** + * 表单版本 + */ + private String version; + + /** + * 是否发布(0未发布 1已发布 9失效) + */ + private Integer isPublish; + + /** + * 表单类型(0内置表单 存 form_content 1外挂表单 存form_path) + */ + private Integer formType; + + /** + * 表单路径 + */ + private String formPath; + + /** + * 表单内容 + */ + private String formContent; + + /** + * 表单扩展,用户自行使用 + */ + private String ext; + + /** + * 租户ID + */ + private String tenantId; + + +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowHisTask.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowHisTask.java new file mode 100644 index 0000000..add831d --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowHisTask.java @@ -0,0 +1,174 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.experimental.Accessors; +import org.dromara.warm.flow.core.entity.HisTask; + +import java.util.Date; +import java.util.List; + +/** + * 历史任务记录对象 flow_his_task + * + * @author warm + * @since 2023-03-29 + */ +@Data +@Accessors(chain = true) +@TableName("flow_his_task") +public class FlowHisTask implements HisTask { + + /** + * 主键 + */ + @TableId + private Long id; + + /** + * 任务开始时间 + */ + @TableField(fill = FieldFill.INSERT) + private Date createTime; + + /** + * 审批完成时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private Date updateTime; + + /** + * 租户ID + */ + private String tenantId; + + /** + * 删除标记 + */ + @TableLogic(value = "0", delval = "1") + private String delFlag; + + /** + * 对应flow_definition表的id + */ + private Long definitionId; + + /** + * 流程名称 + */ + @TableField(exist = false) + private String flowName; + + /** + * 流程实例表id + */ + private Long instanceId; + + /** + * 任务表id + */ + private Long taskId; + + /** + * 协作方式(1审批 2转办 3委派 4会签 5票签 6加签 7减签) + */ + private Integer cooperateType; + + /** + * 业务id + */ + @TableField(exist = false) + private String businessId; + + /** + * 开始节点编码 + */ + private String nodeCode; + + /** + * 开始节点名称 + */ + private String nodeName; + + /** + * 开始节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关) + */ + private Integer nodeType; + + /** + * 目标节点编码 + */ + private String targetNodeCode; + + /** + * 结束节点名称 + */ + private String targetNodeName; + + /** + * 审批者 + */ + private String approver; + + /** + * 协作人(只有转办、会签、票签、委派) + */ + private String collaborator; + + /** + * 权限标识 permissionFlag的list形式 + */ + @TableField(exist = false) + private List permissionList; + + /** + * 跳转类型(PASS通过 REJECT退回 NONE无动作) + */ + private String skipType; + + /** + * 流程状态(0待提交 1审批中 2审批通过 4终止 5作废 6撤销 8已完成 9已退回 10失效 11拿回) + */ + private String flowStatus; + + /** + * 审批意见 + */ + private String message; + + /** + * 流程变量 + */ + private String variable; + + /** + * 业务详情 存业务类的json + */ + private String ext; + + /** + * 审批表单是否自定义(Y=是 N=否) + */ + private String formCustom; + + /** + * 审批表单路径 + */ + private String formPath; + +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowInstance.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowInstance.java new file mode 100644 index 0000000..eb60d98 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowInstance.java @@ -0,0 +1,143 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.experimental.Accessors; +import org.dromara.warm.flow.core.entity.Instance; + +import java.util.Date; + +/** + * 流程实例对象 flow_instance + * + * @author warm + * @since 2023-03-29 + */ +@Data +@Accessors(chain = true) +@TableName("flow_instance") +public class FlowInstance implements Instance { + + /** + * 主键 + */ + @TableId + private Long id; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private Date createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private Date updateTime; + + /** + * 创建人 + */ + private String createBy; + + /** + * 更新人 + */ + private String updateBy; + + /** + * 租户ID + */ + private String tenantId; + + /** + * 删除标记 + */ + @TableLogic(value = "0", delval = "1") + private String delFlag; + + /** + * 对应flow_definition表的id + */ + private Long definitionId; + + /** + * 流程名称 + */ + @TableField(exist = false) + private String flowName; + + /** + * 业务id + */ + private String businessId; + + /** + * 节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关) + */ + private Integer nodeType; + + /** + * 流程节点编码 每个流程的nodeCode是唯一的,即definitionId+nodeCode唯一,在数据库层面做了控制 + */ + private String nodeCode; + + /** + * 流程节点名称 + */ + private String nodeName; + + /** + * 流程变量 + */ + private String variable; + + /** + * 流程状态(0待提交 1审批中 2审批通过 4终止 5作废 6撤销 8已完成 9已退回 10失效 11拿回) + */ + private String flowStatus; + + /** + * 流程激活状态(0挂起 1激活) + */ + private Integer activityStatus; + + /** + * 审批表单是否自定义(Y=是 N=否) + */ + @TableField(exist = false) + private String formCustom; + + /** + * 审批表单是否自定义(Y=是 N=否) + */ + @TableField(exist = false) + private String formPath; + + /** + * 流程定义json + */ + private String defJson; + + /** + * 扩展字段,预留给业务系统使用 + */ + private String ext; + +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowNode.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowNode.java new file mode 100644 index 0000000..53f6c65 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowNode.java @@ -0,0 +1,146 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.experimental.Accessors; +import org.dromara.warm.flow.core.entity.Node; +import org.dromara.warm.flow.core.entity.Skip; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * 流程节点对象 flow_node + * + * @author warm + * @since 2023-03-29 + */ +@Data +@Accessors(chain = true) +@TableName("flow_node") +public class FlowNode implements Node { + + /** + * 跳转条件 + */ + @TableField(exist = false) + List skipList = new ArrayList<>(); + /** + * 主键 + */ + @TableId + private Long id; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private Date createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private Date updateTime; + + /** + * 创建人 + */ + private String createBy; + + /** + * 更新人 + */ + private String updateBy; + + /** + * 租户ID + */ + private String tenantId; + + /** + * 删除标记 + */ + @TableLogic(value = "0", delval = "1") + private String delFlag; + + /** + * 节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关) + */ + private Integer nodeType; + /** + * 流程id + */ + private Long definitionId; + /** + * 流程节点编码 每个流程的nodeCode是唯一的,即definitionId+nodeCode唯一,在数据库层面做了控制 + */ + private String nodeCode; + /** + * 流程节点名称 + */ + private String nodeName; + /** + * 权限标识(权限类型:权限标识,可以多个,用@@隔开) + */ + private String permissionFlag; + /** + * 流程签署比例值 + */ + private String nodeRatio; + /** + * 流程节点坐标 + */ + private String coordinate; + /** + * 版本 + * + * @deprecated 下个版本废弃 + */ + @Deprecated + private String version; + /** + * 任意结点跳转 + */ + private String anyNodeSkip; + /** + * 监听器类型 + */ + private String listenerType; + /** + * 监听器路径 + */ + private String listenerPath; + /** + * 审批表单是否自定义(Y=是 N=否) + */ + private String formCustom; + + /** + * 审批表单路径 + */ + private String formPath; + + /** + * 节点扩展属性 + */ + private String ext; + +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowSkip.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowSkip.java new file mode 100644 index 0000000..3d944a2 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowSkip.java @@ -0,0 +1,127 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.experimental.Accessors; +import org.dromara.warm.flow.core.entity.Skip; + +import java.util.Date; + + +/** + * 节点跳转关联对象 flow_skip + * + * @author warm + * @since 2023-03-29 + */ +@Data +@Accessors(chain = true) +@TableName("flow_skip") +public class FlowSkip implements Skip { + + /** + * 主键 + */ + @TableId + private Long id; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private Date createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private Date updateTime; + + /** + * 创建人 + */ + private String createBy; + + /** + * 更新人 + */ + private String updateBy; + + /** + * 租户ID + */ + private String tenantId; + + /** + * 删除标记 + */ + @TableLogic(value = "0", delval = "1") + private String delFlag; + + /** + * 流程id + */ + private Long definitionId; + + /** + * 节点id + */ + @TableField(exist = false) + private Long nodeId; + + /** + * 当前流程节点的编码 + */ + private String nowNodeCode; + + /** + * 当前节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关) + */ + private Integer nowNodeType; + + /** + * 下一个流程节点的编码 + */ + private String nextNodeCode; + + /** + * 下一个节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关) + */ + private Integer nextNodeType; + + /** + * 跳转名称 + */ + private String skipName; + + /** + * 跳转类型(PASS审批通过 REJECT退回) + */ + private String skipType; + + /** + * 跳转条件 + */ + private String skipCondition; + + /** + * 流程跳转坐标 + */ + private String coordinate; + +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowTask.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowTask.java new file mode 100644 index 0000000..58c01af --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowTask.java @@ -0,0 +1,141 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.experimental.Accessors; +import org.dromara.warm.flow.core.entity.Task; +import org.dromara.warm.flow.core.entity.User; + +import java.util.Date; +import java.util.List; + +/** + * 待办任务记录对象 flow_task + * + * @author warm + * @since 2023-03-29 + */ +@Data +@Accessors(chain = true) +@TableName("flow_task") +public class FlowTask implements Task { + + /** + * 主键 + */ + @TableId + private Long id; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private Date createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private Date updateTime; + + /** + * 创建人 + */ + private String createBy; + + /** + * 更新人 + */ + private String updateBy; + + /** + * 租户ID + */ + private String tenantId; + + /** + * 删除标记 + */ + @TableLogic(value = "0", delval = "1") + private String delFlag; + + /** + * 对应flow_definition表的id + */ + private Long definitionId; + + /** + * 流程实例表id + */ + private Long instanceId; + + /** + * 流程名称 + */ + @TableField(exist = false) + private String flowName; + + /** + * 业务id + */ + @TableField(exist = false) + private String businessId; + + /** + * 节点编码 + */ + private String nodeCode; + + /** + * 节点名称 + */ + private String nodeName; + + /** + * 节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关) + */ + private Integer nodeType; + + /** + * 流程状态(0待提交 1审批中 2审批通过 4终止 5作废 6撤销 8已完成 9已退回 10失效 11拿回) + */ + private String flowStatus; + + /** + * 权限标识 permissionFlag的list形式 + */ + @TableField(exist = false) + private List permissionList; + + /** + * 流程用户列表 + */ + @TableField(exist = false) + private List userList; + + /** + * 审批表单是否自定义(Y=是 N=否) + */ + private String formCustom; + + /** + * 审批表单 + */ + private String formPath; + +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowUser.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowUser.java new file mode 100644 index 0000000..0a2f517 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowUser.java @@ -0,0 +1,91 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.experimental.Accessors; +import org.dromara.warm.flow.core.entity.User; + +import java.util.Date; + + +/** + * 流程用户对象 flow_user + * + * @author xiarg + * @since 2024/5/10 10:58 + */ +@Data +@Accessors(chain = true) +@TableName("flow_user") +public class FlowUser implements User { + + /** + * 主键 + */ + @TableId + private Long id; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private Date createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private Date updateTime; + + /** + * 创建人:比如作为委托的人保存 + */ + private String createBy; + + /** + * 更新人 + */ + private String updateBy; + + /** + * 租户ID + */ + private String tenantId; + + /** + * 删除标记 + */ + @TableLogic(value = "0", delval = "1") + private String delFlag; + + /** + * 人员类型(1待办任务的审批人权限 2待办任务的转办人权限 3待办任务的委托人权限) + */ + private String type; + + /** + * 权限人 + */ + private String processedBy; + + /** + * 任务表id + */ + private Long associated; + +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/keygen/MybatisPlusIdGen.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/keygen/MybatisPlusIdGen.java new file mode 100644 index 0000000..926b6ec --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/keygen/MybatisPlusIdGen.java @@ -0,0 +1,45 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.keygen; + +import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator; +import com.baomidou.mybatisplus.core.toolkit.IdWorker; +import org.dromara.warm.flow.core.invoker.FrameInvoker; +import org.dromara.warm.flow.core.keygen.KenGen; + +/** + * MybatisPlusIdGen + * + * @author warm + */ +public class MybatisPlusIdGen implements KenGen { + + + /** + * 获取唯一ID + * + * @return id + */ + @Override + public synchronized long nextId() { + IdentifierGenerator bean = FrameInvoker.getBean(IdentifierGenerator.class); + if (bean != null) { + return bean.nextId(null).longValue(); + } + return IdWorker.getId(); + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowDefinitionMapper.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowDefinitionMapper.java new file mode 100644 index 0000000..1247114 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowDefinitionMapper.java @@ -0,0 +1,28 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.mapper; + +import org.dromara.warm.flow.orm.entity.FlowDefinition; + +/** + * 流程定义Mapper接口 + * + * @author warm + * @since 2023-03-29 + */ +public interface FlowDefinitionMapper extends WarmMapper { + +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowFormMapper.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowFormMapper.java new file mode 100644 index 0000000..736a23a --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowFormMapper.java @@ -0,0 +1,27 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.mapper; + +import org.dromara.warm.flow.orm.entity.FlowForm; + +/** + * @author vanlin + * @className FlowFormMapper + * @description + * @since 2024-9-23 16:19 + */ +public interface FlowFormMapper extends WarmMapper { +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowHisTaskMapper.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowHisTaskMapper.java new file mode 100644 index 0000000..ed5f2b8 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowHisTaskMapper.java @@ -0,0 +1,28 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.mapper; + +import org.dromara.warm.flow.orm.entity.FlowHisTask; + +/** + * 历史任务记录Mapper接口 + * + * @author warm + * @since 2023-03-29 + */ +public interface FlowHisTaskMapper extends WarmMapper { + +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowInstanceMapper.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowInstanceMapper.java new file mode 100644 index 0000000..ed68f0d --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowInstanceMapper.java @@ -0,0 +1,28 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.mapper; + +import org.dromara.warm.flow.orm.entity.FlowInstance; + +/** + * 流程实例Mapper接口 + * + * @author warm + * @since 2023-03-29 + */ +public interface FlowInstanceMapper extends WarmMapper { + +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowNodeMapper.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowNodeMapper.java new file mode 100644 index 0000000..7915d8b --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowNodeMapper.java @@ -0,0 +1,29 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.mapper; + +import org.dromara.warm.flow.orm.entity.FlowNode; + + +/** + * 流程节点Mapper接口 + * + * @author warm + * @since 2023-03-29 + */ +public interface FlowNodeMapper extends WarmMapper { + +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowSkipMapper.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowSkipMapper.java new file mode 100644 index 0000000..3ea5ac8 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowSkipMapper.java @@ -0,0 +1,28 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.mapper; + +import org.dromara.warm.flow.orm.entity.FlowSkip; + +/** + * 节点跳转关联Mapper接口 + * + * @author warm + * @since 2023-03-29 + */ +public interface FlowSkipMapper extends WarmMapper { + +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowTaskMapper.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowTaskMapper.java new file mode 100644 index 0000000..d8ca71b --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowTaskMapper.java @@ -0,0 +1,41 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.mapper; + +import org.apache.ibatis.annotations.Param; +import org.dromara.warm.flow.core.entity.HisTask; +import org.dromara.warm.flow.orm.entity.FlowHisTask; +import org.dromara.warm.flow.orm.entity.FlowTask; + +import java.util.List; + +/** + * 待办任务Mapper接口 + * + * @author warm + * @since 2023-03-29 + */ +public interface FlowTaskMapper extends WarmMapper { + /** + * 查询活动任务 + * + * @param instanceId 实例 id + * @return 活动任务列表 + */ + List queryActiveTask(@Param("instanceId") Long instanceId); + + Integer setNextApprUser(@Param("instanceId") Long instanceId, @Param("employeeId") String employeeId); +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowUserMapper.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowUserMapper.java new file mode 100644 index 0000000..849e0ad --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowUserMapper.java @@ -0,0 +1,28 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.mapper; + +import org.dromara.warm.flow.orm.entity.FlowUser; + +/** + * 流程用户Mapper接口 + * + * @author xiarg + * @since 2024/5/10 11:16 + */ +public interface FlowUserMapper extends WarmMapper { + +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/mapper/WarmMapper.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/mapper/WarmMapper.java new file mode 100644 index 0000000..9fa293b --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/java/org/dromara/warm/flow/orm/mapper/WarmMapper.java @@ -0,0 +1,29 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.dromara.warm.flow.core.entity.RootEntity; + +/** + * BaseMapper接口 + * + * @author warm + * @since 2023-03-17 + */ +public interface WarmMapper extends BaseMapper { + +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/resources/warm/flow/FlowDefinitionMapper.xml b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/resources/warm/flow/FlowDefinitionMapper.xml new file mode 100644 index 0000000..db69f23 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/resources/warm/flow/FlowDefinitionMapper.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/resources/warm/flow/FlowFormMapper.xml b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/resources/warm/flow/FlowFormMapper.xml new file mode 100644 index 0000000..95998ca --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/resources/warm/flow/FlowFormMapper.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/resources/warm/flow/FlowHisTaskMapper.xml b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/resources/warm/flow/FlowHisTaskMapper.xml new file mode 100644 index 0000000..72e7fd5 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/resources/warm/flow/FlowHisTaskMapper.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/resources/warm/flow/FlowInstanceMapper.xml b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/resources/warm/flow/FlowInstanceMapper.xml new file mode 100644 index 0000000..c211ed7 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/resources/warm/flow/FlowInstanceMapper.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/resources/warm/flow/FlowNodeMapper.xml b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/resources/warm/flow/FlowNodeMapper.xml new file mode 100644 index 0000000..73414cb --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/resources/warm/flow/FlowNodeMapper.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/resources/warm/flow/FlowSkipMapper.xml b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/resources/warm/flow/FlowSkipMapper.xml new file mode 100644 index 0000000..078a520 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/resources/warm/flow/FlowSkipMapper.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/resources/warm/flow/FlowTaskMapper.xml b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/resources/warm/flow/FlowTaskMapper.xml new file mode 100644 index 0000000..77a0d96 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/resources/warm/flow/FlowTaskMapper.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/resources/warm/flow/FlowUserMapper.xml b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/resources/warm/flow/FlowUserMapper.xml new file mode 100644 index 0000000..e8cc63f --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-core/src/main/resources/warm/flow/FlowUserMapper.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-sb-starter/pom.xml b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-sb-starter/pom.xml new file mode 100644 index 0000000..6870d30 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-sb-starter/pom.xml @@ -0,0 +1,45 @@ + + + 4.0.0 + + org.dromara.warm + warm-flow-mybatis-plus + 1.8.4 + + + warm-flow-mybatis-plus-sb-starter + warm-flow-mybatis-plus-sb-starter + warm-flow orm为mybatis-plus的springboot starter + https://github.com/dromara/warm-flow + + + + org.dromara.warm + warm-flow-mybatis-plus-core + + + + com.baomidou + mybatis-plus-boot-starter + + + + com.baomidou + mybatis-plus-jsqlparser-4.9 + + + + org.springframework + spring-tx + + + + org.dromara.warm + warm-flow-plugin-modes-sb + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-sb-starter/src/main/java/org/dromara/warm/flow/spring/boot/config/FlowAutoConfig.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-sb-starter/src/main/java/org/dromara/warm/flow/spring/boot/config/FlowAutoConfig.java new file mode 100644 index 0000000..4cb51b9 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-sb-starter/src/main/java/org/dromara/warm/flow/spring/boot/config/FlowAutoConfig.java @@ -0,0 +1,42 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.spring.boot.config; + +import org.dromara.warm.flow.core.config.WarmFlow; +import org.dromara.warm.flow.core.utils.IdUtils; +import org.dromara.warm.flow.orm.keygen.MybatisPlusIdGen; +import org.dromara.warm.plugin.modes.sb.config.BeanConfig; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Configuration; + +/** + * 工作流bean注册配置 + * + * @author warm + * @since 2023/6/5 23:01 + */ +@Configuration +@ConditionalOnProperty(value = "warm-flow.enabled", havingValue = "true", matchIfMissing = true) +@MapperScan("org.dromara.warm.flow.orm.mapper") +public class FlowAutoConfig extends BeanConfig { + + @Override + public void after(WarmFlow flowConfig) { + // 设置Mybatis-Plus默认主键生成器 + IdUtils.setInstanceNative(new MybatisPlusIdGen()); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-sb-starter/src/main/resources/META-INF/spring.factories b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-sb-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..6543e11 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-sb-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + org.dromara.warm.flow.spring.boot.config.FlowAutoConfig diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-sb-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-sb-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..264198f --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-sb-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +org.dromara.warm.flow.spring.boot.config.FlowAutoConfig diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-sb3-starter/pom.xml b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-sb3-starter/pom.xml new file mode 100644 index 0000000..03164cb --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-sb3-starter/pom.xml @@ -0,0 +1,91 @@ + + + 4.0.0 + + org.dromara.warm + warm-flow-mybatis-plus + 1.8.4 + + + warm-flow-mybatis-plus-sb3-starter + warm-flow-mybatis-plus-sb3-starter + warm-flow orm为mybatis-plus的springboot3 starter + https://github.com/dromara/warm-flow + + + 3.5.12 + + + + + org.dromara.warm + warm-flow-mybatis-plus-sb-starter + + + mybatis-plus-extension + com.baomidou + + + com.baomidou + mybatis-plus-boot-starter + + + + + + com.baomidou + mybatis-plus-spring-boot3-starter + ${mybatis-plus.version} + + + + com.baomidou + mybatis-plus-jsqlparser + ${mybatis-plus.version} + + + + + + + + org.springframework.boot + spring-boot-dependencies + ${springboot3.version} + pom + import + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + ${java17.version} + ${java17.version} + true + ${java17.path}\javac + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.0 + + + + ${java17.version} + + + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-solon-plugin/pom.xml b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-solon-plugin/pom.xml new file mode 100644 index 0000000..2e7b414 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-solon-plugin/pom.xml @@ -0,0 +1,49 @@ + + + 4.0.0 + + org.dromara.warm + warm-flow-mybatis-plus + 1.8.4 + + + warm-flow-mybatis-plus-solon-plugin + warm-flow-mybatis-plus-solon-plugin + warm-flow orm为mybatis-plus的solon plugin + https://github.com/dromara/warm-flow + + + + org.noear + solon + + + + org.slf4j + slf4j-api + + + + org.dromara.warm + warm-flow-mybatis-plus-core + + + + com.baomidou + mybatis-plus-solon-plugin + + + + com.baomidou + mybatis-plus-jsqlparser-4.9 + + + + org.dromara.warm + warm-flow-plugin-modes-solon + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-solon-plugin/src/main/java/org/dromara/warm/flow/solon/WarmFlowDaoSolonPlugin.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-solon-plugin/src/main/java/org/dromara/warm/flow/solon/WarmFlowDaoSolonPlugin.java new file mode 100644 index 0000000..e3ad673 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-solon-plugin/src/main/java/org/dromara/warm/flow/solon/WarmFlowDaoSolonPlugin.java @@ -0,0 +1,61 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.solon; + +import com.baomidou.mybatisplus.core.MybatisConfiguration; +import org.apache.ibatis.builder.xml.XMLMapperBuilder; +import org.apache.ibatis.io.Resources; +import org.dromara.warm.flow.solon.config.FlowAutoConfig; +import org.noear.solon.core.AppContext; +import org.noear.solon.core.Plugin; + +import java.util.Arrays; +import java.util.List; + +/** + * Warm-Flow工作流插件 + * + * @author warm + */ +public class WarmFlowDaoSolonPlugin implements Plugin { + + @Override + public void start(AppContext context) { + context.beanMake(FlowAutoConfig.class); + + context.onEvent(MybatisConfiguration.class, e -> { + List mapperList = Arrays.asList( + "warm/flow/FlowDefinitionMapper.xml", + "warm/flow/FlowHisTaskMapper.xml", + "warm/flow/FlowInstanceMapper.xml", + "warm/flow/FlowNodeMapper.xml", + "warm/flow/FlowFormMapper.xml", + "warm/flow/FlowSkipMapper.xml", + "warm/flow/FlowTaskMapper.xml", + "warm/flow/FlowUserMapper.xml"); + + try { + for (String mapper : mapperList) { + XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(Resources.getResourceAsStream(mapper), + e, getClass().getResource("/") + mapper, e.getSqlFragments()); + xmlMapperBuilder.parse(); + } + } catch (Exception ex) { + throw new RuntimeException(ex); + } + }); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-solon-plugin/src/main/java/org/dromara/warm/flow/solon/config/FlowAutoConfig.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-solon-plugin/src/main/java/org/dromara/warm/flow/solon/config/FlowAutoConfig.java new file mode 100644 index 0000000..7832524 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-solon-plugin/src/main/java/org/dromara/warm/flow/solon/config/FlowAutoConfig.java @@ -0,0 +1,39 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.solon.config; + +import org.dromara.warm.flow.core.FlowEngine; +import org.dromara.warm.flow.core.config.WarmFlow; +import org.dromara.warm.flow.core.utils.IdUtils; +import org.dromara.warm.flow.orm.keygen.MybatisPlusIdGen; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Configuration; + +/** + * 工作流bean注册配置 + * + * @author warm + * @since 2023/6/5 23:01 + */ +@Configuration +public class FlowAutoConfig { + @Bean + public WarmFlow after() { + // 设置Mybatis-Plus默认主键生成器 + IdUtils.setInstanceNative(new MybatisPlusIdGen()); + return FlowEngine.getFlowConfig(); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-solon-plugin/src/main/resources/META-INF/solon/org.dromara.warm.flow.solon.properties b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-solon-plugin/src/main/resources/META-INF/solon/org.dromara.warm.flow.solon.properties new file mode 100644 index 0000000..9eb3b92 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis-plus/warm-flow-mybatis-plus-solon-plugin/src/main/resources/META-INF/solon/org.dromara.warm.flow.solon.properties @@ -0,0 +1 @@ +solon.plugin=org.dromara.warm.flow.solon.WarmFlowDaoSolonPlugin diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/pom.xml b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/pom.xml new file mode 100644 index 0000000..5c1128a --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/pom.xml @@ -0,0 +1,24 @@ + + + 4.0.0 + + org.dromara.warm + warm-flow-orm + 1.8.4 + + + warm-flow-mybatis + warm-flow-mybatis + pom + https://github.com/dromara/warm-flow + + + warm-flow-mybatis-core + warm-flow-mybatis-sb-starter + warm-flow-mybatis-sb3-starter + warm-flow-mybatis-solon-plugin + + + diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/pom.xml b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/pom.xml new file mode 100644 index 0000000..fa1e7f9 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/pom.xml @@ -0,0 +1,34 @@ + + + 4.0.0 + + org.dromara.warm + warm-flow-mybatis + 1.8.4 + + + warm-flow-mybatis-core + warm-flow-mybatis-core + https://github.com/dromara/warm-flow + + + + org.dromara.warm + warm-flow-core + + + + org.mybatis + mybatis + + + org.projectlombok + lombok + provided + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowDefinitionDaoImpl.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowDefinitionDaoImpl.java new file mode 100644 index 0000000..70cdbd3 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowDefinitionDaoImpl.java @@ -0,0 +1,54 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.dao; + +import org.dromara.warm.flow.core.invoker.FrameInvoker; +import org.dromara.warm.flow.core.orm.dao.FlowDefinitionDao; +import org.dromara.warm.flow.orm.entity.FlowDefinition; +import org.dromara.warm.flow.orm.mapper.FlowDefinitionMapper; +import org.dromara.warm.flow.orm.utils.TenantDeleteUtil; + +import java.util.List; + +/** + * 流程定义Mapper接口 + * + * @author warm + * @since 2023-03-29 + */ +public class FlowDefinitionDaoImpl extends WarmDaoImpl implements FlowDefinitionDao { + + @Override + public FlowDefinitionMapper getMapper() { + return FrameInvoker.getBean(FlowDefinitionMapper.class); + } + + @Override + public FlowDefinition newEntity() { + return new FlowDefinition(); + } + + @Override + public List queryByCodeList(List flowCodeList) { + return getMapper().queryByCodeList(flowCodeList, TenantDeleteUtil.getEntity(newEntity())); + } + + @Override + public void updatePublishStatus(List ids, Integer publishStatus) { + getMapper().updatePublishStatus(ids, publishStatus, TenantDeleteUtil.getEntity(newEntity())); + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowFormDaoImpl.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowFormDaoImpl.java new file mode 100644 index 0000000..2b989fc --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowFormDaoImpl.java @@ -0,0 +1,47 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.dao; + +import org.dromara.warm.flow.core.invoker.FrameInvoker; +import org.dromara.warm.flow.core.orm.dao.FlowFormDao; +import org.dromara.warm.flow.orm.entity.FlowForm; +import org.dromara.warm.flow.orm.mapper.FlowFormMapper; +import org.dromara.warm.flow.orm.utils.TenantDeleteUtil; + +import java.util.List; + +/** + * @author vanlin + * @className FlowFormDaoImpl + * @description + * @since 2024/8/19 14:29 + */ +public class FlowFormDaoImpl extends WarmDaoImpl implements FlowFormDao { + @Override + public FlowFormMapper getMapper() { + return FrameInvoker.getBean(FlowFormMapper.class); + } + + @Override + public FlowForm newEntity() { + return new FlowForm(); + } + + @Override + public List queryByCodeList(List formCodeList) { + return getMapper().queryByCodeList(formCodeList, TenantDeleteUtil.getEntity(newEntity())); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowHisTaskDaoImpl.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowHisTaskDaoImpl.java new file mode 100644 index 0000000..5d1f3c7 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowHisTaskDaoImpl.java @@ -0,0 +1,70 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.dao; + +import org.dromara.warm.flow.core.FlowEngine; +import org.dromara.warm.flow.core.invoker.FrameInvoker; +import org.dromara.warm.flow.core.orm.dao.FlowHisTaskDao; +import org.dromara.warm.flow.core.utils.StringUtils; +import org.dromara.warm.flow.orm.entity.FlowHisTask; +import org.dromara.warm.flow.orm.mapper.FlowHisTaskMapper; +import org.dromara.warm.flow.orm.utils.TenantDeleteUtil; + +import java.util.List; + +/** + * 历史任务记录Mapper接口 + * + * @author warm + * @since 2023-03-29 + */ +public class FlowHisTaskDaoImpl extends WarmDaoImpl implements FlowHisTaskDao { + + @Override + public FlowHisTaskMapper getMapper() { + return FrameInvoker.getBean(FlowHisTaskMapper.class); + } + + @Override + public FlowHisTask newEntity() { + return new FlowHisTask(); + } + + @Override + public List getNoReject(Long instanceId) { + return getMapper().getNoReject(instanceId, TenantDeleteUtil.getEntity(newEntity())); + } + + @Override + public List getByInsAndNodeCodes(Long instanceId, List nodeCodes) { + return getMapper().getByInsAndNodeCodes(instanceId, nodeCodes, TenantDeleteUtil.getEntity(newEntity())); + } + + @Override + public int deleteByInsIds(List instanceIds) { + FlowHisTask entity = TenantDeleteUtil.getEntity(newEntity()); + if (StringUtils.isNotEmpty(entity.getDelFlag())) { + return getMapper().updateByInsIdsLogic(instanceIds, entity, FlowEngine.getFlowConfig().getLogicDeleteValue(), entity.getDelFlag()); + } + return getMapper().deleteByInsIds(instanceIds, entity); + } + + @Override + public List listByTaskIdAndCooperateTypes(Long taskId, Integer[] cooperateTypes) { + return getMapper().listByTaskIdAndCooperateTypes(cooperateTypes, TenantDeleteUtil.getEntity(newEntity()).setTaskId(taskId)); + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowInstanceDaoImpl.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowInstanceDaoImpl.java new file mode 100644 index 0000000..4853765 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowInstanceDaoImpl.java @@ -0,0 +1,48 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.dao; + +import org.dromara.warm.flow.core.invoker.FrameInvoker; +import org.dromara.warm.flow.core.orm.dao.FlowInstanceDao; +import org.dromara.warm.flow.orm.entity.FlowInstance; +import org.dromara.warm.flow.orm.mapper.FlowInstanceMapper; +import org.dromara.warm.flow.orm.utils.TenantDeleteUtil; + +import java.util.List; + +/** + * 流程实例Mapper接口 + * + * @author warm + * @since 2023-03-29 + */ +public class FlowInstanceDaoImpl extends WarmDaoImpl implements FlowInstanceDao { + + @Override + public FlowInstanceMapper getMapper() { + return FrameInvoker.getBean(FlowInstanceMapper.class); + } + + @Override + public FlowInstance newEntity() { + return new FlowInstance(); + } + + @Override + public List getByDefIds(List defIds) { + return getMapper().getByDefIds(defIds, TenantDeleteUtil.getEntity(newEntity())); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowNodeDaoImpl.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowNodeDaoImpl.java new file mode 100644 index 0000000..c4a80a9 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowNodeDaoImpl.java @@ -0,0 +1,68 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.dao; + +import org.dromara.warm.flow.core.FlowEngine; +import org.dromara.warm.flow.core.invoker.FrameInvoker; +import org.dromara.warm.flow.core.orm.dao.FlowNodeDao; +import org.dromara.warm.flow.core.utils.StringUtils; +import org.dromara.warm.flow.orm.entity.FlowNode; +import org.dromara.warm.flow.orm.mapper.FlowNodeMapper; +import org.dromara.warm.flow.orm.utils.TenantDeleteUtil; + +import java.io.Serializable; +import java.util.Collection; +import java.util.List; + + +/** + * 流程节点Mapper接口 + * + * @author warm + * @since 2023-03-29 + */ +public class FlowNodeDaoImpl extends WarmDaoImpl implements FlowNodeDao { + + @Override + public FlowNodeMapper getMapper() { + return FrameInvoker.getBean(FlowNodeMapper.class); + } + + @Override + public FlowNode newEntity() { + return new FlowNode(); + } + + @Override + public List getByNodeCodes(List nodeCodes, Long definitionId) { + return getMapper().getByNodeCodes(nodeCodes, definitionId, TenantDeleteUtil.getEntity(newEntity())); + } + + /** + * 批量删除流程节点 + * + * @param defIds 需要删除的数据主键集合 + * @return 结果 + */ + @Override + public int deleteNodeByDefIds(Collection defIds) { + FlowNode entity = TenantDeleteUtil.getEntity(newEntity()); + if (StringUtils.isNotEmpty(entity.getDelFlag())) { + return getMapper().updateNodeByDefIdsLogic(defIds, entity, FlowEngine.getFlowConfig().getLogicDeleteValue(), entity.getDelFlag()); + } + return getMapper().deleteNodeByDefIds(defIds, entity); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowSkipDaoImpl.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowSkipDaoImpl.java new file mode 100644 index 0000000..20bce28 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowSkipDaoImpl.java @@ -0,0 +1,62 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.dao; + +import org.dromara.warm.flow.core.FlowEngine; +import org.dromara.warm.flow.core.invoker.FrameInvoker; +import org.dromara.warm.flow.core.orm.dao.FlowSkipDao; +import org.dromara.warm.flow.core.utils.StringUtils; +import org.dromara.warm.flow.orm.entity.FlowSkip; +import org.dromara.warm.flow.orm.mapper.FlowSkipMapper; +import org.dromara.warm.flow.orm.utils.TenantDeleteUtil; + +import java.io.Serializable; +import java.util.Collection; + +/** + * 节点跳转关联Mapper接口 + * + * @author warm + * @since 2023-03-29 + */ +public class FlowSkipDaoImpl extends WarmDaoImpl implements FlowSkipDao { + + @Override + public FlowSkipMapper getMapper() { + return FrameInvoker.getBean(FlowSkipMapper.class); + } + + @Override + public FlowSkip newEntity() { + return new FlowSkip(); + } + + /** + * 批量删除节点跳转关联 + * + * @param defIds 需要删除的数据主键集合 + * @return 结果 + */ + @Override + public int deleteSkipByDefIds(Collection defIds) { + FlowSkip entity = TenantDeleteUtil.getEntity(newEntity()); + if (StringUtils.isNotEmpty(entity.getDelFlag())) { + return getMapper().updateSkipByDefIdsLogic(defIds, entity, FlowEngine.getFlowConfig().getLogicDeleteValue(), entity.getDelFlag()); + } + return getMapper().deleteSkipByDefIds(defIds, entity); + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowTaskDaoImpl.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowTaskDaoImpl.java new file mode 100644 index 0000000..c266ad9 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowTaskDaoImpl.java @@ -0,0 +1,65 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.dao; + +import org.dromara.warm.flow.core.FlowEngine; +import org.dromara.warm.flow.core.invoker.FrameInvoker; +import org.dromara.warm.flow.core.orm.dao.FlowTaskDao; +import org.dromara.warm.flow.core.utils.StringUtils; +import org.dromara.warm.flow.orm.entity.FlowTask; +import org.dromara.warm.flow.orm.mapper.FlowTaskMapper; +import org.dromara.warm.flow.orm.utils.TenantDeleteUtil; + +import java.util.List; + +/** + * 待办任务Mapper接口 + * + * @author warm + * @since 2023-03-29 + */ +public class FlowTaskDaoImpl extends WarmDaoImpl implements FlowTaskDao { + + @Override + public FlowTaskMapper getMapper() { + return FrameInvoker.getBean(FlowTaskMapper.class); + } + + @Override + public FlowTask newEntity() { + return new FlowTask(); + } + + /** + * 根据instanceIds删除 + * + * @param instanceIds 主键 + * @return 结果 + */ + @Override + public int deleteByInsIds(List instanceIds) { + FlowTask entity = TenantDeleteUtil.getEntity(newEntity()); + if (StringUtils.isNotEmpty(entity.getDelFlag())) { + return getMapper().updateByInsIdsLogic(instanceIds, entity, FlowEngine.getFlowConfig().getLogicDeleteValue(), entity.getDelFlag()); + } + return getMapper().deleteByInsIds(instanceIds, entity); + } + + @Override + public List getByInsIdAndNodeCodes(Long instanceId, List nodeCodes) { + return getMapper().getByInsIdAndNodeCodes(instanceId, nodeCodes, TenantDeleteUtil.getEntity(newEntity())); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowUserDaoImpl.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowUserDaoImpl.java new file mode 100644 index 0000000..7fcd495 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/dao/FlowUserDaoImpl.java @@ -0,0 +1,78 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.dao; + +import org.dromara.warm.flow.core.FlowEngine; +import org.dromara.warm.flow.core.invoker.FrameInvoker; +import org.dromara.warm.flow.core.orm.dao.FlowUserDao; +import org.dromara.warm.flow.core.utils.CollUtil; +import org.dromara.warm.flow.core.utils.StringUtils; +import org.dromara.warm.flow.orm.entity.FlowUser; +import org.dromara.warm.flow.orm.mapper.FlowUserMapper; +import org.dromara.warm.flow.orm.utils.TenantDeleteUtil; + +import java.util.List; + +/** + * 流程用户Mapper接口 + * + * @author warm + * @since 2023-03-29 + */ +public class FlowUserDaoImpl extends WarmDaoImpl implements FlowUserDao { + + @Override + public FlowUserMapper getMapper() { + return FrameInvoker.getBean(FlowUserMapper.class); + } + + @Override + public FlowUser newEntity() { + return new FlowUser(); + } + + @Override + public int deleteByTaskIds(List taskIdList) { + FlowUser entity = TenantDeleteUtil.getEntity(newEntity()); + if (StringUtils.isNotEmpty(entity.getDelFlag())) { + return getMapper().updateByTaskIdsLogic(taskIdList, entity, FlowEngine.getFlowConfig().getLogicDeleteValue(), + entity.getDelFlag()); + } + return getMapper().deleteByTaskIds(taskIdList, entity); + } + + @Override + public List listByAssociatedAndTypes(List associatedList, String[] types) { + String dataSourceType = FlowEngine.dataSourceType(); + if (CollUtil.isNotEmpty(associatedList) && associatedList.size() == 1) { + return getMapper().listByAssociatedAndTypes(types, null + , TenantDeleteUtil.getEntity(newEntity()).setAssociated(associatedList.get(0)), dataSourceType); + } + return getMapper().listByAssociatedAndTypes(types, associatedList + , TenantDeleteUtil.getEntity(newEntity()), dataSourceType); + } + + @Override + public List listByProcessedBys(Long associated, List processedBys, String[] types) { + String dataSourceType = FlowEngine.dataSourceType(); + if (CollUtil.isNotEmpty(processedBys) && processedBys.size() == 1) { + return getMapper().listByProcessedBys(types, null, TenantDeleteUtil + .getEntity(newEntity()).setAssociated(associated).setProcessedBy(processedBys.get(0)), dataSourceType); + } + return getMapper().listByProcessedBys(types, processedBys + , TenantDeleteUtil.getEntity(newEntity()).setAssociated(associated), dataSourceType); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/dao/WarmDaoImpl.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/dao/WarmDaoImpl.java new file mode 100644 index 0000000..078765c --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/dao/WarmDaoImpl.java @@ -0,0 +1,154 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.dao; + +import org.dromara.warm.flow.core.FlowEngine; +import org.dromara.warm.flow.core.entity.RootEntity; +import org.dromara.warm.flow.core.orm.agent.WarmQuery; +import org.dromara.warm.flow.core.orm.dao.WarmDao; +import org.dromara.warm.flow.core.utils.ObjectUtil; +import org.dromara.warm.flow.core.utils.StringUtils; +import org.dromara.warm.flow.core.utils.page.Page; +import org.dromara.warm.flow.orm.mapper.WarmMapper; +import org.dromara.warm.flow.orm.utils.TenantDeleteUtil; + +import java.io.Serializable; +import java.util.Collection; +import java.util.List; + +/** + * BaseMapper接口 + * + * @author warm + * @since 2023-03-17 + */ +public abstract class WarmDaoImpl implements WarmDao { + + public abstract WarmMapper getMapper(); + + /** + * 根据id查询 + * + * @param id 主键 + * @return 实体 + */ + @Override + public T selectById(Serializable id) { + return getMapper().selectById(id, TenantDeleteUtil.getEntity(newEntity())); + } + + /** + * 根据ids查询 + * + * @param ids 主键 + * @return 实体 + */ + @Override + public List selectByIds(Collection ids) { + return getMapper().selectByIds(ids, TenantDeleteUtil.getEntity(newEntity())); + } + + @Override + public Page selectPage(T entity, Page page) { + TenantDeleteUtil.getEntity(entity); + String dataSourceType = FlowEngine.dataSourceType(); + ifNecessaryChangePage(page, dataSourceType); + long total = getMapper().selectCount(entity); + if (total > 0) { + List list = getMapper().selectList(entity, page, page.getOrderBy() + " " + page.getIsAsc(), dataSourceType); + return new Page<>(list, total); + } + return Page.empty(); + } + + @Override + public List selectList(T entity, WarmQuery query) { + TenantDeleteUtil.getEntity(entity); + String dataSourceType = FlowEngine.dataSourceType(); + if (ObjectUtil.isNull(query)) { + return getMapper().selectList(entity, null, null, dataSourceType); + } + return getMapper().selectList(entity, null, query.getOrderBy() + " " + query.getIsAsc(), dataSourceType); + } + + @Override + public long selectCount(T entity) { + TenantDeleteUtil.getEntity(entity); + return getMapper().selectCount(entity); + } + + @Override + public int save(T entity) { + TenantDeleteUtil.getEntity(entity); + return getMapper().insert(entity); + } + + @Override + public int updateById(T entity) { + TenantDeleteUtil.getEntity(entity); + return getMapper().updateById(entity); + } + + @Override + public int delete(T entity) { + TenantDeleteUtil.getEntity(entity); + if (StringUtils.isNotEmpty(entity.getDelFlag())) { + return getMapper().updateLogic(entity, FlowEngine.getFlowConfig().getLogicDeleteValue(), entity.getDelFlag()); + } + return getMapper().delete(entity); + } + + @Override + public int deleteById(Serializable id) { + T entity = TenantDeleteUtil.getEntity(newEntity()); + if (StringUtils.isNotEmpty(entity.getDelFlag())) { + return getMapper().updateByIdLogic(id, entity, FlowEngine.getFlowConfig().getLogicDeleteValue(), entity.getDelFlag()); + } + return getMapper().deleteById(id, entity); + } + + @Override + public int deleteByIds(Collection ids) { + T entity = TenantDeleteUtil.getEntity(newEntity()); + if (StringUtils.isNotEmpty(entity.getDelFlag())) { + return getMapper().updateByIdsLogic(ids, entity, FlowEngine.getFlowConfig().getLogicDeleteValue(), entity.getDelFlag()); + } + return getMapper().deleteByIds(ids, entity); + } + + @Override + public void saveBatch(List list) { + for (T record : list) { + TenantDeleteUtil.getEntity(record); + } + String dataSourceType = FlowEngine.dataSourceType(); + getMapper().saveBatch(list, dataSourceType); + } + + @Override + public void updateBatch(List list) { + for (T record : list) { + updateById(record); + } + } + + private void ifNecessaryChangePage(Page page, String dataSourceType) { + page.setPageNum((page.getPageNum() - 1) * page.getPageSize()); + if ("oracle".equals(dataSourceType)) { + page.setPageSize(page.getPageSize() + page.getPageNum()); + } + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowDefinition.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowDefinition.java new file mode 100644 index 0000000..60201e8 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowDefinition.java @@ -0,0 +1,140 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.entity; + +import lombok.Data; +import lombok.experimental.Accessors; +import org.dromara.warm.flow.core.entity.Definition; +import org.dromara.warm.flow.core.entity.Node; +import org.dromara.warm.flow.core.entity.User; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * 流程定义对象 flow_definition + * + * @author warm + * @since 2023-03-29 + */ +@Data +@Accessors(chain = true) +public class FlowDefinition implements Definition { + + /** + * 主键 + */ + private Long id; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + + /** + * 创建人 + */ + private String createBy; + + /** + * 更新人 + */ + private String updateBy; + + /** + * 租户ID + */ + private String tenantId; + + /** + * 删除标记 + */ + private String delFlag; + + /** + * 流程编码 + */ + private String flowCode; + + /** + * 流程名称 + */ + private String flowName; + + /** + * 设计器模型(CLASSICS经典模型 MIMIC仿钉钉模型) + */ + private String modelValue; + + /** + * 流程类别 + */ + private String category; + + /** + * 流程版本 + */ + private String version; + + /** + * 是否发布(0未开启 1开启) + */ + private Integer isPublish; + + /** + * 审批表单是否自定义(Y是 N否) + */ + private String formCustom; + + /** + * 审批表单路径 + */ + private String formPath; + + /** + * 流程激活状态(0挂起 1激活) + */ + private Integer activityStatus; + + /** + * 监听器类型 + */ + private String listenerType; + + /** + * 监听器路径 + */ + private String listenerPath; + + /** + * 扩展字段,预留给业务系统使用 + */ + private String ext; + + private List nodeList = new ArrayList<>(); + + /** + * 流程权限人 + */ + private List userList = new ArrayList<>(); + +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowForm.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowForm.java new file mode 100644 index 0000000..4a3ed62 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowForm.java @@ -0,0 +1,107 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.entity; + +import lombok.Data; +import lombok.experimental.Accessors; +import org.dromara.warm.flow.core.entity.Form; + +import java.util.Date; + + +/** + * @author vanlin + * @since 2024/8/19 10:30 + */ +@Data +@Accessors(chain = true) +public class FlowForm implements Form { + + /** + * 主键 + */ + private Long id; + + /** + * 表单编码 + */ + private String formCode; + + /** + * 表单名称 + */ + private String formName; + + /** + * 表单版本 + */ + private String version; + + /** + * 是否发布(0未发布 1已发布 9失效) + */ + private Integer isPublish; + + /** + * 表单类型(0内置表单 存 form_content 1外挂表单 存form_path) + */ + private Integer formType; + + /** + * 表单路径 + */ + private String formPath; + + /** + * 表单内容 + */ + private String formContent; + + /** + * 表单扩展,用户自行使用 + */ + private String ext; + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + + /** + * 创建人 + */ + private String createBy; + + /** + * 更新人 + */ + private String updateBy; + + /** + * 租户ID + */ + private String tenantId; + + /** + * 删除标记 + */ + private String delFlag; + +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowHisTask.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowHisTask.java new file mode 100644 index 0000000..96815e4 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowHisTask.java @@ -0,0 +1,165 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.entity; + +import lombok.Data; +import lombok.experimental.Accessors; +import org.dromara.warm.flow.core.entity.HisTask; + +import java.util.Date; +import java.util.List; + +/** + * 历史任务记录对象 flow_his_task + * + * @author warm + * @since 2023-03-29 + */ +@Data +@Accessors(chain = true) +public class FlowHisTask implements HisTask { + + /** + * 主键 + */ + private Long id; + + /** + * 任务开始时间 + */ + private Date createTime; + + /** + * 审批完成时间 + */ + private Date updateTime; + + /** + * 租户ID + */ + private String tenantId; + + /** + * 删除标记 + */ + private String delFlag; + + /** + * 对应flow_definition表的id + */ + private Long definitionId; + + /** + * 流程名称 + */ + private String flowName; + + /** + * 流程实例表id + */ + private Long instanceId; + + /** + * 任务表id + */ + private Long taskId; + + /** + * 协作方式(1审批 2转办 3委派 4会签 5票签 6加签 7减签) + */ + private Integer cooperateType; + + /** + * 业务id + */ + private String businessId; + + /** + * 开始节点编码 + */ + private String nodeCode; + + /** + * 开始节点名称 + */ + private String nodeName; + + /** + * 开始节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关) + */ + private Integer nodeType; + + /** + * 目标节点编码 + */ + private String targetNodeCode; + + /** + * 结束节点名称 + */ + private String targetNodeName; + + /** + * 审批者 + */ + private String approver; + + /** + * 协作人(只有转办、会签、票签、委派) + */ + private String collaborator; + + /** + * 权限标识 permissionFlag的list形式 + */ + private List permissionList; + + /** + * 跳转类型(PASS通过 REJECT退回 NONE无动作) + */ + private String skipType; + + /** + * 流程状态(0待提交 1审批中 2审批通过 4终止 5作废 6撤销 8已完成 9已退回 10失效 11拿回) + */ + private String flowStatus; + + /** + * 审批意见 + */ + private String message; + + /** + * 流程变量 + */ + private String variable; + + /** + * 业务详情 存业务类的json + */ + private String ext; + + /** + * 审批表单是否自定义(Y=是 N=否) + */ + private String formCustom; + + /** + * 审批表单是否自定义(Y=是 N=否) + */ + private String formPath; + +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowInstance.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowInstance.java new file mode 100644 index 0000000..83fd861 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowInstance.java @@ -0,0 +1,138 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.entity; + +import lombok.Data; +import lombok.experimental.Accessors; +import org.dromara.warm.flow.core.entity.Instance; + +import java.util.Date; + + +/** + * 流程实例对象 flow_instance + * + * @author warm + * @since 2023-03-29 + */ +@Data +@Accessors(chain = true) +public class FlowInstance implements Instance { + + /** + * 主键 + */ + private Long id; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + + /** + * 创建人 + */ + private String createBy; + + /** + * 更新人 + */ + private String updateBy; + + /** + * 租户ID + */ + private String tenantId; + + /** + * 删除标记 + */ + private String delFlag; + + /** + * 对应flow_definition表的id + */ + private Long definitionId; + + /** + * 流程名称 + */ + private String flowName; + + /** + * 业务ID + */ + private String businessId; + + /** + * @see org.dromara.warm.flow.core.enums.NodeType + * 节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关) + */ + private Integer nodeType; + + /** + * 流程节点编码 每个流程的nodeCode是唯一的,即definitionId+nodeCode唯一,在数据库层面做了控制 + */ + private String nodeCode; + + /** + * 流程节点名称 + */ + private String nodeName; + + /** + * 流程变量 + */ + private String variable; + + /** + * @see org.dromara.warm.flow.core.enums.FlowStatus + * 流程状态(0待提交 1审批中 2审批通过 4终止 5作废 6撤销 8已完成 9已退回 10失效 11拿回) + */ + private String flowStatus; + + /** + * @see org.dromara.warm.flow.core.enums.ActivityStatus + * 流程激活状态(0挂起 1激活) + */ + private Integer activityStatus; + + /** + * 审批表单是否自定义(Y=是 N=否) + */ + private String formCustom; + + /** + * 审批表单是否自定义(Y=是 N=否) + */ + private String formPath; + + /** + * 流程定义json + */ + private String defJson; + + /** + * 扩展字段,预留给业务系统使用 + */ + private String ext; + +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowNode.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowNode.java new file mode 100644 index 0000000..df72eea --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowNode.java @@ -0,0 +1,137 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.entity; + +import lombok.Data; +import lombok.experimental.Accessors; +import org.dromara.warm.flow.core.entity.Node; +import org.dromara.warm.flow.core.entity.Skip; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * 流程节点对象 flow_node + * + * @author warm + * @since 2023-03-29 + */ +@Data +@Accessors(chain = true) +public class FlowNode implements Node { + + /** + * 跳转条件 + */ + List skipList = new ArrayList<>(); + /** + * 主键 + */ + private Long id; + /** + * 创建时间 + */ + private Date createTime; + /** + * 更新时间 + */ + private Date updateTime; + + /** + * 创建人 + */ + private String createBy; + + /** + * 更新人 + */ + private String updateBy; + + /** + * 租户ID + */ + private String tenantId; + + /** + * 删除标记 + */ + private String delFlag; + + /** + * 节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关) + */ + private Integer nodeType; + /** + * 流程id + */ + private Long definitionId; + /** + * 流程节点编码 每个流程的nodeCode是唯一的,即definitionId+nodeCode唯一,在数据库层面做了控制 + */ + private String nodeCode; + /** + * 流程节点名称 + */ + private String nodeName; + /** + * 权限标识(权限类型:权限标识,可以多个,用@@隔开) + */ + private String permissionFlag; + /** + * 流程签署比例值 + */ + private String nodeRatio; + /** + * 流程节点坐标 + */ + private String coordinate; + /** + * 版本 + * + * @deprecated 下个版本废弃 + */ + @Deprecated + private String version; + /** + * 任意结点跳转 + */ + private String anyNodeSkip; + /** + * 监听器类型 + */ + private String listenerType; + /** + * 监听器路径 + */ + private String listenerPath; + /** + * 审批表单是否自定义(Y=是 N=否) + */ + private String formCustom; + + /** + * 审批表单路径 + */ + private String formPath; + + /** + * 节点扩展属性 + */ + private String ext; + +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowSkip.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowSkip.java new file mode 100644 index 0000000..021a00c --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowSkip.java @@ -0,0 +1,120 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.entity; + +import lombok.Data; +import lombok.experimental.Accessors; +import org.dromara.warm.flow.core.entity.Skip; + +import java.util.Date; + + +/** + * 节点跳转关联对象 flow_skip + * + * @author warm + * @since 2023-03-29 + */ +@Data +@Accessors(chain = true) +public class FlowSkip implements Skip { + + /** + * 主键 + */ + private Long id; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + + /** + * 创建人 + */ + private String createBy; + + /** + * 更新人 + */ + private String updateBy; + + /** + * 租户ID + */ + private String tenantId; + + /** + * 删除标记 + */ + private String delFlag; + + /** + * 流程id + */ + private Long definitionId; + + /** + * 节点id + */ + private Long nodeId; + + /** + * 当前流程节点的编码 + */ + private String nowNodeCode; + + /** + * 当前节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关) + */ + private Integer nowNodeType; + + /** + * 下一个流程节点的编码 + */ + private String nextNodeCode; + + /** + * 下一个节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关) + */ + private Integer nextNodeType; + + /** + * 跳转名称 + */ + private String skipName; + + /** + * 跳转类型(PASS审批通过 REJECT退回) + */ + private String skipType; + + /** + * 跳转条件 + */ + private String skipCondition; + + /** + * 流程跳转坐标 + */ + private String coordinate; + +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowTask.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowTask.java new file mode 100644 index 0000000..420fda3 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowTask.java @@ -0,0 +1,132 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.entity; + +import lombok.Data; +import lombok.experimental.Accessors; +import org.dromara.warm.flow.core.entity.Task; +import org.dromara.warm.flow.core.entity.User; + +import java.util.Date; +import java.util.List; + +/** + * 待办任务记录对象 flow_task + * + * @author warm + * @since 2023-03-29 + */ +@Data +@Accessors(chain = true) +public class FlowTask implements Task { + + /** + * 主键 + */ + private Long id; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + + /** + * 创建人 + */ + private String createBy; + + /** + * 更新人 + */ + private String updateBy; + + /** + * 租户ID + */ + private String tenantId; + + /** + * 删除标记 + */ + private String delFlag; + + /** + * 对应flow_definition表的id + */ + private Long definitionId; + + /** + * 流程实例表id + */ + private Long instanceId; + + /** + * 流程名称 + */ + private String flowName; + + /** + * 业务id + */ + private String businessId; + + /** + * 节点编码 + */ + private String nodeCode; + + /** + * 节点名称 + */ + private String nodeName; + + /** + * 节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关) + */ + private Integer nodeType; + + /** + * 流程状态(0待提交 1审批中 2审批通过 4终止 5作废 6撤销 8已完成 9已退回 10失效 11拿回) + * @see org.dromara.warm.flow.core.enums.FlowStatus + */ + private String flowStatus; + + /** + * 权限标识 permissionFlag的list形式 + */ + private List permissionList; + + /** + * 流程用户列表 + */ + private List userList; + + /** + * 审批表单是否自定义(Y=是 N=否) + */ + private String formCustom; + + /** + * 审批表单 + */ + private String formPath; + +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowUser.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowUser.java new file mode 100644 index 0000000..3001655 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/entity/FlowUser.java @@ -0,0 +1,85 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.entity; + +import lombok.Data; +import lombok.experimental.Accessors; +import org.dromara.warm.flow.core.entity.User; + +import java.util.Date; + + +/** + * 流程用户对象 flow_user + * + * @author xiarg + * @since 2024/5/10 10:58 + */ +@Data +@Accessors(chain = true) +public class FlowUser implements User { + + /** + * 主键 + */ + private Long id; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + + /** + * 创建人:比如作为委托的人保存 + */ + private String createBy; + + /** + * 更新人 + */ + private String updateBy; + + /** + * 租户ID + */ + private String tenantId; + + /** + * 删除标记 + */ + private String delFlag; + + /** + * 人员类型(1待办任务的审批人权限 2待办任务的转办人权限 3待办任务的委托人权限) + */ + private String type; + + /** + * 权限人 + */ + private String processedBy; + + /** + * 任务表ID + */ + private Long associated; + +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowDefinitionMapper.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowDefinitionMapper.java new file mode 100644 index 0000000..cc40c49 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowDefinitionMapper.java @@ -0,0 +1,39 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.mapper; + +import org.apache.ibatis.annotations.Param; +import org.dromara.warm.flow.orm.entity.FlowDefinition; + +import java.util.List; + +/** + * 流程定义Mapper接口 + * + * @author warm + * @since 2023-03-29 + */ +public interface FlowDefinitionMapper extends WarmMapper { + + + List queryByCodeList(@Param("flowCodeList") List flowCodeList, @Param("entity") FlowDefinition entity); + + void closeFlowByCodeList(@Param("flowCodeList") List flowCodeList, @Param("entity") FlowDefinition entity); + + + void updatePublishStatus(@Param("ids") List ids, @Param("publishStatus") Integer publishStatus + , @Param("entity") FlowDefinition flowDefinition); +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowFormMapper.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowFormMapper.java new file mode 100644 index 0000000..5ccd9cf --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowFormMapper.java @@ -0,0 +1,31 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.mapper; + +import org.apache.ibatis.annotations.Param; +import org.dromara.warm.flow.orm.entity.FlowForm; + +import java.util.List; + +/** + * @author vanlin + * @className FlowFormMapper + * @description + * @since 2024/8/19 14:30 + */ +public interface FlowFormMapper extends WarmMapper { + List queryByCodeList(@Param("formCodeList") List formCodeList, @Param("entity") FlowForm entity); +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowHisTaskMapper.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowHisTaskMapper.java new file mode 100644 index 0000000..f501fe6 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowHisTaskMapper.java @@ -0,0 +1,76 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.mapper; + +import org.apache.ibatis.annotations.Param; +import org.dromara.warm.flow.orm.entity.FlowHisTask; + +import java.io.Serializable; +import java.util.Collection; +import java.util.List; + +/** + * 历史任务记录Mapper接口 + * + * @author warm + * @since 2023-03-29 + */ +public interface FlowHisTaskMapper extends WarmMapper { + + /** + * 根据instanceId获取未退回的历史记录 + * + * @param instanceId + * @param entity + * @return + */ + List getNoReject(@Param("instanceId") Long instanceId + , @Param("entity") FlowHisTask entity); + + /** + * 根据instanceId和流程编码获取未退回的历史记录 + * + * @param instanceId + * @param nodeCodes + * @return + */ + List getByInsAndNodeCodes(@Param("instanceId") Long instanceId + , @Param("nodeCodes") List nodeCodes + , @Param("entity") FlowHisTask entity); + + + /** + * 根据instanceIds删除 + * + * @param instanceIds 主键 + * @param entity + * @return 结果 + */ + int deleteByInsIds(@Param("instanceIds") List instanceIds, @Param("entity") FlowHisTask entity); + + /** + * 逻辑删除 + * + * @param instanceIds 需要删除的数据主键集合 + * @return 结果 + */ + int updateByInsIdsLogic(@Param("instanceIds") Collection instanceIds + , @Param("entity") FlowHisTask entity, @Param("logicDeleteValue") String logicDeleteValue + , @Param("logicNotDeleteValue") String logicNotDeleteValue); + + List listByTaskIdAndCooperateTypes(@Param("cooperateTypes") Integer[] cooperateTypes + , @Param("entity") FlowHisTask entity); +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowInstanceMapper.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowInstanceMapper.java new file mode 100644 index 0000000..6e8c285 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowInstanceMapper.java @@ -0,0 +1,32 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.mapper; + +import org.apache.ibatis.annotations.Param; +import org.dromara.warm.flow.orm.entity.FlowInstance; + +import java.util.List; + +/** + * 流程实例Mapper接口 + * + * @author warm + * @since 2023-03-29 + */ +public interface FlowInstanceMapper extends WarmMapper { + + List getByDefIds(@Param("defIds") List defIds, @Param("entity") FlowInstance flowInstance); +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowNodeMapper.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowNodeMapper.java new file mode 100644 index 0000000..7dc51de --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowNodeMapper.java @@ -0,0 +1,54 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.mapper; + +import org.apache.ibatis.annotations.Param; +import org.dromara.warm.flow.orm.entity.FlowNode; + +import java.io.Serializable; +import java.util.Collection; +import java.util.List; + + +/** + * 流程节点Mapper接口 + * + * @author warm + * @since 2023-03-29 + */ +public interface FlowNodeMapper extends WarmMapper { + + List getByNodeCodes(@Param("nodeCodes") List nodeCodes, @Param("definitionId") Long definitionId + , @Param("entity") FlowNode entity); + + /** + * 批量删除流程节点 + * + * @param defIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteNodeByDefIds(@Param("defIds") Collection defIds, @Param("entity") FlowNode entity); + + /** + * 逻辑删除 + * + * @param defIds 需要删除的数据主键集合 + * @return 结果 + */ + int updateNodeByDefIdsLogic(@Param("defIds") Collection defIds, @Param("entity") FlowNode entity + , @Param("logicDeleteValue") String logicDeleteValue + , @Param("logicNotDeleteValue") String logicNotDeleteValue); +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowSkipMapper.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowSkipMapper.java new file mode 100644 index 0000000..5123347 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowSkipMapper.java @@ -0,0 +1,49 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.mapper; + +import org.apache.ibatis.annotations.Param; +import org.dromara.warm.flow.orm.entity.FlowSkip; + +import java.io.Serializable; +import java.util.Collection; + +/** + * 节点跳转关联Mapper接口 + * + * @author warm + * @since 2023-03-29 + */ +public interface FlowSkipMapper extends WarmMapper { + + /** + * 批量删除节点跳转关联 + * + * @param defIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteSkipByDefIds(@Param("defIds") Collection defIds, @Param("entity") FlowSkip entity); + + /** + * 逻辑删除 + * + * @param defIds 需要删除的数据主键集合 + * @return 结果 + */ + int updateSkipByDefIdsLogic(@Param("defIds") Collection defIds, @Param("entity") FlowSkip entity + , @Param("logicDeleteValue") String logicDeleteValue + , @Param("logicNotDeleteValue") String logicNotDeleteValue); +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowTaskMapper.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowTaskMapper.java new file mode 100644 index 0000000..df7df73 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowTaskMapper.java @@ -0,0 +1,64 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.mapper; + +import org.apache.ibatis.annotations.Param; +import org.dromara.warm.flow.orm.entity.FlowTask; + +import java.io.Serializable; +import java.util.Collection; +import java.util.List; + +/** + * 待办任务Mapper接口 + * + * @author warm + * @since 2023-03-29 + */ +public interface FlowTaskMapper extends WarmMapper { + + /** + * 根据instanceIds删除 + * + * @param instanceIds 主键 + * @return 结果 + */ + int deleteByInsIds(@Param("instanceIds") Collection instanceIds + , @Param("entity") FlowTask entity); + + /** + * 逻辑删除 + * + * @param instanceIds 需要删除的数据主键集合 + * @return 结果 + */ + int updateByInsIdsLogic(@Param("instanceIds") Collection instanceIds + , @Param("entity") FlowTask entity + , @Param("logicDeleteValue") String logicDeleteValue + , @Param("logicNotDeleteValue") String logicNotDeleteValue); + + /** + * 根据实例id和节点code查询 + * + * @param instanceId 实例id + * @param nodeCodes 节点code + * @param entity 实体 + * @return 结果 + */ + List getByInsIdAndNodeCodes(@Param("instanceId") Long instanceId, + @Param("nodeCodes") List nodeCodes, + @Param("entity") FlowTask entity); +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowUserMapper.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowUserMapper.java new file mode 100644 index 0000000..6fd081b --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/mapper/FlowUserMapper.java @@ -0,0 +1,65 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.mapper; + +import org.apache.ibatis.annotations.Param; +import org.dromara.warm.flow.orm.entity.FlowUser; + +import java.io.Serializable; +import java.util.Collection; +import java.util.List; + +/** + * 流程用户Mapper接口 + * + * @author xiarg + * @since 2024/5/10 11:16 + */ +public interface FlowUserMapper extends WarmMapper { + + /** + * 根据 taskIds 删除 + * + * @param taskIds 待办任务id集合 + * @return int + * @author xiarg + * @since 2024/5/11 11:24 + */ + int deleteByTaskIds(@Param("taskIds") Collection taskIds, + @Param("entity") FlowUser entity); + + /** + * 根据 taskIds 逻辑删除 + * + * @param taskIds 待办任务id集合 + * @return int + * @author xiarg + * @since 2024/5/11 11:24 + */ + int updateByTaskIdsLogic(@Param("taskIds") Collection taskIds, + @Param("entity") FlowUser entity, + @Param("logicDeleteValue") String logicDeleteValue, + @Param("logicNotDeleteValue") String logicNotDeleteValue); + + List listByAssociatedAndTypes(@Param("types") String[] types + , @Param("associateds") List associateds + , @Param("entity") FlowUser entity, @Param("dataSourceType") String dataSourceType); + + List listByProcessedBys(@Param("types") String[] types + , @Param("processedBys") List processedBys + , @Param("entity") FlowUser entity, @Param("dataSourceType") String dataSourceType); + +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/mapper/WarmMapper.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/mapper/WarmMapper.java new file mode 100644 index 0000000..92c62e6 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/mapper/WarmMapper.java @@ -0,0 +1,147 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.mapper; + +import org.apache.ibatis.annotations.Param; +import org.dromara.warm.flow.core.utils.page.Page; + +import java.io.Serializable; +import java.util.Collection; +import java.util.List; + +/** + * BaseMapper接口 + * + * @author warm + * @since 2023-03-17 + */ +public interface WarmMapper { + + /** + * 根据id查询 + * + * @param id 主键 + * @param entity 查询实体列表 + * @return 实体 + */ + T selectById(@Param("id") Serializable id, @Param("entity") T entity); + + /** + * 根据ids查询 + * + * @param ids 主键 + * @param entity 查询实体列表 + * @return 实体 + */ + List selectByIds(@Param("ids") Collection ids, @Param("entity") T entity); + + /** + * 分页查询 + * + * @param entity 实体列表 + * @return 集合 + */ + List selectList(@Param("entity") T entity, @Param("page") Page page, @Param("order") String order, + @Param("dataSourceType") String dataSourceType); + + /** + * 查询数量 + * + * @param entity 实体列表 + * @return 集合 + */ + long selectCount(@Param("entity") T entity); + + /** + * 新增 + * + * @param entity 实体 + * @return 结果 + */ + int insert(T entity); + + /** + * 根据id修改 + * + * @param entity 实体 + * @return 结果 + */ + int updateById(T entity); + + /** + * 根据entity删除 + * + * @param entity 实体 + * @return 结果 + */ + int delete(T entity); + + /** + * 逻辑删除 + * + * @param entity 实体 + * @return 结果 + */ + int updateLogic(@Param("entity") T entity, @Param("logicDeleteValue") String logicDeleteValue + , @Param("logicNotDeleteValue") String logicNotDeleteValue); + + /** + * 根据id删除 + * + * @param id 主键 + * @param entity 查询实体列表 + * @return 结果 + */ + int deleteById(@Param("id") Serializable id, @Param("entity") T entity); + + /** + * 逻辑删除 + * + * @param id 主键 + * @return 结果 + */ + int updateByIdLogic(@Param("id") Serializable id, @Param("entity") T entity, @Param("logicDeleteValue") String logicDeleteValue + , @Param("logicNotDeleteValue") String logicNotDeleteValue); + + /** + * 根据ids批量删除 + * + * @param ids 需要删除的数据主键集合 + * @param entity 查询实体列表 + * @return 结果 + */ + int deleteByIds(@Param("ids") Collection ids, @Param("entity") T entity); + + /** + * 逻辑删除 + * + * @param ids 需要删除的数据主键集合 + * @return 结果 + */ + int updateByIdsLogic(@Param("ids") Collection ids, @Param("entity") T entity + , @Param("logicDeleteValue") String logicDeleteValue + , @Param("logicNotDeleteValue") String logicNotDeleteValue); + + /** + * 批量插入 + * + * @param list 需要插入的集合元素 + * @param dataSourceType 数据源类型 + * @return 插入的数量 + */ + int saveBatch(@Param("list") Collection list, @Param("dataSourceType") String dataSourceType); + +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/utils/CommonUtil.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/utils/CommonUtil.java new file mode 100644 index 0000000..0191d5c --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/utils/CommonUtil.java @@ -0,0 +1,62 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.utils; + +import org.apache.ibatis.session.Configuration; +import org.dromara.warm.flow.core.config.WarmFlow; +import org.dromara.warm.flow.core.utils.StringUtils; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.DatabaseMetaData; + +public class CommonUtil { + + private CommonUtil() { + } + + public static void setDataSourceType(WarmFlow flowConfig, Configuration configuration) { + String dataSourceType = flowConfig.getDataSourceType(); + // 未配置时尝试获取可用数据库类型 + if (StringUtils.isEmpty(dataSourceType)) { + DataSource dataSource = configuration.getEnvironment().getDataSource(); + DatabaseMetaData metaData; + Connection connection = null; + try { + connection = dataSource.getConnection(); + metaData = connection.getMetaData(); + dataSourceType = metaData.getDatabaseProductName().toLowerCase(); + } catch (Exception e) { + // 不能因为一个字段的取值, 影响到框架自身运行环境 + // throw new RuntimeException(e); + } finally { + try { + connection.close(); + } catch (Exception e) { + // 不能因为一个字段的取值, 影响到框架自身运行环境 + // throw new RuntimeException(e); + } + } + } + + + // 兜底数据库类型 + if (StringUtils.isEmpty(dataSourceType)) { + dataSourceType = "mysql"; + } + flowConfig.setDataSourceType(dataSourceType); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/utils/TenantDeleteUtil.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/utils/TenantDeleteUtil.java new file mode 100644 index 0000000..2083aab --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/java/org/dromara/warm/flow/orm/utils/TenantDeleteUtil.java @@ -0,0 +1,51 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.orm.utils; + +import org.dromara.warm.flow.core.FlowEngine; +import org.dromara.warm.flow.core.config.WarmFlow; +import org.dromara.warm.flow.core.entity.RootEntity; +import org.dromara.warm.flow.core.handler.TenantHandler; +import org.dromara.warm.flow.core.utils.ObjectUtil; + +/** + * mybatis-plus 租户和逻辑删除工具类 + * + * @author warm + */ +public class TenantDeleteUtil { + + private TenantDeleteUtil() { + } + + /** + * 获取mybatis-plus查询条件, 根据是否租户或者逻辑删除 + * + * @param + */ + public static T getEntity(T entity) { + WarmFlow flowConfig = FlowEngine.getFlowConfig(); + if (flowConfig.isLogicDelete()) { + entity.setDelFlag(flowConfig.getLogicNotDeleteValue()); + } + + TenantHandler tenantHandler = FlowEngine.tenantHandler(); + if (ObjectUtil.isNotNull(tenantHandler)) { + entity.setTenantId(tenantHandler.getTenantId()); + } + return entity; + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/resources/warm/flow/FlowDefinitionMapper.xml b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/resources/warm/flow/FlowDefinitionMapper.xml new file mode 100644 index 0000000..56839bf --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/resources/warm/flow/FlowDefinitionMapper.xml @@ -0,0 +1,427 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + select t.id, + t.flow_code, + t.flow_name, + t.model_value, + t.category, + t.version, + t.is_publish, + t.form_custom, + t.form_path, + t.activity_status, + t.listener_type, + t.listener_path, + t.ext, + t.create_time, + t.update_time, + t.create_by, + t.update_by, + t.del_flag, + t.tenant_id + from flow_definition t + + + + + LIMIT #{page.pageSize} offset #{page.pageNum} + + + + + order by ${order} + + + + + and t.id = #{entity.id} + and t.flow_code = #{entity.flowCode} + and t.flow_name = #{entity.flowName} + and t.model_value = #{entity.modelValue} + and t.category = #{entity.category} + and t.version = #{entity.version} + and t.is_publish = #{entity.isPublish} + and t.form_custom = #{entity.formCustom} + and t.form_path = #{entity.formPath} + and t.activity_status = #{entity.activityStatus} + and t.listener_type = #{entity.listenerType} + and t.listener_path = #{entity.listenerPath} + and t.ext = #{entity.ext} + and t.create_time = #{entity.createTime} + and t.update_time = #{entity.updateTime} + and t.del_flag = #{entity.delFlag} + and t.tenant_id = #{entity.tenantId} + and t.update_by = #{entity.updateBy} + and t.create_by = #{entity.createBy} + + + + + insert into flow_definition( + id, + flow_code, + flow_name, + model_value, + category, + version, + is_publish, + form_custom, + form_path, + activity_status, + listener_type, + listener_path, + ext, + create_time, + update_time, + del_flag, + tenant_id, + create_by, + update_by + ) + values + + ( + #{item.id}, + #{item.flowCode}, + #{item.flowName}, + #{item.modelValue}, + #{item.category}, + #{item.version}, + #{item.isPublish}, + #{item.formCustom}, + #{item.formPath}, + #{item.activityStatus}, + #{item.listenerType}, + #{item.listenerPath}, + #{item.ext}, + #{item.createTime}, + #{item.updateTime}, + #{item.delFlag}, + #{item.tenantId}, + #{item.createBy}, + #{item.updateBy} + ) + + + + + insert into flow_definition( + id, + flow_code, + flow_name, + model_value, + category, + version, + is_publish, + form_custom, + form_path, + activity_status, + listener_type, + listener_path, + ext, + create_time, + update_time, + del_flag, + tenant_id, + create_by, + update_by + ) + + select + #{item.id}, + #{item.flowCode}, + #{item.flowName}, + #{item.modelValue}, + #{item.category}, + #{item.version}, + #{item.isPublish}, + #{item.formCustom}, + #{item.formPath}, + #{item.activityStatus}, + #{item.listenerType}, + #{item.listenerPath}, + #{item.ext}, + #{item.createTime}, + #{item.updateTime}, + #{item.delFlag}, + #{item.tenantId}, + #{item.createBy}, + #{item.updateBy} + from dual + + + + + + + + + + + + + + + + + + + + + insert into flow_definition + + id, + flow_code, + flow_name, + model_value, + category, + version, + is_publish, + form_custom, + form_path, + activity_status, + listener_type, + listener_path, + ext, + create_time, + update_time, + del_flag, + tenant_id, + update_by, + create_by, + + + #{id}, + #{flowCode}, + #{flowName}, + #{modelValue}, + #{category}, + #{version}, + #{isPublish}, + #{formCustom}, + #{formPath}, + #{activityStatus}, + #{listenerType}, + #{listenerPath}, + #{ext}, + #{createTime}, + #{updateTime}, + #{delFlag}, + #{tenantId}, + #{updateBy}, + #{createBy}, + + + + + update flow_definition + + flow_code = #{flowCode}, + flow_name = #{flowName}, + model_value = #{modelValue}, + category = #{category}, + version = #{version}, + is_publish = #{isPublish}, + form_custom = #{formCustom}, + form_path = #{formPath}, + activity_status = #{activityStatus}, + listener_type = #{listenerType}, + listener_path = #{listenerPath}, + ext = #{ext}, + create_time = #{createTime}, + update_time = #{updateTime}, + update_by = #{updateBy}, + create_by = #{createBy}, + + where id = #{id} + and del_flag = #{delFlag} + and tenant_id = #{tenantId} + + + + update flow_definition set is_publish = 9 + where flow_code in + + #{item} + + and del_flag = #{entity.delFlag} + and tenant_id = #{entity.tenantId} + + + + update flow_definition set is_publish = #{publishStatus} + where id in + + #{id} + + and del_flag = #{entity.delFlag} + and tenant_id = #{entity.tenantId} + + + + delete + from flow_definition + where id = #{id} + and tenant_id = #{entity.tenantId} + + + + update flow_definition + set del_flag = #{logicDeleteValue} + where id = #{id} + and tenant_id = #{entity.tenantId} + and del_flag = #{logicNotDeleteValue} + + + + + delete from flow_definition where id in + + #{id} + + and tenant_id = #{entity.tenantId} + + + + update flow_definition + set del_flag = #{logicDeleteValue} + where id in + + #{id} + + and tenant_id = #{entity.tenantId} + and del_flag = #{logicNotDeleteValue} + + + + + delete from flow_definition + + and id = #{id} + and flow_code = #{flowCode} + and flow_name = #{flowName} + and model_value = #{modelValue} + and category = #{category} + and version = #{version} + and is_publish = #{isPublish} + and form_custom = #{formCustom} + and form_path = #{formPath} + and activity_status = #{activityStatus} + and listener_type = #{listenerType} + and listener_path = #{listenerPath} + and ext = #{ext} + and create_time = #{createTime} + and update_time = #{updateTime} + and tenant_id = #{tenantId} + + + + + update flow_definition + set del_flag = #{logicDeleteValue} + + and id = #{entity.id} + and flow_code = #{entity.flowCode} + and flow_name = #{entity.flowName} + and model_value = #{entity.modelValue} + and category = #{entity.category} + and version = #{entity.version} + and is_publish = #{entity.isPublish} + and form_custom = #{entity.formCustom} + and form_path = #{entity.formPath} + and activity_status = #{entity.activityStatus} + and listener_type = + #{entity.listenerType} + + and listener_path = + #{entity.listenerPath} + + and ext = #{entity.ext} + and create_time = #{entity.createTime} + and update_time = #{entity.updateTime} + and tenant_id = #{entity.tenantId} + and del_flag = + #{logicNotDeleteValue} + + + + + + + + + + + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/resources/warm/flow/FlowFormMapper.xml b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/resources/warm/flow/FlowFormMapper.xml new file mode 100644 index 0000000..2c60ec8 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/resources/warm/flow/FlowFormMapper.xml @@ -0,0 +1,365 @@ + + + + + + + + + + + + + + + + + + + + + + + select t.id, + t.form_code, + t.form_name, + t.version, + t.is_publish, + t.form_type, + t.form_path, + t.form_content, + t.ext, + t.create_time, + t.update_time, + t.del_flag, + t.tenant_id, + t.create_by, + t.update_by + from flow_form t + + + + LIMIT + #{page.pageSize} offset #{page.pageNum} + + + + + order by ${order} + + + + + and t.id = #{entity.id} + and t.form_code = #{entity.formCode} + and t.form_name = #{entity.formName} + and t.version = #{entity.version} + and t.is_publish = #{entity.isPublish} + and t.form_type = #{entity.formType} + and t.form_path = #{entity.formPath} + and t.form_content = #{entity.formContent} + and t.ext = #{entity.ext} + and t.create_time = #{entity.createTime} + and t.update_time = #{entity.updateTime} + and t.del_flag = #{entity.delFlag} + and t.tenant_id = #{entity.tenantId} + and t.update_by = #{entity.updateBy} + and t.create_by = #{entity.createBy} + + + + + insert into flow_form( + id, + form_code, + form_name, + version, + is_publish, + form_type, + form_path, + form_content, + ext, + create_time, + update_time, + del_flag, + tenant_id, + create_by, + update_by + ) + values + + ( + #{item.id}, + #{item.formCode}, + #{item.formName}, + #{item.version}, + #{item.isPublish}, + #{item.formType}, + #{item.formPath}, + #{item.formContent}, + #{item.ext}, + #{item.createTime}, + #{item.updateTime}, + #{item.delFlag}, + #{item.tenantId}, + #{item.createBy}, + #{item.updateBy} + ) + + + + + insert into flow_form( + id, + form_code, + form_name, + version, + is_publish, + form_type, + form_path, + form_content, + ext, + create_time, + update_time, + del_flag, + tenant_id, + create_by, + update_by + ) + + select + #{item.id}, + #{item.formCode}, + #{item.formName}, + #{item.version}, + #{item.isPublish}, + #{item.formType}, + #{item.formPath}, + #{item.formContent}, + #{item.ext}, + #{item.createTime}, + #{item.updateTime}, + #{item.delFlag}, + #{item.tenantId}, + #{item.createBy}, + #{item.updateBy} + from dual + + + + + + + + + + + + + + + + + + + + + insert into flow_form + + id, + form_code, + form_name, + version, + is_publish, + form_type, + form_path, + form_content, + ext, + create_time, + update_time, + del_flag, + tenant_id, + create_by, + update_by, + + + #{id}, + #{formCode}, + #{formName}, + #{version}, + #{isPublish}, + #{formType}, + #{formPath}, + #{formContent}, + #{ext}, + #{createTime}, + #{updateTime}, + #{delFlag}, + #{tenantId}, + #{createBy}, + #{updateBy} + + + + + update flow_form + + form_code = #{formCode}, + form_name = #{formName}, + version = #{version}, + is_publish = #{isPublish}, + form_type = #{formType}, + form_path = #{formPath}, + form_content = #{formContent}, + ext = #{ext}, + create_time = #{createTime}, + create_by = #{createBy}, + update_by = #{updateBy}, + + , update_time=now() + where id = #{id} + and del_flag = #{delFlag} + and tenant_id = #{tenantId} + + + + update flow_form set is_publish = 9 + where form_code in + + #{item} + + and del_flag = #{entity.delFlag} + and tenant_id = #{entity.tenantId} + + + + delete + from flow_form + where id = #{id} + and tenant_id = #{entity.tenantId} + + + + update flow_form + set del_flag = #{logicDeleteValue} + where id = #{id} + and tenant_id = #{entity.tenantId} + and del_flag = #{logicNotDeleteValue} + + + + + delete from flow_form where id in + + #{id} + + and tenant_id = #{entity.tenantId} + + + + update flow_form + set del_flag = #{logicDeleteValue} + where id in + + #{id} + + and tenant_id = #{entity.tenantId} + and del_flag = #{logicNotDeleteValue} + + + + + delete from flow_form + + and id = #{id} + and form_code = #{formCode} + and form_name = #{formName} + and version = #{version} + and is_publish = #{isPublish} + and form_custom = #{formType} + and form_path = #{formPath} + and form_content = #{formContent} + and ext = #{ext} + and create_time = #{createTime} + and update_time = #{updateTime} + and tenant_id = #{tenantId} + + + + + update flow_form + set del_flag = #{logicDeleteValue} + + and id = #{entity.id} + and form_code = #{entity.formCode} + and form_name = #{entity.formName} + and version = #{entity.version} + and is_publish = #{entity.isPublish} + and form_custom = #{entity.formType} + and form_path = #{entity.formPath} + and form_content = #{entity.formContent} + and ext = #{entity.ext} + and create_time = #{entity.createTime} + and update_time = #{entity.updateTime} + and tenant_id = #{entity.tenantId} + and del_flag = + #{logicNotDeleteValue} + + + + + + + + + + + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/resources/warm/flow/FlowHisTaskMapper.xml b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/resources/warm/flow/FlowHisTaskMapper.xml new file mode 100644 index 0000000..9c33650 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/resources/warm/flow/FlowHisTaskMapper.xml @@ -0,0 +1,515 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select t.id, + t.node_code, + t.node_name, + t.node_type, + t.target_node_code, + t.target_node_name, + t.approver, + t.collaborator, + t.definition_id, + t.instance_id, + t.task_id, + t.cooperate_type, + t.skip_type, + t.flow_status, + t.form_custom, + t.form_path, + t.message, + t.variable, + t.ext, + t.create_time, + t.update_time, + t.tenant_id, + t.del_flag + from flow_his_task t + + + + + LIMIT #{page.pageSize} offset #{page.pageNum} + + + + + order by ${order} + + + + + and t.id = #{id} + and t.node_code = #{entity.nodeCode} + and t.node_name = #{entity.nodeName} + and t.target_node_code = + #{entity.targetNodeCode} + + and t.target_node_name = + #{entity.targetNodeName} + + and t.approver = #{entity.approver} + and t.collaborator = + #{entity.collaborator} + + and t.definition_id = #{entity.definitionId} + and t.instance_id = #{entity.instanceId} + and t.task_id = #{entity.taskId} + and t.cooperate_type = #{entity.cooperateType} + and t.skip_type = #{entity.skipType} + and t.flow_status = #{entity.flowStatus} + and t.form_custom = #{entity.formCustom} + and t.form_path = #{entity.formPath} + and t.message = #{entity.message} + and t.variable = #{entity.variable} + and t.ext = #{entity.ext} + and t.create_time = #{entity.createTime} + and t.update_time = #{entity.updateTime} + and t.del_flag = #{entity.delFlag} + and t.tenant_id = #{entity.tenantId} + + + + + + + + + + + + + + + insert into flow_his_task( + id, + node_code, + node_name, + node_type, + target_node_code, + target_node_name, + approver, + collaborator, + definition_id, + instance_id, + task_id, + cooperate_type, + skip_type, + flow_status, + form_custom, + form_path, + message, + variable, + ext, + create_time, + update_time, + tenant_id, + del_flag + ) + values + + ( + #{item.id}, + #{item.nodeCode}, + #{item.nodeName}, + #{item.nodeType}, + #{item.targetNodeCode}, + #{item.targetNodeName}, + #{item.approver}, + #{item.collaborator}, + #{item.definitionId}, + #{item.instanceId}, + #{item.taskId}, + #{item.cooperateType}, + #{item.skipType}, + #{item.flowStatus}, + #{item.formCustom}, + #{item.formPath}, + #{item.message}, + #{item.variable}, + #{item.ext}, + #{item.createTime}, + #{item.updateTime}, + #{item.tenantId}, + #{item.delFlag} + ) + + + + + insert into flow_his_task( + id, + node_code, + node_name, + node_type, + target_node_code, + target_node_name, + approver, + collaborator, + definition_id, + instance_id, + task_id, + cooperate_type, + skip_type, + flow_status, + form_custom, + form_path, + message, + variable, + ext, + create_time, + update_time, + tenant_id, + del_flag + ) + + select + #{item.id}, + #{item.nodeCode}, + #{item.nodeName}, + #{item.nodeType}, + #{item.targetNodeCode}, + #{item.targetNodeName}, + #{item.approver}, + #{item.collaborator}, + #{item.definitionId}, + #{item.instanceId}, + #{item.taskId}, + #{item.cooperateType}, + #{item.skipType}, + #{item.flowStatus}, + #{item.formCustom}, + #{item.formPath}, + #{item.message}, + #{item.variable}, + #{item.ext}, + #{item.createTime}, + #{item.updateTime}, + #{item.tenantId}, + #{item.delFlag} + from dual + + + + + + + + + + + + + + + insert into flow_his_task + + id, + node_code, + node_name, + node_type, + target_node_code, + target_node_name, + approver, + collaborator, + definition_id, + instance_id, + task_id, + cooperate_type, + skip_type, + flow_status, + form_custom, + form_path, + message, + variable, + ext, + create_time, + update_time, + tenant_id, + del_flag, + + + #{id}, + #{nodeCode}, + #{nodeName}, + #{nodeType}, + #{targetNodeCode}, + #{targetNodeName}, + #{approver}, + #{collaborator}, + #{definitionId}, + #{instanceId}, + #{taskId}, + #{cooperateType}, + #{skipType}, + #{flowStatus}, + #{formCustom}, + #{formPath}, + #{message}, + #{variable}, + #{ext}, + #{createTime}, + #{updateTime}, + #{tenantId}, + #{delFlag}, + + + + + update flow_his_task + + node_code = #{nodeCode}, + node_name = #{nodeName}, + node_type = #{nodeType}, + target_node_code = #{targetNodeCode}, + target_node_name = #{targetNodeName}, + collaborator = #{collaborator}, + definition_id = #{definitionId}, + instance_id = #{instanceId}, + task_id = #{taskId}, + cooperate_type = #{cooperateType}, + skip_type = #{skipType}, + flow_status = #{flowStatus}, + form_custom = #{formCustom}, + form_path = #{formPath}, + message = #{message}, + variable = #{variable}, + ext = #{ext}, + create_time = #{createTime}, + update_time = #{updateTime}, + + where id = #{id} + and del_flag = #{delFlag} + and tenant_id = #{tenantId} + + + + delete + from flow_his_task + where id = #{id} + and tenant_id = #{entity.tenantId} + + + + update flow_his_task + set del_flag = #{logicDeleteValue} + where id = #{id} + and tenant_id = #{entity.tenantId} + and del_flag = #{logicNotDeleteValue} + + + + + delete from flow_his_task where instance_id in + + #{instanceId} + + and tenant_id = #{entity.tenantId} + + + + update flow_his_task + set del_flag = #{logicDeleteValue} + where instance_id in + + #{instanceId} + + and tenant_id = #{entity.tenantId} + and del_flag = #{logicNotDeleteValue} + + + + + delete from flow_his_task where id in + + #{id} + + and tenant_id = #{entity.tenantId} + + + + update flow_his_task + set del_flag = #{logicDeleteValue} + where id in + + #{id} + + and tenant_id = #{entity.tenantId} + and del_flag = #{logicNotDeleteValue} + + + + + delete from flow_his_task + + and id = #{id} + and node_code = #{nodeCode} + and node_name = #{nodeName} + and node_type = #{nodeType} + and target_node_code = #{targetNodeCode} + and target_node_name = #{targetNodeName} + and collaborator = #{collaborator} + and definition_id = #{definitionId} + and instance_id = #{instanceId} + and task_id = #{taskId} + and cooperate_type = #{cooperateType} + and skip_type = #{skipType} + and flow_status = #{flowStatus} + and form_custom = #{formCustom} + and form_path = #{formPath} + and message = #{message} + and variable = #{variable} + and ext = #{ext} + and create_time = #{createTime} + and update_time = #{updateTime} + and tenant_id = #{tenantId} + + + + + update flow_his_task + set del_flag = #{logicDeleteValue} + + and id = #{entity.id} + and node_code = #{entity.nodeCode} + and node_name = #{entity.nodeName} + and node_type = #{entity.nodeType} + and target_node_code = + #{entity.targetNodeCode} + + and target_node_name = + #{entity.targetNodeName} + + and approver = #{entity.approver} + and collaborator = + #{entity.collaborator} + + and definition_id = #{entity.definitionId} + and instance_id = #{entity.instanceId} + and task_id = #{entity.taskId} + and cooperate_type = #{entity.cooperateType} + and skip_type = #{entity.skipType} + and flow_status = #{entity.flowStatus} + and form_custom = #{entity.formCustom} + and form_path = #{entity.formPath} + and message = #{entity.message} + and variable = #{entity.variable} + and ext = #{entity.ext} + and create_time = #{entity.createTime} + and update_time = #{entity.updateTime} + and tenant_id = #{entity.tenantId} + and del_flag = + #{logicNotDeleteValue} + + + + + + + + + + + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/resources/warm/flow/FlowInstanceMapper.xml b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/resources/warm/flow/FlowInstanceMapper.xml new file mode 100644 index 0000000..c95d2c6 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/resources/warm/flow/FlowInstanceMapper.xml @@ -0,0 +1,385 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + select t.id, + t.business_id, + t.definition_id, + t.tenant_id, + t.node_type, + t.node_code, + t.node_name, + t.variable, + t.flow_status, + t.activity_status, + t.def_json, + t.create_by, + t.create_time, + t.update_time, + t.del_flag, + t.tenant_id, + t.ext, + t.update_by + from flow_instance t + + + + + LIMIT #{page.pageSize} offset #{page.pageNum} + + + + + order by ${order} + + + + + and t.id = #{entity.id} + and t.business_id = #{entity.businessId} + + and t.definition_id = #{entity.definitionId} + and t.node_code = #{entity.nodeCode} + and t.node_name = #{entity.nodeName} + and t.variable = #{entity.variable} + and t.flow_status = #{entity.flowStatus} + and t.activity_status = #{entity.activityStatus} + and t.def_json = #{entity.defJson} + and t.create_by = #{entity.createBy} + and t.create_time = #{entity.createTime} + and t.update_time = #{entity.updateTime} + and t.del_flag = #{entity.delFlag} + and t.tenant_id = #{entity.tenantId} + and t.ext = #{entity.ext} + and t.update_by = #{entity.updateBy} + + + + + + + + + + + + + + + insert into flow_instance( + id, + business_id, + definition_id, + node_code, + node_name, + node_type, + variable, + flow_status, + activity_status, + def_json, + create_by, + create_time, + update_time, + del_flag, + tenant_id, + ext, + update_by + ) + values + + ( + #{item.id}, + #{item.businessId}, + #{item.definitionId}, + #{item.nodeCode}, + #{item.nodeName}, + #{item.nodeType}, + #{item.variable}, + #{item.flowStatus}, + #{item.activityStatus}, + #{item.defJson}, + #{item.createBy}, + #{item.createTime}, + #{item.updateTime}, + #{item.delFlag}, + #{item.tenantId}, + #{item.ext}, + #{item.updateBy} + ) + + + + + insert into flow_instance( + id, + business_id, + definition_id, + node_code, + node_name, + node_type, + variable, + flow_status, + activity_status, + def_json, + create_by, + create_time, + update_time, + del_flag, + tenant_id, + ext, + update_by + ) + + ( + #{item.id}, + #{item.businessId}, + #{item.definitionId}, + #{item.nodeCode}, + #{item.nodeName}, + #{item.nodeType}, + #{item.variable}, + #{item.flowStatus}, + #{item.activityStatus}, + #{item.defJson}, + #{item.createBy}, + #{item.createTime}, + #{item.updateTime}, + #{item.delFlag}, + #{item.tenantId}, + #{item.ext}, + #{item.updateBy} + ) + + + + + + + + + + + insert into flow_instance + + id, + business_id, + definition_id, + node_code, + node_name, + node_type, + variable, + flow_status, + activity_status, + def_json, + create_by, + create_time, + update_time, + del_flag, + tenant_id, + ext, + update_by, + + + #{id}, + #{businessId}, + #{definitionId}, + #{nodeCode}, + #{nodeName}, + #{nodeType}, + #{variable}, + #{flowStatus}, + #{activityStatus}, + #{defJson}, + #{createBy}, + #{createTime}, + #{updateTime}, + #{delFlag}, + #{tenantId}, + #{ext}, + #{updateBy}, + + + + + update flow_instance + + business_id = #{businessId}, + definition_id = #{definitionId}, + node_type = #{nodeType}, + node_code = #{nodeCode}, + node_name = #{nodeName}, + variable = #{variable}, + flow_status = #{flowStatus}, + activity_status = #{activityStatus}, + def_json = #{defJson}, + create_by = #{createBy}, + create_time = #{createTime}, + update_time = #{updateTime}, + ext = #{ext}, + update_by = #{updateBy}, + + where id = #{id} + and del_flag = #{delFlag} + and tenant_id = #{tenantId} + + + + delete + from flow_instance + where id = #{id} + and tenant_id = #{entity.tenantId} + + + + update flow_instance + set del_flag = #{logicDeleteValue} + where id = #{id} + and tenant_id = #{entity.tenantId} + and del_flag = #{logicNotDeleteValue} + + + + + delete from flow_instance where id in + + #{id} + + and tenant_id = #{entity.tenantId} + + + + update flow_instance + set del_flag = #{logicDeleteValue} + where id in + + #{id} + + and tenant_id = #{entity.tenantId} + and del_flag = #{logicNotDeleteValue} + + + + + delete from flow_instance + + and id = #{id} + and business_id = #{businessId} + and definition_id = #{definitionId} + and node_code = #{nodeCode} + and node_name = #{nodeName} + and node_type = #{nodeType} + and flow_status = #{flowStatus} + and flow_status = #{variable} + and activity_status = #{activityStatus} + and def_json = #{defJson} + and create_by = #{createBy} + and create_time = #{createTime} + and update_time = #{updateTime} + and tenant_id = #{tenantId} + and ext = #{ext} + + + + + update flow_instance + set del_flag = #{logicDeleteValue} + + and id = #{entity.id} + and business_id = #{entity.businessId} + + and definition_id = #{entity.definitionId} + and node_code = #{entity.nodeCode} + and node_name = #{entity.nodeName} + and node_type = #{entity.nodeType} + and flow_status = #{entity.flowStatus} + and flow_status = #{entity.variable} + and activity_status = #{entity.activityStatus} + and def_json = #{entity.defJson} + and create_by = #{entity.createBy} + and create_time = #{entity.createTime} + and update_time = #{entity.updateTime} + and tenant_id = #{entity.tenantId} + and ext = #{entity.ext} + and del_flag = + #{logicNotDeleteValue} + + + + + + + + + + + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/resources/warm/flow/FlowNodeMapper.xml b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/resources/warm/flow/FlowNodeMapper.xml new file mode 100644 index 0000000..fa9072a --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/resources/warm/flow/FlowNodeMapper.xml @@ -0,0 +1,471 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select t.id, + t.node_type, + t.definition_id, + t.node_code, + t.node_name, + t.permission_flag, + t.node_ratio, + t.coordinate, + t.any_node_skip, + t.listener_type, + t.listener_path, + t.form_custom, + t.form_path, + t.version, + t.create_time, + t.update_time, + t.ext, + t.del_flag, + t.tenant_id, + t.create_by, + t.update_by + from flow_node t + + + + + LIMIT #{page.pageSize} offset #{page.pageNum} + + + + + order by ${order} + + + + + and t.id = #{entity.id} + and t.node_type = #{entity.nodeType} + and t.definition_id = #{entity.definitionId} + and t.node_code = #{entity.nodeCode} + and t.node_name = #{entity.nodeName} + and t.permission_flag = + #{entity.permissionFlag} + + and t.node_ratio = #{entity.nodeRatio} + and t.coordinate = #{entity.coordinate} + + and t.any_node_skip = + #{entity.anyNodeSkip} + + and t.listener_type = + #{entity.listenerType} + + and t.listener_path = + #{entity.listenerPath} + + and t.form_custom = #{entity.formCustom} + and t.form_path = #{entity.formPath} + and t.version = #{entity.version} + and t.create_time = #{entity.createTime} + and t.update_time = #{entity.updateTime} + and t.ext = #{entity.ext} + and t.del_flag = #{entity.delFlag} + and t.tenant_id = #{entity.tenantId} + and t.update_by = #{entity.updateBy} + and t.create_by = #{entity.createBy} + + + + + insert into flow_node( + id, + node_type, + definition_id, + node_code, + node_name, + permission_flag, + node_ratio, + coordinate, + any_node_skip, + listener_type, + listener_path, + form_custom, + form_path, + version, + create_time, + update_time, + ext, + del_flag, + tenant_id, + create_by, + update_by + ) + values + + ( + #{item.id}, + #{item.nodeType}, + #{item.definitionId}, + #{item.nodeCode}, + #{item.nodeName}, + #{item.permissionFlag}, + #{item.nodeRatio}, + #{item.coordinate}, + #{item.anyNodeSkip}, + #{item.listenerType}, + #{item.listenerPath}, + #{item.formCustom}, + #{item.formPath}, + #{item.version}, + #{item.createTime}, + #{item.updateTime}, + #{item.ext}, + #{item.delFlag}, + #{item.tenantId}, + #{item.createBy}, + #{item.updateBy} + ) + + + + + insert + into + flow_node( + id, + node_type, + definition_id, + node_code, + node_name, + permission_flag, + node_ratio, + coordinate, + any_node_skip, + listener_type, + listener_path, + form_custom, + form_path, + version, + create_time, + update_time, + ext, + del_flag, + tenant_id, + create_by, + update_by + ) + + select + #{item.id}, + #{item.nodeType}, + #{item.definitionId}, + #{item.nodeCode}, + #{item.nodeName}, + #{item.permissionFlag}, + #{item.nodeRatio}, + #{item.coordinate}, + #{item.anyNodeSkip}, + #{item.listenerType}, + #{item.listenerPath}, + #{item.formCustom}, + #{item.formPath}, + #{item.version}, + #{item.createTime}, + #{item.updateTime}, + #{item.ext}, + #{item.delFlag}, + #{item.tenantId}, + #{item.createBy}, + #{item.updateBy} + from dual + + + + + + + + + + + + + + + + + + + + + insert into flow_node + + id, + node_type, + definition_id, + node_code, + node_name, + permission_flag, + + node_ratio, + coordinate, + any_node_skip, + listener_type, + listener_path, + form_custom, + form_path, + version, + create_time, + update_time, + ext, + del_flag, + tenant_id, + create_by, + update_by, + + + #{id}, + #{nodeType}, + #{definitionId}, + #{nodeCode}, + #{nodeName}, + #{permissionFlag}, + + #{nodeRatio}, + #{coordinate}, + #{anyNodeSkip}, + #{listenerType}, + #{listenerPath}, + #{formCustom}, + #{formPath}, + #{version}, + #{createTime}, + #{updateTime}, + #{ext}, + #{delFlag}, + #{tenantId}, + #{createBy}, + #{updateBy}, + + + + + update flow_node + + node_type = #{nodeType}, + definition_id = #{definitionId}, + node_code = #{nodeCode}, + node_name = #{nodeName}, + permission_flag = #{permissionFlag}, + node_ratio = #{nodeRatio}, + coordinate = #{coordinate}, + any_node_skip = #{anyNodeSkip}, + listener_type = #{listenerType}, + listener_path = #{listenerPath}, + form_custom = #{formCustom}, + form_path = #{formPath}, + version = #{version}, + create_time = #{createTime}, + update_time = #{updateTime}, + ext = #{ext}, + create_by = #{createBy}, + update_by = #{updateBy}, + + where id = #{id} + and del_flag = #{delFlag} + and tenant_id = #{tenantId} + + + + delete + from flow_node + where id = #{id} + and tenant_id = #{entity.tenantId} + + + + update flow_node + set del_flag = #{logicDeleteValue} + where id = #{id} + and tenant_id = #{entity.tenantId} + and del_flag = #{logicNotDeleteValue} + + + + + delete from flow_node where id in + + #{id} + + and tenant_id = #{entity.tenantId} + + + + update flow_node + set del_flag = #{logicDeleteValue} + where id in + + #{id} + + and tenant_id = #{entity.tenantId} + and del_flag = #{logicNotDeleteValue} + + + + + delete from flow_node + + and id = #{id} + and node_type = #{nodeType} + and definition_id = #{definitionId} + and node_code = #{nodeCode} + and node_name = #{nodeName} + and permission_flag = #{permissionFlag} + and node_ratio = #{nodeRatio} + and coordinate = #{coordinate} + and any_node_skip = #{anyNodeSkip} + and listener_type = #{listenerType} + and listener_path = #{listenerPath} + and form_custom = #{formCustom}, + and form_path = #{formPath}, + and version = #{version} + and create_time = #{createTime} + and update_time = #{updateTime} + and ext = #{ext} + and tenant_id = #{tenantId} + + + + + update flow_node + set del_flag = #{logicDeleteValue} + + and id = #{entity.id} + and node_type = #{entity.nodeType} + and definition_id = #{entity.definitionId} + and node_code = #{entity.nodeCode} + and node_name = #{entity.nodeName} + and permission_flag = + #{entity.permissionFlag} + + and node_ratio = #{entity.nodeRatio} + and coordinate = #{entity.coordinate} + + and any_node_skip = + #{entity.anyNodeSkip} + + and listener_type = + #{entity.listenerType} + + and listener_path = + #{entity.listenerPath} + + and form_custom = #{entity.formCustom} + and form_path = #{entity.formPath} + and version = #{entity.version} + and create_time = #{entity.createTime} + and update_time = #{entity.updateTime} + and ext = #{entity.ext} + and tenant_id = #{entity.tenantId} + and del_flag = + #{logicNotDeleteValue} + + + + + + delete from flow_node where definition_id in + + #{definitionId} + + and tenant_id = #{entity.tenantId} + + + + update flow_node + set del_flag = #{logicDeleteValue} + where definition_id in + + #{definitionId} + + and tenant_id = #{entity.tenantId} + and del_flag = #{logicNotDeleteValue} + + + + + + + + + + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/resources/warm/flow/FlowSkipMapper.xml b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/resources/warm/flow/FlowSkipMapper.xml new file mode 100644 index 0000000..f57bf2b --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/resources/warm/flow/FlowSkipMapper.xml @@ -0,0 +1,389 @@ + + + + + + + + + + + + + + + + + + + + + + + + + select t.id, + t.definition_id, + t.now_node_code, + t.now_node_type, + t.next_node_code, + t.next_node_type, + t.skip_name, + t.skip_type, + t.skip_condition, + t.coordinate, + t.create_time, + t.update_time, + t.del_flag, + t.tenant_id, + t.create_by, + t.update_by + from flow_skip t + + + + + LIMIT #{page.pageSize} offset #{page.pageNum} + + + + + order by ${order} + + + + + and t.id = #{entity.id} + and t.definition_id = #{entity.definitionId} + and t.now_node_code = #{entity.nowNodeCode} + and t.now_node_type = #{entity.nowNodeType} + and t.next_node_code = + #{entity.nextNodeCode} + + and t.next_node_type = #{entity.nextNodeType} + and t.skip_name = #{entity.skip} + and t.skip_type = + #{entity.skipType} + + and t.skip_condition + =#{skipCondition} + + and t.create_time = #{entity.createTime} + and t.update_time = #{entity.updateTime} + and t.del_flag = #{entity.delFlag} + and t.tenant_id = #{entity.tenantId} + and t.update_by = #{entity.updateBy} + and t.create_by = #{entity.createBy} + + + + + + + + + + + + + + + insert into flow_skip( + id, + definition_id, + now_node_code, + now_node_type, + next_node_code, + next_node_type, + skip_name, + skip_type, + skip_condition, + coordinate, + create_time, + update_time, + del_flag, + tenant_id, + create_by, + update_by + ) + values + + ( + #{item.id}, + #{item.definitionId}, + #{item.nowNodeCode}, + #{item.nowNodeType}, + #{item.nextNodeCode}, + #{item.nextNodeType}, + #{item.skipName}, + #{item.skipType}, + #{item.skipCondition}, + #{item.coordinate}, + #{item.createTime}, + #{item.updateTime}, + #{item.delFlag}, + #{item.tenantId}, + #{item.createBy}, + #{item.updateBy} + ) + + + + + insert into flow_skip( + id, + definition_id, + now_node_code, + now_node_type, + next_node_code, + next_node_type, + skip_name, + skip_type, + skip_condition, + coordinate, + create_time, + update_time, + del_flag, + tenant_id, + create_by, + update_by + ) + + select + #{item.id}, + #{item.definitionId}, + #{item.nowNodeCode}, + #{item.nowNodeType}, + #{item.nextNodeCode}, + #{item.nextNodeType}, + #{item.skipName}, + #{item.skipType}, + #{item.skipCondition}, + #{item.coordinate}, + #{item.createTime}, + #{item.updateTime}, + #{item.delFlag}, + #{item.tenantId}, + #{item.createBy}, + #{item.updateBy} + from dual + + + + + + + + + insert into flow_skip + + id, + definition_id, + now_node_code, + now_node_type, + next_node_code, + next_node_type, + skip_name, + skip_type, + skip_condition, + coordinate, + create_time, + update_time, + del_flag, + tenant_id, + create_by, + update_by, + + + #{id}, + #{definitionId}, + #{nowNodeCode}, + #{nowNodeType}, + #{nextNodeCode}, + #{nextNodeType}, + #{skipName}, + #{skipType}, + #{skipCondition}, + #{coordinate}, + #{createTime}, + #{updateTime}, + #{delFlag}, + #{tenantId}, + #{createBy}, + #{updateBy} + + + + + update flow_skip + + definition_id = #{definitionId}, + now_node_code = #{nowNodeCode}, + now_node_type = #{nowNodeType}, + next_node_code = #{nextNodeCode}, + next_node_type = #{nextNodeType}, + skip_name = #{skipName}, + skip_type = #{skipType}, + skip_condition = #{skipCondition}, + coordinate = #{coordinate}, + create_time = #{createTime}, + update_time = #{updateTime}, + create_by = #{createBy}, + update_by = #{updateBy}, + + where id = #{id} + and del_flag = #{delFlag} + and tenant_id = #{tenantId} + + + + delete + from flow_skip + where id = #{id} + and tenant_id = #{entity.tenantId} + + + + update flow_skip + set del_flag = #{logicDeleteValue} + where id = #{id} + and tenant_id = #{entity.tenantId} + and del_flag = #{logicNotDeleteValue} + + + + + delete from flow_skip where id in + + #{id} + + and tenant_id = #{entity.tenantId} + + + + update flow_skip + set del_flag = #{logicDeleteValue} + where id in + + #{id} + + and tenant_id = #{entity.tenantId} + and del_flag = #{logicNotDeleteValue} + + + + + + delete from flow_skip + + and id = #{id} + and definition_id = #{definitionId} + and now_node_code = #{nowNodeCode} + and now_node_type = #{nowNodeType} + and next_node_code = #{nextNodeCode} + and next_node_type = #{nextNodeType} + and skip_name = #{skipName} + and skip_type = #{skipType} + and coordinate = #{coordinate} + and skip_condition = #{skipCondition} + and create_time = #{createTime} + and update_time = #{updateTime} + and tenant_id = #{tenantId} + + + + + update flow_skip + set del_flag = #{logicDeleteValue} + + and id = #{entity.id} + and definition_id = #{entity.definitionId} + and now_node_code = + #{entity.nowNodeCode} + + and now_node_type = #{entity.nowNodeType} + and next_node_code = + #{entity.nextNodeCode} + + and next_node_type = #{entity.nextNodeType} + and skip_name = #{entity.skipName} + and skip_type = #{entity.skipType} + and coordinate = #{entity.coordinate} + + and skip_condition = + #{entity.skipCondition} + + and create_time = #{entity.createTime} + and update_time = #{entity.updateTime} + and tenant_id = #{entity.tenantId} + and del_flag = + #{logicNotDeleteValue} + + + + + + delete from flow_skip where definition_id in + + #{definitionId} + + and tenant_id = #{entity.tenantId} + + + + update flow_skip + set del_flag = #{logicDeleteValue} + where definition_id in + + #{definitionId} + + and tenant_id = #{entity.tenantId} + and del_flag = #{logicNotDeleteValue} + + + + + + + + + + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/resources/warm/flow/FlowTaskMapper.xml b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/resources/warm/flow/FlowTaskMapper.xml new file mode 100644 index 0000000..805cd29 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/resources/warm/flow/FlowTaskMapper.xml @@ -0,0 +1,378 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + select t.id, + t.node_code, + t.node_name, + t.node_type, + t.flow_status, + t.definition_id, + t.instance_id, + t.form_custom, + t.form_path, + t.create_time, + t.update_time, + t.del_flag, + t.tenant_id, + t.create_by, + t.update_by + from flow_task t + + + + + LIMIT #{page.pageSize} offset #{page.pageNum} + + + + + order by ${order} + + + + + and t.id = #{entity.id} + and t.node_code = #{entity.nodeCode} + and t.node_name = #{entity.nodeName} + and t.node_type = #{entity.nodeCode} + and t.flow_status = #{entity.flowStatus} + and t.definition_id = #{entity.definitionId} + and t.instance_id = #{entity.instanceId} + and t.form_custom = #{entity.formCustom} + and t.form_path = #{entity.formPath} + and t.create_time = #{entity.createTime} + and t.update_time = #{entity.updateTime} + and t.tenant_id = #{entity.tenantId} + and t.del_flag = #{entity.delFlag} + and t.create_by = #{entity.createBy} + and t.update_by = #{entity.updateBy} + + + + + insert into flow_task( + id, + node_code, + node_name, + node_type, + flow_status, + definition_id, + instance_id, + form_custom, + form_path, + create_time, + update_time, + del_flag, + tenant_id, + create_by, + update_by + ) + values + + ( + #{item.id}, + #{item.nodeCode}, + #{item.nodeName}, + #{item.nodeType}, + #{item.flowStatus}, + #{item.definitionId}, + #{item.instanceId}, + #{item.formCustom}, + #{item.formPath}, + #{item.createTime}, + #{item.updateTime}, + #{item.delFlag}, + #{item.tenantId}, + #{item.createBy}, + #{item.updateBy} + ) + + + + + insert into flow_task( + id, + node_code, + node_name, + node_type, + flow_status, + definition_id, + instance_id, + form_custom, + form_path, + create_time, + update_time, + del_flag, + tenant_id, + create_by, + update_by + ) + + select + #{item.id}, + #{item.nodeCode}, + #{item.nodeName}, + #{item.nodeType}, + #{item.flowStatus}, + #{item.definitionId}, + #{item.instanceId}, + #{item.formCustom}, + #{item.formPath}, + #{item.createTime}, + #{item.updateTime}, + #{item.delFlag}, + #{item.tenantId}, + #{item.createBy}, + #{item.updateBy} + from dual + + + + + + + + + + + + + + + + + + + + + insert into flow_task + + id, + node_code, + node_name, + node_type, + flow_status, + definition_id, + instance_id, + form_custom, + form_path, + create_time, + update_time, + del_flag, + tenant_id, + create_by, + update_by, + + + #{id}, + #{nodeCode}, + #{nodeName}, + #{nodeType}, + #{flowStatus}, + #{definitionId}, + #{instanceId}, + #{formCustom}, + #{formPath}, + #{createTime}, + #{updateTime}, + #{delFlag}, + #{tenantId}, + #{createBy}, + #{updateBy} + + + + + update flow_task + + node_code = #{nodeCode}, + node_name = #{nodeName}, + node_type = #{nodeType}, + #{flowStatus}, + definition_id = #{definitionId}, + instance_id = #{instanceId}, + form_custom = #{formCustom}, + form_path = #{formPath}, + create_time = #{createTime}, + update_time = #{updateTime}, + create_by = #{createBy}, + update_by = #{updateBy}, + + where id = #{id} + and del_flag = #{delFlag} + and tenant_id = #{tenantId} + + + + delete + from flow_task + where id = #{id} + and tenant_id = #{entity.tenantId} + + + + update flow_task + set del_flag = #{logicDeleteValue} + where id = #{id} + and tenant_id = #{entity.tenantId} + and del_flag = #{logicNotDeleteValue} + + + + + delete from flow_task where instance_id in + + #{instanceId} + + and tenant_id = #{entity.tenantId} + + + + update flow_task + set del_flag = #{logicDeleteValue} + where instance_id in + + #{instanceId} + + and tenant_id = #{entity.tenantId} + and del_flag = #{logicNotDeleteValue} + + + + + delete from flow_task where id in + + #{id} + + and tenant_id = #{entity.tenantId} + + + + update flow_task + set del_flag = #{logicDeleteValue} + where id in + + #{id} + + and tenant_id = #{entity.tenantId} + and del_flag = #{logicNotDeleteValue} + + + + + delete from flow_task + + and id = #{id} + and node_code = #{nodeCode} + and node_name = #{nodeName} + and node_type = #{nodeType} + and flow_status = #{flowStatus} + and definition_id = #{definitionId} + and instance_id = #{instanceId} + and form_custom = #{formCustom} + and form_path = #{formPath} + and create_time = #{createTime} + and update_time = #{updateTime} + and tenant_id = #{tenantId} + + + + + update flow_task + set del_flag = #{logicDeleteValue} + + and id = #{entity.id} + and node_code = #{entity.nodeCode} + and node_name = #{entity.nodeName} + and node_type = #{entity.nodeType} + and flow_status = #{entity.flowStatus} + and definition_id = #{entity.definitionId} + and instance_id = #{entity.instanceId} + and form_custom = #{entity.formCustom} + and form_path = #{entity.formPath} + and create_time = #{entity.createTime} + and update_time = #{entity.updateTime} + and tenant_id = #{entity.tenantId} + and del_flag = + #{logicNotDeleteValue} + + + + + + + + + + + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/resources/warm/flow/FlowUserMapper.xml b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/resources/warm/flow/FlowUserMapper.xml new file mode 100644 index 0000000..04ef69c --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-core/src/main/resources/warm/flow/FlowUserMapper.xml @@ -0,0 +1,370 @@ + + + + + + + + + + + + + + + + + + + select t.id, + t.type, + t.processed_by, + t.associated, + t.create_time, + t.update_time, + t.create_by, + t.tenant_id, + t.del_flag, + t.update_by + from flow_user t + + + + + LIMIT #{page.pageSize} offset #{page.pageNum} + + + + + order by ${order} + + + + + and t.id = #{entity.id} + and t.type = #{entity.type} + and t.processed_by = + #{entity.processedBy} + + and t.associated = #{entity.associated} + and t.create_time = #{entity.createTime} + and t.update_time = #{entity.updateTime} + and t.create_by = #{entity.createBy} + and t.tenant_id = #{entity.tenantId} + and t.del_flag = #{entity.delFlag} + and t.update_by = #{entity.updateBy} + + + + + + + + + + + + + insert into flow_user( + id, + type, + processed_by, + associated, + create_time, + update_time, + create_by, + del_flag, + tenant_id, + update_by + ) + values + + ( + #{item.id}, + #{item.type}, + #{item.processedBy}, + #{item.associated}, + #{item.createTime}, + #{item.updateTime}, + #{item.createBy}, + #{item.delFlag}, + #{item.tenantId}, + #{item.updateBy} + ) + + + + + insert into flow_user( + id, + type, + processed_by, + associated, + create_time, + update_time, + create_by, + del_flag, + tenant_id, + update_by + ) + + select + #{item.id}, + #{item.type}, + #{item.processedBy}, + #{item.associated}, + #{item.createTime}, + #{item.updateTime}, + #{item.createBy}, + #{item.delFlag}, + #{item.tenantId}, + #{item.updateBy} + from dual + + + + + + + + + + + + + + + insert into flow_user + + id, + type, + processed_by, + associated, + create_time, + update_time, + create_by, + del_flag, + tenant_id, + update_by, + + + #{id}, + #{type}, + #{processedBy}, + #{associated}, + #{createTime}, + #{updateTime}, + #{createBy}, + #{delFlag}, + #{tenantId}, + #{updateBy} + + + + + update flow_user + + type = #{type}, + processed_by = #{processedBy}, + associated = #{associated}, + create_time = #{createTime}, + update_time = #{updateTime}, + create_by = #{createBy}, + update_by = #{updateBy}, + + where id = #{id} + and del_flag = #{delFlag} + and tenant_id = #{tenantId} + + + + delete + from flow_user + where id = #{id} + and tenant_id = #{entity.tenantId} + + + + update flow_user + set del_flag = #{logicDeleteValue} + where id = #{id} + and tenant_id = #{entity.tenantId} + and del_flag = #{logicNotDeleteValue} + + + + + delete from flow_user where id in + + #{id} + + and tenant_id = #{entity.tenantId} + + + + update flow_user + set del_flag = #{logicDeleteValue} + where id in + + #{id} + + and tenant_id = #{entity.tenantId} + and del_flag = #{logicNotDeleteValue} + + + + + delete from flow_user + + and id = #{id} + and type = #{type} + and processed_by = #{processedBy} + and associated = #{associated} + and create_time = #{createTime} + and update_time = #{updateTime} + and create_by = #{createBy} + and tenant_id = #{tenantId} + + + + + update flow_user + set del_flag = #{logicDeleteValue} + + and id = #{entity.id} + and type = #{type} + and processed_by = #{entity.processedBy} + and associated = #{entity.associated} + and create_time = #{entity.createTime} + and update_time = #{entity.updateTime} + and create_by = #{entity.createBy} + and tenant_id = #{entity.tenantId} + and del_flag = #{entity.delFlag} + + + + + delete from flow_user where associated in + + #{taskId} + + + + + update flow_user + set del_flag = #{logicDeleteValue} + where associated in + + #{taskId} + + and tenant_id = #{entity.tenantId} + and del_flag = #{logicNotDeleteValue} + + + + + + + + + + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-sb-starter/pom.xml b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-sb-starter/pom.xml new file mode 100644 index 0000000..9389c9e --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-sb-starter/pom.xml @@ -0,0 +1,41 @@ + + + 4.0.0 + + org.dromara.warm + warm-flow-mybatis + 1.8.4 + + + warm-flow-mybatis-sb-starter + warm-flow-mybatis-sb-starter + warm-flow orm为mybatis的springboot starter + https://github.com/dromara/warm-flow + + + + + org.dromara.warm + warm-flow-mybatis-core + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + + + + org.springframework + spring-tx + + + + org.dromara.warm + warm-flow-plugin-modes-sb + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-sb-starter/src/main/java/org/dromara/warm/flow/spring/boot/config/FlowAutoConfig.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-sb-starter/src/main/java/org/dromara/warm/flow/spring/boot/config/FlowAutoConfig.java new file mode 100644 index 0000000..9ca7f8e --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-sb-starter/src/main/java/org/dromara/warm/flow/spring/boot/config/FlowAutoConfig.java @@ -0,0 +1,71 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.spring.boot.config; + +import org.apache.ibatis.builder.xml.XMLMapperBuilder; +import org.apache.ibatis.io.Resources; +import org.apache.ibatis.session.SqlSessionFactory; +import org.apache.ibatis.type.JdbcType; +import org.dromara.warm.flow.core.config.WarmFlow; +import org.dromara.warm.flow.orm.utils.CommonUtil; +import org.dromara.warm.plugin.modes.sb.config.BeanConfig; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Configuration; + +import java.util.Arrays; +import java.util.List; + +/** + * 工作流bean注册配置 + * + * @author warm + * @since 2023/6/5 23:01 + */ +@Configuration +@ConditionalOnProperty(value = "warm-flow.enabled", havingValue = "true", matchIfMissing = true) +@MapperScan("org.dromara.warm.flow.orm.mapper") +public class FlowAutoConfig extends BeanConfig { + + private final SqlSessionFactory sqlSessionFactory; + + public FlowAutoConfig(SqlSessionFactory sqlSessionFactory) { + this.sqlSessionFactory = sqlSessionFactory; + } + + @Override + public void after(WarmFlow flowConfig) { + loadXml(sqlSessionFactory); + CommonUtil.setDataSourceType(flowConfig, sqlSessionFactory.getConfiguration()); + } + + private void loadXml(SqlSessionFactory sqlSessionFactory) { + List mapperList = Arrays.asList("warm/flow/FlowDefinitionMapper.xml", "warm/flow/FlowHisTaskMapper.xml" + , "warm/flow/FlowInstanceMapper.xml", "warm/flow/FlowNodeMapper.xml", "warm/flow/FlowFormMapper.xml" + , "warm/flow/FlowSkipMapper.xml", "warm/flow/FlowTaskMapper.xml", "warm/flow/FlowUserMapper.xml"); + org.apache.ibatis.session.Configuration configuration = sqlSessionFactory.getConfiguration(); + configuration.setJdbcTypeForNull(JdbcType.NULL); + try { + for (String mapper : mapperList) { + XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(Resources.getResourceAsStream(mapper), + configuration, getClass().getResource("/") + mapper, configuration.getSqlFragments()); + xmlMapperBuilder.parse(); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-sb-starter/src/main/resources/META-INF/spring.factories b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-sb-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..6543e11 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-sb-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + org.dromara.warm.flow.spring.boot.config.FlowAutoConfig diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-sb-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-sb-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..264198f --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-sb-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +org.dromara.warm.flow.spring.boot.config.FlowAutoConfig diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-sb3-starter/pom.xml b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-sb3-starter/pom.xml new file mode 100644 index 0000000..1cf1efe --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-sb3-starter/pom.xml @@ -0,0 +1,84 @@ + + + 4.0.0 + + org.dromara.warm + warm-flow-mybatis + 1.8.4 + + + warm-flow-mybatis-sb3-starter + warm-flow-mybatis-sb3-starter + warm-flow orm为mybatis的springboot3 starter + https://github.com/dromara/warm-flow + + + 3.0.3 + + + + + org.dromara.warm + warm-flow-mybatis-sb-starter + + + org.mybatis + mybatis + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + + + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + ${mybatis-spring-boot3} + + + + + + + org.springframework.boot + spring-boot-dependencies + ${springboot3.version} + pom + import + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + ${java17.version} + ${java17.version} + true + ${java17.path}\javac + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.0 + + + + ${java17.version} + + + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-solon-plugin/pom.xml b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-solon-plugin/pom.xml new file mode 100644 index 0000000..af67611 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-solon-plugin/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + + org.dromara.warm + warm-flow-mybatis + 1.8.4 + + + warm-flow-mybatis-solon-plugin + warm-flow-mybatis-solon-plugin + warm-flow orm为mybatis的solon plugin + https://github.com/dromara/warm-flow + + + + org.noear + solon + + + + org.slf4j + slf4j-api + + + + org.dromara.warm + warm-flow-mybatis-core + + + + org.noear + mybatis-solon-plugin + + + + org.dromara.warm + warm-flow-plugin-modes-solon + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-solon-plugin/src/main/java/org/dromara/warm/flow/solon/WarmFlowDaoSolonPlugin.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-solon-plugin/src/main/java/org/dromara/warm/flow/solon/WarmFlowDaoSolonPlugin.java new file mode 100644 index 0000000..c0362dd --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-solon-plugin/src/main/java/org/dromara/warm/flow/solon/WarmFlowDaoSolonPlugin.java @@ -0,0 +1,61 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.solon; + +import org.apache.ibatis.builder.xml.XMLMapperBuilder; +import org.apache.ibatis.io.Resources; +import org.apache.ibatis.session.Configuration; +import org.dromara.warm.flow.solon.config.FlowAutoConfig; +import org.noear.solon.core.AppContext; +import org.noear.solon.core.Plugin; + +import java.util.Arrays; +import java.util.List; + +/** + * Warm-Flow工作流插件 + * + * @author warm + */ +public class WarmFlowDaoSolonPlugin implements Plugin { + + @Override + public void start(AppContext context) { + context.beanMake(FlowAutoConfig.class); + + context.onEvent(Configuration.class, e -> { + List mapperList = Arrays.asList( + "warm/flow/FlowDefinitionMapper.xml", + "warm/flow/FlowHisTaskMapper.xml", + "warm/flow/FlowInstanceMapper.xml", + "warm/flow/FlowNodeMapper.xml", + "warm/flow/FlowFormMapper.xml", + "warm/flow/FlowSkipMapper.xml", + "warm/flow/FlowTaskMapper.xml", + "warm/flow/FlowUserMapper.xml"); + + try { + for (String mapper : mapperList) { + XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(Resources.getResourceAsStream(mapper), + e, getClass().getResource("/") + mapper, e.getSqlFragments()); + xmlMapperBuilder.parse(); + } + } catch (Exception ex) { + throw new RuntimeException(ex); + } + }); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-solon-plugin/src/main/java/org/dromara/warm/flow/solon/config/FlowAutoConfig.java b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-solon-plugin/src/main/java/org/dromara/warm/flow/solon/config/FlowAutoConfig.java new file mode 100644 index 0000000..e4b78a7 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-solon-plugin/src/main/java/org/dromara/warm/flow/solon/config/FlowAutoConfig.java @@ -0,0 +1,40 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.solon.config; + +import org.apache.ibatis.solon.annotation.Db; +import org.dromara.warm.flow.core.config.WarmFlow; +import org.dromara.warm.flow.orm.utils.CommonUtil; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; + +/** + * 工作流bean注册配置 + * + * @author warm + * @since 2023/6/5 23:01 + */ +@Configuration +@Condition(onProperty = "${warm-flow.enabled:true} = true") +public class FlowAutoConfig { + + @Bean + public WarmFlow initFlow(@Db org.apache.ibatis.session.Configuration db1Cfg, WarmFlow flowConfig) { + CommonUtil.setDataSourceType(flowConfig, db1Cfg); + return flowConfig; + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-solon-plugin/src/main/resources/META-INF/solon/org.dromara.warm.flow.solon.properties b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-solon-plugin/src/main/resources/META-INF/solon/org.dromara.warm.flow.solon.properties new file mode 100644 index 0000000..9eb3b92 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-orm/warm-flow-mybatis/warm-flow-mybatis-solon-plugin/src/main/resources/META-INF/solon/org.dromara.warm.flow.solon.properties @@ -0,0 +1 @@ +solon.plugin=org.dromara.warm.flow.solon.WarmFlowDaoSolonPlugin diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/pom.xml b/smart-flow-api/warm-flow/warm-flow-plugin/pom.xml new file mode 100644 index 0000000..6c00e66 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/pom.xml @@ -0,0 +1,24 @@ + + + 4.0.0 + + org.dromara.warm + warm-flow + 1.8.4 + + + warm-flow-plugin + warm-flow-plugin + pom + https://github.com/dromara/warm-flow + + + warm-flow-plugin-json + warm-flow-plugin-modes + warm-flow-plugin-ui + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-json/pom.xml b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-json/pom.xml new file mode 100644 index 0000000..8648d80 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-json/pom.xml @@ -0,0 +1,53 @@ + + + 4.0.0 + + org.dromara.warm + warm-flow-plugin + 1.8.4 + + + warm-flow-plugin-json + warm-flow-plugin-json + https://github.com/dromara/warm-flow + + + + org.dromara.warm + warm-flow-core + + + + com.alibaba.fastjson2 + fastjson2 + true + + + + com.google.code.gson + gson + true + + + + com.fasterxml.jackson.core + jackson-databind + true + + + + org.noear + snack3 + true + + + + org.noear + snack4 + true + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-json/src/main/java/org/dromara/warm/plugin/json/JsonConvertFastJson.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-json/src/main/java/org/dromara/warm/plugin/json/JsonConvertFastJson.java new file mode 100644 index 0000000..ddbeb76 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-json/src/main/java/org/dromara/warm/plugin/json/JsonConvertFastJson.java @@ -0,0 +1,95 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.plugin.json; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONWriter; +import com.alibaba.fastjson2.TypeReference; +import org.dromara.warm.flow.core.json.JsonConvert; +import org.dromara.warm.flow.core.utils.ObjectUtil; +import org.dromara.warm.flow.core.utils.StringUtils; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * fastjson:map和json字符串转换工具类 + * + * @author warm + */ +public class JsonConvertFastJson implements JsonConvert { + + /** + * 将字符串转为map + * + * @param jsonStr json字符串 + * @return map + */ + @Override + public Map strToMap(String jsonStr) { + if (StringUtils.isNotEmpty(jsonStr)) { + return JSON.parseObject(jsonStr, new TypeReference>() { + }); + } + return new HashMap<>(); + } + + /** + * 将字符串转为bean + * + * @param jsonStr json字符串 + * @param clazz Class + * @return T + */ + @Override + public T strToBean(String jsonStr, Class clazz) { + if (StringUtils.isNotEmpty(jsonStr)) { + return JSON.parseObject(jsonStr, clazz); + } + return null; + } + + /** + * 将字符串转为集合 + * + * @param jsonStr json字符串 + * @return List + */ + @Override + public List strToList(String jsonStr) { + if (StringUtils.isNotEmpty(jsonStr)) { + return JSON.parseObject(jsonStr, new TypeReference>() { + }); + } + return null; + } + + /** + * 将对象转为字符串 + * + * @param variable object + * @return json字符串 + */ + @Override + public String objToStr(Object variable) { + if (ObjectUtil.isNotNull(variable)) { + return JSON.toJSONString(variable); + } + return null; + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-json/src/main/java/org/dromara/warm/plugin/json/JsonConvertGson.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-json/src/main/java/org/dromara/warm/plugin/json/JsonConvertGson.java new file mode 100644 index 0000000..f628bfb --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-json/src/main/java/org/dromara/warm/plugin/json/JsonConvertGson.java @@ -0,0 +1,99 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.plugin.json; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; +import org.dromara.warm.flow.core.json.JsonConvert; +import org.dromara.warm.flow.core.utils.ObjectUtil; +import org.dromara.warm.flow.core.utils.StringUtils; + +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * fastjson:map和json字符串转换工具类 + * + * @author warm + */ +public class JsonConvertGson implements JsonConvert { + + private static final Gson GSON = new GsonBuilder().create(); + + /** + * 将字符串转为map + * + * @param jsonStr json字符串 + * @return map + */ + @Override + public Map strToMap(String jsonStr) { + if (StringUtils.isNotEmpty(jsonStr)) { + Type type = new TypeToken>() { + }.getType(); + return GSON.fromJson(jsonStr, type); + } + return new HashMap<>(); + } + + /** + * 将字符串转为bean + * + * @param jsonStr json字符串 + * @param clazz Class + * @return T + */ + @Override + public T strToBean(String jsonStr, Class clazz) { + if (StringUtils.isNotEmpty(jsonStr)) { + return GSON.fromJson(jsonStr, clazz); + } + return null; + } + + /** + * 将字符串转为集合 + * + * @param jsonStr json字符串 + * @return List + */ + @Override + public List strToList(String jsonStr) { + if (StringUtils.isNotEmpty(jsonStr)) { + Type listType = new TypeToken>(){}.getType(); + return GSON.fromJson(jsonStr, listType); + } + return null; + } + + /** + * 将对象转为字符串 + * + * @param variable object + * @return json字符串 + */ + @Override + public String objToStr(Object variable) { + if (ObjectUtil.isNotNull(variable)) { + return GSON.toJson(variable); + } + return null; + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-json/src/main/java/org/dromara/warm/plugin/json/JsonConvertJackson.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-json/src/main/java/org/dromara/warm/plugin/json/JsonConvertJackson.java new file mode 100644 index 0000000..e53c3ba --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-json/src/main/java/org/dromara/warm/plugin/json/JsonConvertJackson.java @@ -0,0 +1,127 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.plugin.json; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.type.TypeFactory; +import org.dromara.warm.flow.core.exception.FlowException; +import org.dromara.warm.flow.core.json.JsonConvert; +import org.dromara.warm.flow.core.utils.ObjectUtil; +import org.dromara.warm.flow.core.utils.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * jackson:map和json字符串转换工具类 + * + * @author warm + */ +public class JsonConvertJackson implements JsonConvert { + + private static final Logger log = LoggerFactory.getLogger(JsonConvertJackson.class); + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .setSerializationInclusion(JsonInclude.Include.NON_EMPTY); + + /** + * 将字符串转为map + * + * @param jsonStr json字符串 + * @return map + */ + @Override + public Map strToMap(String jsonStr) { + if (StringUtils.isNotEmpty(jsonStr)) { + try { + return OBJECT_MAPPER.readValue(jsonStr, TypeFactory.defaultInstance().constructMapType(Map.class, String.class, Object.class)); + } catch (IOException e) { + log.error("json转换异常", e); + throw new FlowException("json转换异常"); + } + } + return new HashMap<>(); + } + + /** + * 将字符串转为bean + * + * @param jsonStr json字符串 + * @param clazz Class + * @return T + */ + @Override + public T strToBean(String jsonStr, Class clazz) { + if (StringUtils.isNotEmpty(jsonStr)) { + try { + return OBJECT_MAPPER.readValue(jsonStr, clazz); + } catch (IOException e) { + log.error("json转换异常", e); + throw new FlowException("json转换异常"); + } + } + return null; + } + + /** + * 将字符串转为集合 + * + * @param jsonStr json字符串 + * @return List + */ + @Override + public List strToList(String jsonStr) { + if (StringUtils.isNotEmpty(jsonStr)) { + try { + return OBJECT_MAPPER.readValue(jsonStr, new TypeReference>() { + }); + } catch (IOException e) { + log.error("json转换异常", e); + throw new FlowException("json转换异常"); + } + } + return null; + } + + /** + * 将对象转为字符串 + * + * @param variable object + * @return json字符串 + */ + @Override + public String objToStr(Object variable) { + if (ObjectUtil.isNotNull(variable)) { + try { + return OBJECT_MAPPER.writeValueAsString(variable); + } catch (Exception e) { + log.error("Map转换异常", e); + throw new FlowException("Map转换异常"); + } + } + return null; + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-json/src/main/java/org/dromara/warm/plugin/json/JsonConvertSnack.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-json/src/main/java/org/dromara/warm/plugin/json/JsonConvertSnack.java new file mode 100644 index 0000000..8d76ff2 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-json/src/main/java/org/dromara/warm/plugin/json/JsonConvertSnack.java @@ -0,0 +1,90 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.plugin.json; + +import org.dromara.warm.flow.core.json.JsonConvert; +import org.dromara.warm.flow.core.utils.ObjectUtil; +import org.dromara.warm.flow.core.utils.StringUtils; +import org.noear.snack.ONode; +import org.noear.snack.core.Feature; +import org.noear.snack.core.Options; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * snack:map和json字符串转换工具类 + * + * @author warm + */ +public class JsonConvertSnack implements JsonConvert { + + /** + * 不可删除,为了在spi加载时候,发下不存在snack3依赖包,触发异常不加载此实现类 + */ + private static final ONode O_NODE = new ONode(); + + /** + * 将字符串转为map + * + * @param jsonStr json字符串 + * @return map + */ + @Override + public Map strToMap(String jsonStr) { + return StringUtils.isEmpty(jsonStr) ? new HashMap<>() : ONode.deserialize(jsonStr); + } + + /** + * 将字符串转为bean + * + * @param jsonStr json字符串 + * @param clazz Class + * @return T + */ + @Override + public T strToBean(String jsonStr, Class clazz) { + return StringUtils.isEmpty(jsonStr) ? null : ONode.deserialize(jsonStr, clazz); + } + + /** + * 将字符串转为集合 + * + * @param jsonStr json字符串 + * @return List + */ + @Override + public List strToList(String jsonStr) { + return StringUtils.isEmpty(jsonStr) ? null : ONode.deserialize(jsonStr, (new ArrayList() { + }).getClass()); + } + + /** + * 将对象转为字符串 + * + * @param variable object + * @return json字符串 + */ + @Override + public String objToStr(Object variable) { + if (ObjectUtil.isNotNull(variable)) { + return ONode.stringify(variable); + } + return null; + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-json/src/main/java/org/dromara/warm/plugin/json/JsonConvertSnack4.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-json/src/main/java/org/dromara/warm/plugin/json/JsonConvertSnack4.java new file mode 100644 index 0000000..ae5e0a8 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-json/src/main/java/org/dromara/warm/plugin/json/JsonConvertSnack4.java @@ -0,0 +1,88 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.plugin.json; + +import org.dromara.warm.flow.core.json.JsonConvert; +import org.dromara.warm.flow.core.utils.ObjectUtil; +import org.dromara.warm.flow.core.utils.StringUtils; +import org.noear.snack4.ONode; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * snack:map和json字符串转换工具类 + * + * @author warm + */ +public class JsonConvertSnack4 implements JsonConvert { + + /** + * 不可删除,为了在spi加载时候,发下不存在snack3依赖包,触发异常不加载此实现类 + */ + private static final ONode O_NODE = new ONode(); + + /** + * 将字符串转为map + * + * @param jsonStr json字符串 + * @return map + */ + @Override + public Map strToMap(String jsonStr) { + return StringUtils.isEmpty(jsonStr) ? new HashMap<>() : ONode.deserialize(jsonStr); + } + + /** + * 将字符串转为bean + * + * @param jsonStr json字符串 + * @param clazz Class + * @return T + */ + @Override + public T strToBean(String jsonStr, Class clazz) { + return StringUtils.isEmpty(jsonStr) ? null : ONode.deserialize(jsonStr, clazz); + } + + /** + * 将字符串转为集合 + * + * @param jsonStr json字符串 + * @return List + */ + @Override + public List strToList(String jsonStr) { + return StringUtils.isEmpty(jsonStr) ? null : ONode.deserialize(jsonStr, (new ArrayList() { + }).getClass()); + } + + /** + * 将对象转为字符串 + * + * @param variable object + * @return json字符串 + */ + @Override + public String objToStr(Object variable) { + if (ObjectUtil.isNotNull(variable)) { + return ONode.serialize(variable); + } + return null; + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-json/src/main/resources/META-INF/services/org.dromara.warm.flow.core.json.JsonConvert b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-json/src/main/resources/META-INF/services/org.dromara.warm.flow.core.json.JsonConvert new file mode 100644 index 0000000..39d5afe --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-json/src/main/resources/META-INF/services/org.dromara.warm.flow.core.json.JsonConvert @@ -0,0 +1,5 @@ +org.dromara.warm.plugin.json.JsonConvertSnack +org.dromara.warm.plugin.json.JsonConvertSnack4 +org.dromara.warm.plugin.json.JsonConvertJackson +org.dromara.warm.plugin.json.JsonConvertFastJson +org.dromara.warm.plugin.json.JsonConvertGson diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/pom.xml b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/pom.xml new file mode 100644 index 0000000..4713d6f --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + org.dromara.warm + warm-flow-plugin + 1.8.4 + + + warm-flow-plugin-modes + warm-flow-plugin-modes + pom + https://github.com/dromara/warm-flow + + + warm-flow-plugin-modes-sb + warm-flow-plugin-modes-solon + + + diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-sb/pom.xml b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-sb/pom.xml new file mode 100644 index 0000000..fa63e1d --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-sb/pom.xml @@ -0,0 +1,63 @@ + + + 4.0.0 + + org.dromara.warm + warm-flow-plugin-modes + 1.8.4 + + + warm-flow-plugin-modes-sb + warm-flow-plugin-modes-sb + https://github.com/dromara/warm-flow + + + + org.dromara.warm + warm-flow-core + + + + org.dromara.warm + warm-flow-mybatis-core + true + + + + org.dromara.warm + warm-flow-mybatis-plus-core + true + + + + + org.dromara.warm + warm-flow-plugin-json + + + + org.springframework.boot + spring-boot + + + + org.springframework.boot + spring-boot-autoconfigure + + + + org.springframework + spring-context + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-sb/src/main/java/org/dromara/warm/plugin/modes/sb/config/BeanConfig.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-sb/src/main/java/org/dromara/warm/plugin/modes/sb/config/BeanConfig.java new file mode 100644 index 0000000..8338ebd --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-sb/src/main/java/org/dromara/warm/plugin/modes/sb/config/BeanConfig.java @@ -0,0 +1,175 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.plugin.modes.sb.config; + +import org.dromara.warm.flow.core.FlowEngine; +import org.dromara.warm.flow.core.config.WarmFlow; +import org.dromara.warm.flow.core.invoker.FrameInvoker; +import org.dromara.warm.flow.core.orm.dao.*; +import org.dromara.warm.flow.core.service.*; +import org.dromara.warm.flow.core.service.impl.*; +import org.dromara.warm.flow.core.utils.ExpressionUtil; +import org.dromara.warm.flow.orm.dao.*; +import org.dromara.warm.flow.orm.entity.*; +import org.dromara.warm.plugin.modes.sb.expression.*; +import org.dromara.warm.plugin.modes.sb.helper.SpelHelper; +import org.dromara.warm.plugin.modes.sb.utils.SpringUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.core.env.Environment; + +import java.util.Objects; + +/** + * 工作流bean注册配置 + * + * @author warm + * @since 2023/6/5 23:01 + */ +@SuppressWarnings("rawtypes unchecked") +@Import({SpringUtil.class, SpelHelper.class}) +@ConditionalOnProperty(value = "warm-flow.enabled", havingValue = "true", matchIfMissing = true) +@EnableConfigurationProperties(WarmFlowProperties.class) +public class BeanConfig { + + private static final Logger log = LoggerFactory.getLogger(BeanConfig.class); + + @Bean + public FlowDefinitionDao definitionDao() { + return new FlowDefinitionDaoImpl(); + } + + @Bean + public DefService definitionService(FlowDefinitionDao definitionDao) { + return new DefServiceImpl().setDao(definitionDao); + } + + @Bean + public ChartService chartService() { + return new ChartServiceImpl(); + } + + @Bean + public FlowNodeDao nodeDao() { + return new FlowNodeDaoImpl(); + } + + @Bean + public NodeService nodeService(FlowNodeDao nodeDao) { + return new NodeServiceImpl().setDao(nodeDao); + } + + @Bean + public FlowSkipDao skipDao() { + return new FlowSkipDaoImpl(); + } + + @Bean + public SkipService skipService(FlowSkipDao skipDao) { + return new SkipServiceImpl().setDao(skipDao); + } + + @Bean + public FlowInstanceDao instanceDao() { + return new FlowInstanceDaoImpl(); + } + + @Bean + public InsService instanceService(FlowInstanceDao instanceDao) { + return new InsServiceImpl().setDao(instanceDao); + } + + @Bean + public FlowTaskDao taskDao() { + return new FlowTaskDaoImpl(); + } + + @Bean + public TaskService taskService(FlowTaskDao taskDao) { + return new TaskServiceImpl().setDao(taskDao); + } + + @Bean + public FlowHisTaskDao hisTaskDao() { + return new FlowHisTaskDaoImpl(); + } + + @Bean + public HisTaskService hisTaskService(FlowHisTaskDao hisTaskDao) { + return new HisTaskServiceImpl().setDao(hisTaskDao); + } + + @Bean + public FlowUserDao flowUserDao() { + return new FlowUserDaoImpl(); + } + + @Bean + public UserService flowUserService(FlowUserDao userDao) { + return new UserServiceImpl().setDao(userDao); + } + + @Bean + public FlowFormDao formDao() { + return new FlowFormDaoImpl(); + } + + @Bean + public FormService flowFormService(FlowFormDao formDao) { + return new FormServiceImpl().setDao(formDao); + } + + @Bean + public WarmFlow initFlow() { + setNewEntity(); + FrameInvoker.setCfgFunction((key) -> Objects.requireNonNull(SpringUtil.getBean(Environment.class)).getProperty(key)); + FrameInvoker.setBeanFunction(SpringUtil::getBean); + WarmFlowProperties warmFlow = SpringUtil.getBean(WarmFlowProperties.class); + warmFlow.init(); + FlowEngine.setFlowConfig(warmFlow); + setExpression(); + after(warmFlow); + log.info("【warm-flow】,加载完成"); + return warmFlow; + } + + private void setExpression() { + ExpressionUtil.setExpression(new ConditionStrategyDefault()); + ExpressionUtil.setExpression(new ConditionStrategySpel()); + ExpressionUtil.setExpression(new ListenerStrategySpel()); + ExpressionUtil.setExpression(new HandlerStrategySpel()); + ExpressionUtil.setExpression(new VoteSignStrategyDefault()); + ExpressionUtil.setExpression(new VoteSignStrategySpel()); + } + + public void setNewEntity() { + FlowEngine.setNewDef(FlowDefinition::new); + FlowEngine.setNewIns(FlowInstance::new); + FlowEngine.setNewHisTask(FlowHisTask::new); + FlowEngine.setNewNode(FlowNode::new); + FlowEngine.setNewSkip(FlowSkip::new); + FlowEngine.setNewTask(FlowTask::new); + FlowEngine.setNewUser(FlowUser::new); + FlowEngine.setNewForm(FlowForm::new); + } + + public void after(WarmFlow flowConfig) { + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-sb/src/main/java/org/dromara/warm/plugin/modes/sb/config/WarmFlowProperties.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-sb/src/main/java/org/dromara/warm/plugin/modes/sb/config/WarmFlowProperties.java new file mode 100644 index 0000000..8986199 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-sb/src/main/java/org/dromara/warm/plugin/modes/sb/config/WarmFlowProperties.java @@ -0,0 +1,26 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.plugin.modes.sb.config; + +import org.dromara.warm.flow.core.config.WarmFlow; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * @author Lion Li + */ +@ConfigurationProperties("warm-flow") +public class WarmFlowProperties extends WarmFlow { +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-sb/src/main/java/org/dromara/warm/plugin/modes/sb/expression/ConditionStrategyDefault.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-sb/src/main/java/org/dromara/warm/plugin/modes/sb/expression/ConditionStrategyDefault.java new file mode 100644 index 0000000..113c2e6 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-sb/src/main/java/org/dromara/warm/plugin/modes/sb/expression/ConditionStrategyDefault.java @@ -0,0 +1,47 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.plugin.modes.sb.expression; + + +import org.dromara.warm.flow.core.constant.FlowCons; +import org.dromara.warm.flow.core.strategy.ConditionStrategy; +import org.dromara.warm.plugin.modes.sb.helper.SpelHelper; + +import java.util.Map; + +/** + * 默认条件表达式 default@@${flag == 5 && flag > 4} + * + * @author warm + */ +public class ConditionStrategyDefault implements ConditionStrategy { + + @Override + public String getType() { + return FlowCons.DEFAULT; + } + + @Override + public Boolean eval(String expression, Map variable) { + expression = expression.replace("$", "#"); + for (Map.Entry entry : variable.entrySet()) { + if (expression.contains(entry.getKey())) { + expression = expression.replace(entry.getKey(), "#" + entry.getKey()); + } + } + return Boolean.TRUE.equals(SpelHelper.parseExpression(expression, variable)); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-sb/src/main/java/org/dromara/warm/plugin/modes/sb/expression/ConditionStrategySpel.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-sb/src/main/java/org/dromara/warm/plugin/modes/sb/expression/ConditionStrategySpel.java new file mode 100644 index 0000000..2115503 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-sb/src/main/java/org/dromara/warm/plugin/modes/sb/expression/ConditionStrategySpel.java @@ -0,0 +1,40 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.plugin.modes.sb.expression; + +import org.dromara.warm.flow.core.constant.FlowCons; +import org.dromara.warm.flow.core.strategy.ConditionStrategy; +import org.dromara.warm.plugin.modes.sb.helper.SpelHelper; + +import java.util.Map; + +/** + * spel条件表达式 spel@@#{@user.eval()} + * + * @author warm + */ +public class ConditionStrategySpel implements ConditionStrategy { + + @Override + public String getType() { + return FlowCons.SPEL; + } + + @Override + public Boolean eval(String expression, Map variable) { + return Boolean.TRUE.equals(SpelHelper.parseExpression(expression, variable)); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-sb/src/main/java/org/dromara/warm/plugin/modes/sb/expression/HandlerStrategySpel.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-sb/src/main/java/org/dromara/warm/plugin/modes/sb/expression/HandlerStrategySpel.java new file mode 100644 index 0000000..7c187d4 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-sb/src/main/java/org/dromara/warm/plugin/modes/sb/expression/HandlerStrategySpel.java @@ -0,0 +1,39 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.plugin.modes.sb.expression; + +import org.dromara.warm.flow.core.strategy.HandlerStrategy; +import org.dromara.warm.plugin.modes.sb.helper.SpelHelper; + +import java.util.Map; + +/** + * 办理人表达式spel: @@spel@@|#{@user.evalVar()} + * + * @author warm + */ +public class HandlerStrategySpel implements HandlerStrategy { + + @Override + public String getType() { + return "#"; + } + + @Override + public Object preEval(String expression, Map variable) { + return SpelHelper.parseExpression(expression, variable); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-sb/src/main/java/org/dromara/warm/plugin/modes/sb/expression/ListenerStrategySpel.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-sb/src/main/java/org/dromara/warm/plugin/modes/sb/expression/ListenerStrategySpel.java new file mode 100644 index 0000000..aed8ae6 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-sb/src/main/java/org/dromara/warm/plugin/modes/sb/expression/ListenerStrategySpel.java @@ -0,0 +1,41 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.plugin.modes.sb.expression; + +import org.dromara.warm.flow.core.strategy.ListenerStrategy; +import org.dromara.warm.plugin.modes.sb.helper.SpelHelper; + +import java.util.Map; + +/** + * spel监听器表达式 #{@user.eval()} + * + * @author warm + */ +public class ListenerStrategySpel implements ListenerStrategy { + + @Override + public String getType() { + return "#"; + } + + @Override + public Boolean eval(String expression, Map variable) { + SpelHelper.parseExpression(expression, variable); + // 恒返回true,说明匹配上监听器表达式,扩展策略也一定要返回true + return true; + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-sb/src/main/java/org/dromara/warm/plugin/modes/sb/expression/VoteSignStrategyDefault.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-sb/src/main/java/org/dromara/warm/plugin/modes/sb/expression/VoteSignStrategyDefault.java new file mode 100644 index 0000000..54122e7 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-sb/src/main/java/org/dromara/warm/plugin/modes/sb/expression/VoteSignStrategyDefault.java @@ -0,0 +1,47 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.plugin.modes.sb.expression; + + +import org.dromara.warm.flow.core.constant.FlowCons; +import org.dromara.warm.flow.core.strategy.VoteSignStrategy; +import org.dromara.warm.plugin.modes.sb.helper.SpelHelper; + +import java.util.Map; + +/** + * 默认条件表达式 default@@${flag == 5 && flag > 4} + * + * @author warm + */ +public class VoteSignStrategyDefault implements VoteSignStrategy { + + @Override + public String getType() { + return FlowCons.DEFAULT; + } + + @Override + public Boolean eval(String expression, Map variable) { + expression = expression.replace("$", "#"); + for (Map.Entry entry : variable.entrySet()) { + if (expression.contains(entry.getKey())) { + expression = expression.replace(entry.getKey(), "#" + entry.getKey()); + } + } + return Boolean.TRUE.equals(SpelHelper.parseExpression(expression, variable)); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-sb/src/main/java/org/dromara/warm/plugin/modes/sb/expression/VoteSignStrategySpel.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-sb/src/main/java/org/dromara/warm/plugin/modes/sb/expression/VoteSignStrategySpel.java new file mode 100644 index 0000000..af4a747 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-sb/src/main/java/org/dromara/warm/plugin/modes/sb/expression/VoteSignStrategySpel.java @@ -0,0 +1,40 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.plugin.modes.sb.expression; + +import org.dromara.warm.flow.core.constant.FlowCons; +import org.dromara.warm.flow.core.strategy.VoteSignStrategy; +import org.dromara.warm.plugin.modes.sb.helper.SpelHelper; + +import java.util.Map; + +/** + * spel条件表达式 spel@@#{@user.eval()} + * + * @author warm + */ +public class VoteSignStrategySpel implements VoteSignStrategy { + + @Override + public String getType() { + return FlowCons.SPEL; + } + + @Override + public Boolean eval(String expression, Map variable) { + return Boolean.TRUE.equals(SpelHelper.parseExpression(expression, variable)); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-sb/src/main/java/org/dromara/warm/plugin/modes/sb/helper/SpelHelper.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-sb/src/main/java/org/dromara/warm/plugin/modes/sb/helper/SpelHelper.java new file mode 100644 index 0000000..f2ce011 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-sb/src/main/java/org/dromara/warm/plugin/modes/sb/helper/SpelHelper.java @@ -0,0 +1,86 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.plugin.modes.sb.helper; + +import org.dromara.warm.flow.core.exception.FlowException; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.expression.BeanFactoryResolver; +import org.springframework.expression.BeanResolver; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.ParserContext; +import org.springframework.expression.common.TemplateParserContext; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; + +import java.util.Map; + +/** + * 条件表达式spel + * + * @author warm + */ +@Configuration +public class SpelHelper implements ApplicationContextAware { + + private static final ExpressionParser parser = new SpelExpressionParser(); + private final static ParserContext parserContext = new TemplateParserContext(); + + private static ApplicationContext applicationContext; + + /** + * bean解析器 用于处理 spel 表达式中对 bean 的调用 + */ + private static BeanResolver beanResolver = null; + + public static BeanResolver beanResolver() { + if (beanResolver == null) { + beanResolver = new BeanFactoryResolver(getBeanFactory()); + } + return beanResolver; + } + + /** + * @param expression + * @return + */ + public static Object parseExpression(String expression, Map variable) { + StandardEvaluationContext context = new StandardEvaluationContext(); + context.setBeanResolver(beanResolver()); + context.setVariables(variable); + return parser.parseExpression(expression, parserContext).getValue(context, Object.class); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + if (SpelHelper.applicationContext == null) { + SpelHelper.applicationContext = applicationContext; + } + } + + public static ListableBeanFactory getBeanFactory() { + if (null == applicationContext) { + throw new FlowException("No ConfigurableListableBeanFactory or ApplicationContext injected, maybe not in the Spring environment?"); + } else { + return applicationContext; + } + } + + +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-sb/src/main/java/org/dromara/warm/plugin/modes/sb/utils/SpringUtil.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-sb/src/main/java/org/dromara/warm/plugin/modes/sb/utils/SpringUtil.java new file mode 100644 index 0000000..8450c91 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-sb/src/main/java/org/dromara/warm/plugin/modes/sb/utils/SpringUtil.java @@ -0,0 +1,55 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.plugin.modes.sb.utils; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +/** + * @author PMB + */ +@Component("warmFlowSpringUtil") +public class SpringUtil implements ApplicationContextAware { + + private static ApplicationContext applicationContext; + + /** + * 获取applicationContext,应用上下文 + * + * @return + */ + public static ApplicationContext getApplicationContext() { + return applicationContext; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + if (SpringUtil.applicationContext == null) { + SpringUtil.applicationContext = applicationContext; + } + } + + /** + * 通过class获取Bean + */ + public static M getBean(Class clazz) { + return getApplicationContext().getBean(clazz); + } + +} + diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-solon/pom.xml b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-solon/pom.xml new file mode 100644 index 0000000..433aaf7 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-solon/pom.xml @@ -0,0 +1,45 @@ + + + 4.0.0 + + org.dromara.warm + warm-flow-plugin-modes + 1.8.4 + + + warm-flow-plugin-modes-solon + warm-flow-plugin-modes-solon + https://github.com/dromara/warm-flow + + + + org.dromara.warm + warm-flow-core + + + + org.dromara.warm + warm-flow-mybatis-core + true + + + + org.dromara.warm + warm-flow-mybatis-plus-core + true + + + + org.noear + solon + + + + org.dromara.warm + warm-flow-plugin-json + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-solon/src/main/java/org/dromara/warm/plugin/modes/solon/WarmFlowModesSolonPlugin.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-solon/src/main/java/org/dromara/warm/plugin/modes/solon/WarmFlowModesSolonPlugin.java new file mode 100644 index 0000000..8bc0e28 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-solon/src/main/java/org/dromara/warm/plugin/modes/solon/WarmFlowModesSolonPlugin.java @@ -0,0 +1,35 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.plugin.modes.solon; + +import org.dromara.warm.plugin.modes.solon.config.BeanConfig; +import org.noear.solon.core.AppContext; +import org.noear.solon.core.Plugin; + +/** + * Warm-Flow工作流插件 + * + * @author warm + */ +public class WarmFlowModesSolonPlugin implements Plugin { + + + @Override + public void start(AppContext context) { + context.beanMake(BeanConfig.class); + + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-solon/src/main/java/org/dromara/warm/plugin/modes/solon/config/BeanConfig.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-solon/src/main/java/org/dromara/warm/plugin/modes/solon/config/BeanConfig.java new file mode 100644 index 0000000..1739c45 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-solon/src/main/java/org/dromara/warm/plugin/modes/solon/config/BeanConfig.java @@ -0,0 +1,154 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.plugin.modes.solon.config; + +import org.dromara.warm.flow.core.FlowEngine; +import org.dromara.warm.flow.core.config.WarmFlow; +import org.dromara.warm.flow.core.invoker.FrameInvoker; +import org.dromara.warm.flow.core.orm.dao.*; +import org.dromara.warm.flow.core.service.*; +import org.dromara.warm.flow.core.service.impl.*; +import org.dromara.warm.flow.orm.dao.*; +import org.dromara.warm.flow.orm.entity.*; +import org.noear.solon.Solon; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; +import org.noear.solon.annotation.Inject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 工作流bean注册配置 + * + * @author warm + * @since 2023/6/5 23:01 + */ +@SuppressWarnings("rawtypes unchecked") +@Configuration +@Condition(onProperty = "${warm-flow.enabled:true} = true") +public class BeanConfig { + + private static final Logger log = LoggerFactory.getLogger(BeanConfig.class); + + @Bean(injected = true) + public FlowDefinitionDao definitionDao() { + return new FlowDefinitionDaoImpl(); + } + + @Bean + public DefService definitionService(FlowDefinitionDao definitionDao) { + return new DefServiceImpl().setDao(definitionDao); + } + + @Bean + public ChartService chartService() { + return new ChartServiceImpl(); + } + + @Bean(injected = true) + public FlowNodeDao nodeDao() { + return new FlowNodeDaoImpl(); + } + + @Bean + public NodeService nodeService(FlowNodeDao nodeDao) { + return new NodeServiceImpl().setDao(nodeDao); + } + + @Bean(injected = true) + public FlowSkipDao skipDao() { + return new FlowSkipDaoImpl(); + } + + @Bean + public SkipService skipService(FlowSkipDao skipDao) { + return new SkipServiceImpl().setDao(skipDao); + } + + @Bean(injected = true) + public FlowInstanceDao instanceDao() { + return new FlowInstanceDaoImpl(); + } + + @Bean + public InsService instanceService(FlowInstanceDao instanceDao) { + return new InsServiceImpl().setDao(instanceDao); + } + + @Bean(injected = true) + public FlowTaskDao taskDao() { + return new FlowTaskDaoImpl(); + } + + @Bean + public TaskService taskService(FlowTaskDao taskDao) { + return new TaskServiceImpl().setDao(taskDao); + } + + @Bean(injected = true) + public FlowHisTaskDao hisTaskDao() { + return new FlowHisTaskDaoImpl(); + } + + @Bean + public HisTaskService hisTaskService(FlowHisTaskDao hisTaskDao) { + return new HisTaskServiceImpl().setDao(hisTaskDao); + } + + @Bean(injected = true) + public FlowUserDao flowUserDao() { + return new FlowUserDaoImpl(); + } + + @Bean + public UserService flowUserService(FlowUserDao userDao) { + return new UserServiceImpl().setDao(userDao); + } + + @Bean + public FlowFormDao flowFormDao() { + return new FlowFormDaoImpl(); + } + + @Bean(injected = true) + public FormService flowFormService(FlowFormDao formDao) { + return new FormServiceImpl().setDao(formDao); + } + + @Bean + public WarmFlow initFlow(@Inject("${warm-flow}") WarmFlow warmFlow) { + setNewEntity(); + FrameInvoker.setCfgFunction((key) -> Solon.cfg().get(key)); + FrameInvoker.setBeanFunction(Solon.context()::getBean); + warmFlow.init(); + FlowEngine.setFlowConfig(warmFlow); + log.info("【warm-flow】,加载完成"); + return warmFlow; + } + + public void setNewEntity() { + FlowEngine.setNewDef(FlowDefinition::new); + FlowEngine.setNewIns(FlowInstance::new); + FlowEngine.setNewHisTask(FlowHisTask::new); + FlowEngine.setNewNode(FlowNode::new); + FlowEngine.setNewSkip(FlowSkip::new); + FlowEngine.setNewTask(FlowTask::new); + FlowEngine.setNewUser(FlowUser::new); + FlowEngine.setNewForm(FlowForm::new); + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-solon/src/main/resources/META-INF/solon/org.dromara.warm.plugin.modes.solon.properties b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-solon/src/main/resources/META-INF/solon/org.dromara.warm.plugin.modes.solon.properties new file mode 100644 index 0000000..ed7691a --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-modes/warm-flow-plugin-modes-solon/src/main/resources/META-INF/solon/org.dromara.warm.plugin.modes.solon.properties @@ -0,0 +1 @@ +solon.plugin=org.dromara.warm.plugin.modes.solon.WarmFlowModesSolonPlugin diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/pom.xml b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/pom.xml new file mode 100644 index 0000000..1f62732 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/pom.xml @@ -0,0 +1,24 @@ + + + 4.0.0 + + org.dromara.warm + warm-flow-plugin + 1.8.4 + + + warm-flow-plugin-ui + warm-flow-plugin-ui + pom + https://github.com/dromara/warm-flow + + + warm-flow-plugin-vue3-ui + warm-flow-plugin-ui-core + warm-flow-plugin-ui-sb-web + warm-flow-plugin-ui-solon-web + + + diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/pom.xml b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/pom.xml new file mode 100644 index 0000000..7de1b5e --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + org.dromara.warm + warm-flow-plugin-ui + 1.8.4 + + + warm-flow-plugin-ui-core + warm-flow-plugin-ui-core + https://github.com/dromara/warm-flow + + + + org.dromara.warm + warm-flow-plugin-vue3-ui + + + + org.dromara.warm + warm-flow-core + + + + + org.projectlombok + lombok + provided + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/dto/HandlerFeedBackDto.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/dto/HandlerFeedBackDto.java new file mode 100644 index 0000000..0277de4 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/dto/HandlerFeedBackDto.java @@ -0,0 +1,39 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.ui.dto; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +import java.util.List; + +/** + * 流程设计器-办理人选择 + * + * @author warm + */ +@Getter +@Setter +@Accessors(chain = true) +public class HandlerFeedBackDto { + + /** + * 入库主键集合,比如怕角色和用户id重复,可拼接为role:id + */ + private List storageIds; + +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/dto/HandlerFunDto.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/dto/HandlerFunDto.java new file mode 100644 index 0000000..c92745e --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/dto/HandlerFunDto.java @@ -0,0 +1,73 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.ui.dto; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +import java.util.List; +import java.util.function.Function; + +/** + * 办理人权限设置列表Function + * + * @author warm + */ +@Getter +@Setter +@Accessors(chain = true) +public class HandlerFunDto { + /** + * 权限列表 + */ + private List list; + + /** + * 权限列表list总数 + */ + private long total; + + /** + * 获取入库主键集合Function + */ + private Function storageId; + + /** + * 获取权限编码Function + */ + private Function handlerCode; + + /** + * 获取权限名称Function + */ + private Function handlerName; + + /** + * 获取权限分组名称Function + */ + private Function groupName; + + /** + * 获取创建时间Function + */ + private Function createTime; + + public HandlerFunDto(List list, long total) { + this.list = list; + this.total = total; + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/dto/HandlerQuery.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/dto/HandlerQuery.java new file mode 100644 index 0000000..661a3fd --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/dto/HandlerQuery.java @@ -0,0 +1,71 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.ui.dto; + +import lombok.Getter; +import lombok.Setter; + +/** + * 流程设计器-办理人权限设置列表查询参数 + * 办理人权限列表选择框,可能存在多个,比如:部门、角色、用户的情况 + * + * @author warm + */ +@Getter +@Setter +public class HandlerQuery { + + /** + * 权限编码,如:zhang、roleAdmin、deptAdmin等编码 + */ + private String handlerCode; + + /** + * 权限名称,如:管理员、角色管理员、部门管理员等名称 + */ + private String handlerName; + + /** + * 办理权限类型,比如用户/角色/部门等 + */ + private String handlerType; + + /** + * 页面左侧树权限分组主键,如:角色、部门等主键 + */ + private String groupId; + + /** + * 当前页码 + */ + private Integer pageNum; + + /** + * 每页显示条数 + */ + private Integer pageSize; + + /** + * 开始时间 + */ + private String beginTime; + + /** + * 结束时间 + */ + private String endTime; + +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/dto/TreeFunDto.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/dto/TreeFunDto.java new file mode 100644 index 0000000..87b1ee4 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/dto/TreeFunDto.java @@ -0,0 +1,59 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.ui.dto; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +import java.util.List; +import java.util.function.Function; + +/** + * 页面左侧树列表Function + * + * @author warm + */ +@Getter +@Setter +@Accessors(chain = true) +public class TreeFunDto { + + /** + * 左侧树列表 + */ + private List list; + + /** + * 获取左侧树ID Function + */ + private Function id; + + /** + * 获取左侧树名称 Function + */ + private Function name; + + /** + * 获取左侧树父级ID Function + */ + private Function parentId; + + public TreeFunDto(List list) { + this.list = list; + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/service/CategoryService.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/service/CategoryService.java new file mode 100644 index 0000000..3593a92 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/service/CategoryService.java @@ -0,0 +1,36 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.ui.service; + +import org.dromara.warm.flow.core.dto.Tree; + +import java.util.List; + +/** + * 分类接口 + * + * @author warm + * @since 2025/6/24 + */ +public interface CategoryService { + + /** + * 查询分类 + * + * @return 分类 + */ + List queryCategory(); +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/service/ChartExtService.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/service/ChartExtService.java new file mode 100644 index 0000000..45d8722 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/service/ChartExtService.java @@ -0,0 +1,93 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.ui.service; + +import org.dromara.warm.flow.core.dto.DefJson; +import org.dromara.warm.flow.core.dto.PromptContent; +import org.dromara.warm.flow.core.enums.NodeType; +import org.dromara.warm.flow.core.utils.MapUtil; + +import java.util.ArrayList; +import java.util.List; + +/** + * 流程图提示信息 + * + * @author warm + */ +public interface ChartExtService { + + + /** + * 设置流程图提示信息 + * + * @param defJson 流程定义json对象 + */ + void execute(DefJson defJson); + + /** + * 初始化流程图提示信息 + * + * @param defJson 流程定义json对象 + */ + default void initPromptContent(DefJson defJson) { + defJson.setTopText("流程名称: " + defJson.getFlowName()); + defJson.getNodeList().forEach(nodeJson -> { + // 提示信息主对象 + PromptContent promptContent = new PromptContent(); + + if (NodeType.isGateWay(nodeJson.getNodeType())) { + return; + } + + // 设置 dialogStyle 样式 + promptContent.setDialogStyle(MapUtil.mergeAll( + "position", "absolute", + "backgroundColor", "#fff", + "border", "1px solid #ccc", + "borderRadius", "4px", + "boxShadow", "0 2px 8px rgba(0, 0, 0, 0.15)", + "padding", "8px 12px", + "fontSize", "14px", + "zIndex", 1000, + "maxWidth", "500px", + "color", "#333" + )); + + // 创建 info 列表 + List infoList = new ArrayList<>(); + + // 添加第一个条目: 任务名称 + PromptContent.InfoItem item = new PromptContent.InfoItem() + .setPrefix("任务名称: ") + .setContent(nodeJson.getNodeName()) + .setContentStyle(MapUtil.mergeAll("border", "1px solid #d1e9ff", + "backgroundColor", "#e8f4ff", + "padding", "4px 8px", + "borderRadius", "4px" + )) + .setRowStyle(MapUtil.mergeAll("fontWeight", "bold", + "margin", "0 0 6px 0", + "padding", "0 0 8px 0", + "borderBottom", "1px solid #ccc" + )); + infoList.add(item); + promptContent.setInfo(infoList); + + nodeJson.setPromptContent(promptContent); + }); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/service/FormPathService.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/service/FormPathService.java new file mode 100644 index 0000000..e1a7099 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/service/FormPathService.java @@ -0,0 +1,36 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.ui.service; + +import org.dromara.warm.flow.core.dto.Tree; + +import java.util.List; + +/** + * 自定义表单路径接口 + * + * @author warm + * @since 2025/10/22 + */ +public interface FormPathService { + + /** + * 查询自定义表单路径 + * + * @return 自定义表单路径 + */ + List queryFormPath(); +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/service/HandlerDictService.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/service/HandlerDictService.java new file mode 100644 index 0000000..97b8301 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/service/HandlerDictService.java @@ -0,0 +1,35 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.ui.service; + +import org.dromara.warm.flow.ui.vo.Dict; + +import java.util.List; + +/** + * 流程设计器-获取办理人选择项 + * + * @author warm + */ +public interface HandlerDictService { + + /** + * 获取办理人选择项 + * + * @return 结果 + */ + List getHandlerDict(); +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/service/HandlerSelectService.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/service/HandlerSelectService.java new file mode 100644 index 0000000..b80632a --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/service/HandlerSelectService.java @@ -0,0 +1,128 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.ui.service; + +import org.dromara.warm.flow.core.dto.FlowPage; +import org.dromara.warm.flow.core.dto.Tree; +import org.dromara.warm.flow.core.utils.*; +import org.dromara.warm.flow.ui.dto.HandlerFunDto; +import org.dromara.warm.flow.ui.dto.HandlerQuery; +import org.dromara.warm.flow.ui.dto.TreeFunDto; +import org.dromara.warm.flow.ui.utils.TreeUtil; +import org.dromara.warm.flow.ui.vo.HandlerAuth; +import org.dromara.warm.flow.ui.vo.HandlerFeedBackVo; +import org.dromara.warm.flow.ui.vo.HandlerSelectVo; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 流程设计器-获取办理人权限设置列表接口 + * + * @author warm + */ +public interface HandlerSelectService { + + /** + * 获取办理人权限设置列表tabs页签,如:用户、角色和部门等,可以返回其中一种或者多种,按业务需求决定 + * + * @return tabs页签 + */ + List getHandlerType(); + + /** + * 获取用户列表、角色列表、部门列表等,可以返回其中一种或者多种,按业务需求决定 + * + * @param query 查询参数 + * @return 结果 + */ + HandlerSelectVo getHandlerSelect(HandlerQuery query); + + /** + * 办理人权限名称回显,兼容老项目,新项目重写提高性能 + * + * @param storageIds 入库主键集合 + * @return 结果 + */ + default List handlerFeedback(List storageIds) { + List handlerFeedBackVos = new ArrayList<>(); + if (CollUtil.isEmpty(storageIds)) { + return handlerFeedBackVos; + } + Map authMap = new HashMap<>(); + List handlerTypes = getHandlerType(); + if (CollUtil.isEmpty(handlerTypes)) { + return handlerFeedBackVos; + } + for (String handlerType : handlerTypes) { + HandlerQuery handlerQuery = new HandlerQuery(); + handlerQuery.setHandlerType(handlerType); + HandlerSelectVo handlerSelectVo = getHandlerSelect(handlerQuery); + if (ObjectUtil.isNotNull(handlerSelectVo)) { + FlowPage handlerAuths = handlerSelectVo.getHandlerAuths(); + List rows = handlerAuths.getRows(); + if (CollUtil.isNotEmpty(rows)) { + authMap.putAll(StreamUtils.toMap(rows, HandlerAuth::getStorageId, HandlerAuth::getHandlerName)); + } + } + } + + // 遍历storageIds,按照原本的顺序回显名称 + for (String storageId : storageIds) { + handlerFeedBackVos.add(new HandlerFeedBackVo(storageId + , MapUtil.isEmpty(authMap) ? "" : authMap.get(storageId))); + } + return handlerFeedBackVos; + } + + default HandlerSelectVo getHandlerSelectVo(HandlerFunDto handlerFunDto) { + List handlerAuths = new ArrayList<>(); + // 遍历角色数据,封装为组件可识别的数据 + for (T obj : handlerFunDto.getList()) { + handlerAuths.add(new HandlerAuth() + .setStorageId(handlerFunDto.getStorageId() == null ? null : handlerFunDto.getStorageId().apply(obj)) + .setHandlerCode(handlerFunDto.getHandlerCode() == null ? null : handlerFunDto.getHandlerCode().apply(obj)) + .setHandlerName(handlerFunDto.getHandlerName() == null ? null : handlerFunDto.getHandlerName().apply(obj)) + .setCreateTime(handlerFunDto.getCreateTime() == null ? null : handlerFunDto.getCreateTime().apply(obj)) + .setGroupName(handlerFunDto.getGroupName() == null ? null : handlerFunDto.getGroupName().apply(obj))); + } + return getResult(handlerAuths, handlerFunDto.getTotal()); + } + + default HandlerSelectVo getHandlerSelectVo(HandlerFunDto handlerFunDto, TreeFunDto treeFunDto) { + HandlerSelectVo handlerSelectVo = getHandlerSelectVo(handlerFunDto); + + List treeList = StreamUtils.toList(treeFunDto.getList(), org -> + new Tree().setId(treeFunDto.getId() == null ? null : treeFunDto.getId().apply(org)) + .setName(treeFunDto.getName() == null ? null : treeFunDto.getName().apply(org)) + .setParentId(treeFunDto.getParentId() == null ? null : treeFunDto.getParentId().apply(org))); + + // 通过递归,构建树状结构 + return handlerSelectVo.setTreeSelections(TreeUtil.buildTree(treeList)); + } + + + default HandlerSelectVo getResult(List handlerAuths, long total) { + return new HandlerSelectVo().setHandlerAuths(new FlowPage() + .setCode(HttpStatus.SUCCESS) + .setMsg("查询成功") + .setRows(handlerAuths) + .setTotal(total)); + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/service/NodeExtService.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/service/NodeExtService.java new file mode 100644 index 0000000..bd766b2 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/service/NodeExtService.java @@ -0,0 +1,35 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.ui.service; + +import org.dromara.warm.flow.ui.vo.NodeExt; + +import java.util.List; + +/** + * 流程设计器-节点扩展属性 + * + * @author warm + */ +public interface NodeExtService { + + /** + * 获取节点扩展属性 + * + * @return 结果 + */ + List getNodeExt(); +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/service/WarmFlowService.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/service/WarmFlowService.java new file mode 100644 index 0000000..74cfed4 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/service/WarmFlowService.java @@ -0,0 +1,355 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.ui.service; + +import lombok.extern.slf4j.Slf4j; +import org.dromara.warm.flow.core.FlowEngine; +import org.dromara.warm.flow.core.config.WarmFlow; +import org.dromara.warm.flow.core.dto.*; +import org.dromara.warm.flow.core.entity.Form; +import org.dromara.warm.flow.core.entity.Instance; +import org.dromara.warm.flow.core.enums.FormCustomEnum; +import org.dromara.warm.flow.core.enums.ModelEnum; +import org.dromara.warm.flow.core.exception.FlowException; +import org.dromara.warm.flow.core.invoker.FrameInvoker; +import org.dromara.warm.flow.core.utils.CollUtil; +import org.dromara.warm.flow.core.utils.ExceptionUtil; +import org.dromara.warm.flow.core.utils.StreamUtils; +import org.dromara.warm.flow.core.utils.StringUtils; +import org.dromara.warm.flow.ui.dto.HandlerFeedBackDto; +import org.dromara.warm.flow.ui.dto.HandlerQuery; +import org.dromara.warm.flow.ui.utils.TreeUtil; +import org.dromara.warm.flow.ui.vo.*; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * 设计器Controller 可选择是否放行,放行可与业务系统共享权限,主要是用来访问业务系统数据 + * + * @author warm + */ +@Slf4j +public class WarmFlowService { + + /** + * 返回流程定义的配置 + * + * @return ApiResult + */ + public static ApiResult config() { + WarmFlowVo warmFlowVo = new WarmFlowVo(); + WarmFlow warmFlow = FlowEngine.getFlowConfig(); + // 获取tokenName + String tokenName = warmFlow.getTokenName(); + if (StringUtils.isEmpty(tokenName)) { + return ApiResult.fail("未配置tokenName"); + } + String[] tokenNames = tokenName.split(","); + List tokenNameList = Arrays.stream(tokenNames).filter(StringUtils::isNotEmpty) + .map(String::trim).collect(Collectors.toList()); + warmFlowVo.setTokenNameList(tokenNameList); + + return ApiResult.ok(warmFlowVo); + } + + /** + * 保存流程json字符串 + * + * @param defJson 流程数据集合 + * @param onlyNodeSkip 是否只保存节点和跳转 + * @return ApiResult + * @throws Exception 异常 + * @author xiarg + * @since 2024/10/29 16:31 + */ + public static ApiResult saveJson(DefJson defJson, boolean onlyNodeSkip) throws Exception { + FlowEngine.defService().saveDef(defJson, onlyNodeSkip); + return ApiResult.ok(); + } + + /** + * 获取流程定义数据(包含节点和跳转) + * + * @param id 流程定义id + * @return ApiResult + * @author xiarg + * @since 2024/10/29 16:31 + */ + public static ApiResult queryDef(Long id) { + try { + DefJson defJson; + if (id == null) { + defJson = new DefJson() + .setModelValue(ModelEnum.CLASSICS.name()) + .setFormCustom(FormCustomEnum.N.name()); + } else { + defJson = FlowEngine.defService().queryDesign(id); + } + CategoryService categoryService = FrameInvoker.getBean(CategoryService.class); + if (categoryService != null) { + List treeList = categoryService.queryCategory(); + defJson.setCategoryList(TreeUtil.buildTree(treeList)); + } + FormPathService formPathService = FrameInvoker.getBean(FormPathService.class); + if (formPathService != null) { + List treeList = formPathService.queryFormPath(); + defJson.setFormPathList(TreeUtil.buildTree(treeList)); + if (id == null) { + defJson.setFormCustom(FormCustomEnum.Y.name()); + } + } + return ApiResult.ok(defJson); + } catch (Exception e) { + log.error("获取流程json字符串", e); + throw new FlowException(ExceptionUtil.handleMsg("获取流程json字符串失败", e)); + } + } + + /** + * 获取流程图 + * + * @param id 流程实例id + * @return ApiResult + */ + public static ApiResult queryFlowChart(Long id) { + try { + Instance instance = FlowEngine.insService().getById(id); + String defJsonStr = instance.getDefJson(); + DefJson defJson = FlowEngine.jsonConvert.strToBean(defJsonStr, DefJson.class); + defJson.setInstance(instance); + + // 获取流程图三原色 + defJson.setChartStatusColor(FlowEngine.chartService().getChartRgb(defJson.getModelValue())); + // 是否显示流程图顶部文字 + defJson.setTopTextShow(FlowEngine.getFlowConfig().isTopTextShow()); + // 需要业务系统实现该接口 + ChartExtService chartExtService = FrameInvoker.getBean(ChartExtService.class); + if (chartExtService != null) { + chartExtService.initPromptContent(defJson); + chartExtService.execute(defJson); + } + + return ApiResult.ok(defJson); + } catch (Exception e) { + log.error("获取流程图", e); + throw new FlowException(ExceptionUtil.handleMsg("获取流程图失败", e)); + } + } + + /** + * 办理人权限设置列表tabs页签 + * + * @return List + */ + public static ApiResult> handlerType() { + try { + // 需要业务系统实现该接口 + HandlerSelectService handlerSelectService = FrameInvoker.getBean(HandlerSelectService.class); + if (handlerSelectService == null) { + return ApiResult.ok(Collections.emptyList()); + } + List handlerType = handlerSelectService.getHandlerType(); + return ApiResult.ok(handlerType); + } catch (Exception e) { + log.error("办理人权限设置列表tabs页签异常", e); + throw new FlowException(ExceptionUtil.handleMsg("办理人权限设置列表tabs页签失败", e)); + } + } + + /** + * 办理人权限设置列表结果 + * + * @return HandlerSelectVo + */ + public static ApiResult handlerResult(HandlerQuery query) { + try { + // 需要业务系统实现该接口 + HandlerSelectService handlerSelectService = FrameInvoker.getBean(HandlerSelectService.class); + if (handlerSelectService == null) { + return ApiResult.ok(new HandlerSelectVo()); + } + HandlerSelectVo handlerSelectVo = handlerSelectService.getHandlerSelect(query); + return ApiResult.ok(handlerSelectVo); + } catch (Exception e) { + log.error("办理人权限设置列表结果异常", e); + throw new FlowException(ExceptionUtil.handleMsg("办理人权限设置列表结果失败", e)); + } + } + + /** + * 办理人权限名称回显 + * + * @return HandlerSelectVo + */ + public static ApiResult> handlerFeedback(HandlerFeedBackDto handlerFeedBackDto) { + try { + // 需要业务系统实现该接口 + HandlerSelectService handlerSelectService = FrameInvoker.getBean(HandlerSelectService.class); + if (handlerSelectService == null) { + List handlerFeedBackVos = StreamUtils.toList(handlerFeedBackDto.getStorageIds(), + storageId -> new HandlerFeedBackVo(storageId, null)); + return ApiResult.ok(handlerFeedBackVos); + } + List handlerFeedBackVos = handlerSelectService.handlerFeedback(handlerFeedBackDto.getStorageIds()); + return ApiResult.ok(handlerFeedBackVos); + } catch (Exception e) { + log.error("办理人权限名称回显", e); + throw new FlowException(ExceptionUtil.handleMsg("办理人权限名称回显", e)); + } + } + + /** + * 办理人选择项 + * + * @return List + */ + public static ApiResult> handlerDict() { + try { + // 需要业务系统实现该接口 + HandlerDictService handlerDictService = FrameInvoker.getBean(HandlerDictService.class); + if (handlerDictService == null) { + List dictList = new ArrayList<>(); + Dict dict = new Dict(); + dict.setLabel("默认表达式"); + dict.setValue("${handler}"); + Dict dict1 = new Dict(); + dict1.setLabel("spel表达式"); + dict1.setValue("#{@user.evalVar(#handler)}"); + Dict dict2 = new Dict(); + dict2.setLabel("其他"); + dict2.setValue(""); + dictList.add(dict); + dictList.add(dict1); + dictList.add(dict2); + + return ApiResult.ok(dictList); + } + return ApiResult.ok(handlerDictService.getHandlerDict()); + } catch (Exception e) { + log.error("办理人权限设置列表结果异常", e); + throw new FlowException(ExceptionUtil.handleMsg("办理人权限设置列表结果失败", e)); + } + } + + /** + * 已发布表单列表 该接口不需要业务系统实现 + */ + public static ApiResult> publishedForm() { + try { + return ApiResult.ok(FlowEngine.formService().list(FlowEngine.newForm().setIsPublish(1))); + } catch (Exception e) { + log.error("已发布表单列表异常", e); + throw new FlowException(ExceptionUtil.handleMsg("已发布表单列表异常", e)); + } + } + + /** + * 读取表单内容 + * + * @param id + * @return + */ + public static ApiResult getFormContent(Long id) { + try { + return ApiResult.ok(FlowEngine.formService().getById(id).getFormContent()); + } catch (Exception e) { + log.error("获取表单内容字符串", e); + throw new FlowException(ExceptionUtil.handleMsg("获取表单内容字符串失败", e)); + } + } + + /** + * 保存表单内容,该接口不需要系统实现 + * + * @param flowDto + * @return + */ + public static ApiResult saveFormContent(FlowDto flowDto) { + FlowEngine.formService().saveContent(flowDto.getId(), flowDto.getFormContent()); + return ApiResult.ok(); + } + + + /** + * 根据任务id获取待办任务表单及数据 + * + * @param taskId 当前任务id + * @return {@link ApiResult} + * @author liangli + * @date 2024/8/21 17:08 + **/ + public static ApiResult load(Long taskId) { + FlowParams flowParams = FlowParams.build(); + + return ApiResult.ok(FlowEngine.taskService().load(taskId, flowParams)); + } + + /** + * 根据任务id获取已办任务表单及数据 + * + * @param hisTaskId + * @return + */ + public static ApiResult hisLoad(Long hisTaskId) { + FlowParams flowParams = FlowParams.build(); + + return ApiResult.ok(FlowEngine.taskService().hisLoad(hisTaskId, flowParams)); + } + + /** + * 通用表单流程审批接口 + * + * @param formData + * @param taskId + * @param skipType + * @param message + * @param nodeCode + * @return + */ + public static ApiResult handle(Map formData, Long taskId, String skipType + , String message, String nodeCode) { + FlowParams flowParams = FlowParams.build() + .skipType(skipType) + .nodeCode(nodeCode) + .message(message); + + flowParams.formData(formData); + + return ApiResult.ok(FlowEngine.taskService().skip(taskId, flowParams)); + } + + /** + * 获取节点扩展属性 + * + * @return List + */ + public static ApiResult> nodeExt() { + try { + // 需要业务系统实现该接口 + NodeExtService nodeExtService = FrameInvoker.getBean(NodeExtService.class); + if (nodeExtService == null) { + return ApiResult.ok(Collections.emptyList()); + } + List nodeExts = nodeExtService.getNodeExt(); + return ApiResult.ok(nodeExts); + } catch (Exception e) { + log.error("获取节点扩展属性", e); + throw new FlowException(ExceptionUtil.handleMsg("获取节点扩展属性失败", e)); + } + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/utils/TreeUtil.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/utils/TreeUtil.java new file mode 100644 index 0000000..0d79096 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/utils/TreeUtil.java @@ -0,0 +1,84 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.ui.utils; + +import org.dromara.warm.flow.core.dto.Tree; +import org.dromara.warm.flow.core.utils.StringUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class TreeUtil { + private TreeUtil() { + } + + /** + * 构建所需要树结构 + * + * @param trees 部门列表 + * @return 树结构列表 + */ + public static List buildTree(List trees) { + List returnList = new ArrayList<>(); + List tempList = trees.stream().map(Tree::getId).collect(Collectors.toList()); + for (Tree dept : trees) { + // 如果是顶级节点, 遍历该父节点的所有子节点 + if (!tempList.contains(dept.getParentId())) { + recursionFn(trees, dept); + returnList.add(dept); + } + } + if (returnList.isEmpty()) { + returnList = trees; + } + return returnList; + } + + /** + * 递归列表 + */ + private static void recursionFn(List list, Tree t) { + // 得到子节点列表 + List childList = getChildList(list, t); + t.setChildren(childList); + for (Tree tChild : childList) { + if (hasChild(list, tChild)) { + recursionFn(list, tChild); + } + } + } + + /** + * 判断是否有子节点 + */ + private static boolean hasChild(List list, Tree t) { + return !getChildList(list, t).isEmpty(); + } + + /** + * 得到子节点列表 + */ + private static List getChildList(List list, Tree t) { + List tlist = new ArrayList<>(); + for (Tree n : list) { + if (StringUtils.isNotEmpty(n.getParentId()) && n.getParentId().equals(t.getId())) { + tlist.add(n); + } + } + return tlist; + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/vo/Dict.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/vo/Dict.java new file mode 100644 index 0000000..8c93929 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/vo/Dict.java @@ -0,0 +1,46 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.ui.vo; + +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; +import java.util.List; + +/** + * 字典 + * + * @author warm + */ +@Getter +@Setter +public class Dict implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 字典标签 + */ + private String label; + + /** + * 字典值 + */ + private String value; + + private List childList; + +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/vo/HandlerAuth.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/vo/HandlerAuth.java new file mode 100644 index 0000000..19a3454 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/vo/HandlerAuth.java @@ -0,0 +1,58 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.ui.vo; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +/** + * 流程设计器-办理人权限设置列表 + * 办理人权限列表选择框,可能存在多个,比如:部门、角色、用户的情况 + * + * @author warm + */ +@Getter +@Setter +@Accessors(chain = true) +public class HandlerAuth { + + /** + * 入库主键,比如怕角色和用户id重复,可拼接为role:id + */ + private String storageId; + + /** + * 权限编码,如:zhang、roleAdmin、deptAdmin等编码 + */ + private String handlerCode; + + /** + * 权限名称,如:管理员、角色管理员、部门管理员等名称 + */ + private String handlerName; + + /** + * 权限分组名称,如:角色、部门等名称 + */ + private String groupName; + + /** + * 创建时间 + */ + private String createTime; + +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/vo/HandlerFeedBackVo.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/vo/HandlerFeedBackVo.java new file mode 100644 index 0000000..82da45b --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/vo/HandlerFeedBackVo.java @@ -0,0 +1,45 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.ui.vo; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.Accessors; + +/** + * 流程设计器-办理人选择 + * + * @author warm + */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Accessors(chain = true) +public class HandlerFeedBackVo { + + /** + * 入库主键,比如怕角色和用户id重复,可拼接为role:id + */ + private String storageId; + + /** + * 权限名称,如:管理员、角色管理员、部门管理员等名称 + */ + private String handlerName; +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/vo/HandlerSelectVo.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/vo/HandlerSelectVo.java new file mode 100644 index 0000000..0b9874e --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/vo/HandlerSelectVo.java @@ -0,0 +1,46 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.ui.vo; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.dromara.warm.flow.core.dto.FlowPage; +import org.dromara.warm.flow.core.dto.Tree; + +import java.util.List; + +/** + * 流程设计器-办理人选择 + * + * @author warm + */ +@Getter +@Setter +@Accessors(chain = true) +public class HandlerSelectVo { + + /** + * 办理人选择,分页列表,有具体办理对象 比如:部门、角色和用户等情况 + */ + private FlowPage handlerAuths; + + /** + * 左侧树状选择,配合{@link #handlerAuths}使用,比如用户先选择部门,然后选择用户 + */ + private List treeSelections; + +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/vo/NodeExt.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/vo/NodeExt.java new file mode 100644 index 0000000..8294160 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/vo/NodeExt.java @@ -0,0 +1,75 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.ui.vo; + +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; +import java.util.List; + +/** + * 节点扩展属性 + * + * @author warm + * @since 2025/2/18 + */ +@Getter +@Setter +public class NodeExt implements Serializable { + private static final long serialVersionUID = 1L; + private String code; + private String name; + private String desc; + private int type; + private List childs; + + @Getter + @Setter + public static class ChildNode { + private String code; + private String desc; + private String label; + private int type; + private boolean must; + private boolean multiple; + private List dict; + + } + + @Getter + @Setter + public static class DictItem { + private String label; + private String value; + private boolean selected; + + public DictItem() { + } + + public DictItem(String label, String value) { + this.label = label; + this.value = value; + } + + public DictItem(String label, String value, boolean selected) { + this.label = label; + this.value = value; + this.selected = selected; + } + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/vo/WarmFlowVo.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/vo/WarmFlowVo.java new file mode 100644 index 0000000..3706ab7 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-core/src/main/java/org/dromara/warm/flow/ui/vo/WarmFlowVo.java @@ -0,0 +1,39 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.ui.vo; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +import java.util.List; + +/** + * 流程配置vo + * + * @author warm + */ +@Getter +@Setter +@Accessors(chain = true) +public class WarmFlowVo { + + /** + * 如果需要工作流共享业务系统权限,默认Authorization + */ + private List tokenNameList; + +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-sb-web/pom.xml b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-sb-web/pom.xml new file mode 100644 index 0000000..cf19106 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-sb-web/pom.xml @@ -0,0 +1,38 @@ + + + 4.0.0 + + org.dromara.warm + warm-flow-plugin-ui + 1.8.4 + + + warm-flow-plugin-ui-sb-web + warm-flow-plugin-ui-sb-web + https://github.com/dromara/warm-flow + + + + org.dromara.warm + warm-flow-plugin-modes-sb + + + + org.dromara.warm + warm-flow-plugin-ui-core + + + + org.springframework + spring-webmvc + + + + org.springframework + spring-tx + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-sb-web/src/main/java/org/dromara/warm/flow/ui/config/WarmFlowUiConfig.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-sb-web/src/main/java/org/dromara/warm/flow/ui/config/WarmFlowUiConfig.java new file mode 100644 index 0000000..31b3a82 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-sb-web/src/main/java/org/dromara/warm/flow/ui/config/WarmFlowUiConfig.java @@ -0,0 +1,43 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.ui.config; + +import org.dromara.warm.flow.ui.controller.WarmFlowController; +import org.dromara.warm.flow.ui.controller.WarmFlowUiController; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * 工作流设计器配置类 + * + * @author ruoyi + */ +@Configuration +@ConditionalOnProperty(value = "warm-flow.ui", havingValue = "true", matchIfMissing = true) +@Import({WarmFlowUiController.class + , WarmFlowController.class}) +public class WarmFlowUiConfig implements WebMvcConfigurer { + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + /** warm-flow配置 */ + registry.addResourceHandler("/warm-flow-ui/**") + .addResourceLocations("classpath:/META-INF/resources/warm-flow-ui/", "classpath:/warm-flow-ui/"); + + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-sb-web/src/main/java/org/dromara/warm/flow/ui/controller/WarmFlowController.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-sb-web/src/main/java/org/dromara/warm/flow/ui/controller/WarmFlowController.java new file mode 100644 index 0000000..31fbd45 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-sb-web/src/main/java/org/dromara/warm/flow/ui/controller/WarmFlowController.java @@ -0,0 +1,209 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.ui.controller; + +import org.dromara.warm.flow.core.dto.ApiResult; +import org.dromara.warm.flow.core.dto.DefJson; +import org.dromara.warm.flow.core.dto.FlowDto; +import org.dromara.warm.flow.core.entity.Form; +import org.dromara.warm.flow.core.entity.Instance; +import org.dromara.warm.flow.ui.dto.HandlerFeedBackDto; +import org.dromara.warm.flow.ui.dto.HandlerQuery; +import org.dromara.warm.flow.ui.service.WarmFlowService; +import org.dromara.warm.flow.ui.vo.Dict; +import org.dromara.warm.flow.ui.vo.HandlerFeedBackVo; +import org.dromara.warm.flow.ui.vo.HandlerSelectVo; +import org.dromara.warm.flow.ui.vo.NodeExt; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +/** + * 设计器Controller 可选择是否放行,放行可与业务系统共享权限,主要是用来访问业务系统数据 + * + * @author warm + */ +@RestController +@RequestMapping("/warm-flow") +public class WarmFlowController { + + /** + * 保存流程json字符串 + * + * @param defJson 流程数据集合 + * @return ApiResult + * @throws Exception 异常 + * @author xiarg + * @since 2024/10/29 16:31 + */ + @PostMapping("/save-json") + @Transactional(rollbackFor = Exception.class) + public ApiResult saveJson(@RequestBody DefJson defJson, @RequestHeader("onlyNodeSkip") boolean onlyNodeSkip) throws Exception { + return WarmFlowService.saveJson(defJson, onlyNodeSkip); + } + + /** + * 获取流程定义数据(包含节点和跳转) + * + * @param id 流程定义id + * @return ApiResult + * @author xiarg + * @since 2024/10/29 16:31 + */ + @GetMapping(value = {"/query-def", "/query-def/{id}"}) + public ApiResult queryDef(@PathVariable(value = "id", required = false) Long id) { + return WarmFlowService.queryDef(id); + } + + /** + * 获取流程图 + * + * @param id 流程实例id + * @return ApiResult + */ + @GetMapping("/query-flow-chart/{id}") + public ApiResult queryFlowChart(@PathVariable("id") Long id) { + return WarmFlowService.queryFlowChart(id); + } + + /** + * 办理人权限设置列表tabs页签 + * + * @return List + */ + @GetMapping("/handler-type") + public ApiResult> handlerType() { + return WarmFlowService.handlerType(); + } + + /** + * 办理人权限设置列表结果 + * + * @return HandlerSelectVo + */ + @GetMapping("/handler-result") + public ApiResult handlerResult(HandlerQuery query) { + return WarmFlowService.handlerResult(query); + } + + /** + * 办理人权限名称回显 + * + * @return HandlerSelectVo + */ + @GetMapping("/handler-feedback") + public ApiResult> handlerFeedback(HandlerFeedBackDto handlerFeedBackDto) { + return WarmFlowService.handlerFeedback(handlerFeedBackDto); + } + + /** + * 办理人选择项 + * + * @return List + */ + @GetMapping("/handler-dict") + public ApiResult> handlerDict() { + return WarmFlowService.handlerDict(); + } + + /** + * 已发布表单列表 该接口不需要业务系统实现 + */ + @Transactional(rollbackFor = Exception.class) + @GetMapping("/published-form") + public ApiResult> publishedForm() { + return WarmFlowService.publishedForm(); + } + + /** + * 读取表单内容 + * + * @param id + * @return + */ + @GetMapping("/form-content/{id}") + public ApiResult getFormContent(@PathVariable("id") Long id) { + return WarmFlowService.getFormContent(id); + } + + /** + * 保存表单内容,该接口不需要系统实现 + * + * @param flowDto + * @return + */ + @Transactional(rollbackFor = Exception.class) + @PostMapping("/form-content") + public ApiResult saveFormContent(@RequestBody FlowDto flowDto) { + return WarmFlowService.saveFormContent(flowDto); + } + + + /** + * 根据任务id获取待办任务表单及数据 + * + * @param taskId 当前任务id + * @return {@link ApiResult< FlowDto >} + * @author liangli + * @date 2024/8/21 17:08 + **/ + @GetMapping(value = "/execute/load/{taskId}") + public ApiResult load(@PathVariable("taskId") Long taskId) { + return WarmFlowService.load(taskId); + } + + /** + * 根据任务id获取已办任务表单及数据 + * + * @param hisTaskId + * @return + */ + @GetMapping(value = "/execute/hisLoad/{taskId}") + public ApiResult hisLoad(@PathVariable("taskId") Long hisTaskId) { + return WarmFlowService.hisLoad(hisTaskId); + } + + /** + * 通用表单流程审批接口 + * + * @param formData + * @param taskId + * @param skipType + * @param message + * @param nodeCode + * @return + */ + @Transactional(rollbackFor = Exception.class) + @PostMapping(value = "/execute/handle") + public ApiResult handle(@RequestBody Map formData, @RequestParam("taskId") Long taskId + , @RequestParam("skipType") String skipType, @RequestParam("message") String message + , @RequestParam(value = "nodeCode", required = false) String nodeCode) { + return WarmFlowService.handle(formData, taskId, skipType, message, nodeCode); + } + + /** + * 获取节点扩展属性 + * + * @return List + */ + @GetMapping("/node-ext") + public ApiResult> nodeExt() { + return WarmFlowService.nodeExt(); + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-sb-web/src/main/java/org/dromara/warm/flow/ui/controller/WarmFlowUiController.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-sb-web/src/main/java/org/dromara/warm/flow/ui/controller/WarmFlowUiController.java new file mode 100644 index 0000000..22a243a --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-sb-web/src/main/java/org/dromara/warm/flow/ui/controller/WarmFlowUiController.java @@ -0,0 +1,44 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.ui.controller; + +import org.dromara.warm.flow.core.dto.ApiResult; +import org.dromara.warm.flow.ui.service.WarmFlowService; +import org.dromara.warm.flow.ui.vo.WarmFlowVo; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 设计器Controller 匿名访问 + * + * @author warm + */ +@RestController +@RequestMapping("/warm-flow-ui") +public class WarmFlowUiController { + + /** + * 返回流程定义的配置 + * + * @return ApiResult + */ + @GetMapping("/config") + public ApiResult config() { + return WarmFlowService.config(); + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-sb-web/src/main/resources/META-INF/spring.factories b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-sb-web/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..c40394e --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-sb-web/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + org.dromara.warm.flow.ui.config.WarmFlowUiConfig diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-sb-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-sb-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..73a081b --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-sb-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +org.dromara.warm.flow.ui.config.WarmFlowUiConfig diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-solon-web/pom.xml b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-solon-web/pom.xml new file mode 100644 index 0000000..eda258d --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-solon-web/pom.xml @@ -0,0 +1,38 @@ + + + 4.0.0 + + org.dromara.warm + warm-flow-plugin-ui + 1.8.4 + + + warm-flow-plugin-ui-solon-web + warm-flow-plugin-ui-solon-web + https://github.com/dromara/warm-flow + + + + org.dromara.warm + warm-flow-plugin-modes-solon + + + + org.dromara.warm + warm-flow-plugin-ui-core + + + + org.noear + solon-data + + + + org.noear + solon-web-staticfiles + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-solon-web/src/main/java/org/dromara/warm/flow/ui/WarmFlowUiSolonPlugin.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-solon-web/src/main/java/org/dromara/warm/flow/ui/WarmFlowUiSolonPlugin.java new file mode 100644 index 0000000..ccce90f --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-solon-web/src/main/java/org/dromara/warm/flow/ui/WarmFlowUiSolonPlugin.java @@ -0,0 +1,41 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.ui; + +import org.dromara.warm.flow.core.config.WarmFlow; +import org.noear.solon.core.AppContext; +import org.noear.solon.core.Plugin; +import org.noear.solon.web.staticfiles.StaticMappings; +import org.noear.solon.web.staticfiles.repository.ClassPathStaticRepository; + +/** + * Warm-Flow工作流插件 + * + * @author warm + */ +public class WarmFlowUiSolonPlugin implements Plugin { + @Override + public void start(AppContext context) { + context.beanScan(WarmFlowUiSolonPlugin.class); + + context.getBeanAsync(WarmFlow.class, warmFlow -> { + if (warmFlow.isUi()) { + StaticMappings.add("/warm-flow-ui/" + , new ClassPathStaticRepository("/META-INF/resources/warm-flow-ui/")); + } + }); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-solon-web/src/main/java/org/dromara/warm/flow/ui/controller/WarmFlowController.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-solon-web/src/main/java/org/dromara/warm/flow/ui/controller/WarmFlowController.java new file mode 100644 index 0000000..bdcc776 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-solon-web/src/main/java/org/dromara/warm/flow/ui/controller/WarmFlowController.java @@ -0,0 +1,235 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.ui.controller; + +import org.dromara.warm.flow.core.dto.ApiResult; +import org.dromara.warm.flow.core.dto.DefJson; +import org.dromara.warm.flow.core.dto.FlowDto; +import org.dromara.warm.flow.core.entity.Form; +import org.dromara.warm.flow.core.entity.Instance; +import org.dromara.warm.flow.ui.dto.HandlerFeedBackDto; +import org.dromara.warm.flow.ui.dto.HandlerQuery; +import org.dromara.warm.flow.ui.service.WarmFlowService; +import org.dromara.warm.flow.ui.vo.Dict; +import org.dromara.warm.flow.ui.vo.HandlerFeedBackVo; +import org.dromara.warm.flow.ui.vo.HandlerSelectVo; +import org.dromara.warm.flow.ui.vo.NodeExt; +import org.noear.solon.annotation.*; +import org.noear.solon.data.annotation.Tran; + +import java.util.List; +import java.util.Map; + +/** + * 设计器Controller 可选择是否放行,放行可与业务系统共享权限,主要是用来访问业务系统数据 + * + * @author warm + */ +@Controller +@Mapping("/warm-flow") +public class WarmFlowController { + + /** + * 保存流程json字符串 + * + * @param defJson 流程定义json对象 + * @return ApiResult + * @throws Exception 异常 + * @author xiarg + * @since 2024/10/29 16:31 + */ + @Post + @Mapping("/save-json") + @Tran + public ApiResult saveJson(DefJson defJson, @Header("onlyNodeSkip") boolean onlyNodeSkip) throws Exception { + return WarmFlowService.saveJson(defJson, onlyNodeSkip); + } + + /** + * 获取流程定义全部数据(包含节点和跳转) + * + * @param id 流程定义id + * @return ApiResult + * @author xiarg + * @since 2024/10/29 16:31 + */ + @Get + @Mapping("/query-def/{id}") + public ApiResult queryDef(@Path("id") Long id) { + return WarmFlowService.queryDef(id); + } + + /** + * 获取流程定义全部数据(包含节点和跳转) + * + * @return ApiResult + * @author xiarg + * @since 2024/10/29 16:31 + */ + @Get + @Mapping("/query-def") + public ApiResult queryDef1() { + return WarmFlowService.queryDef(null); + } + + + /** + * 获取流程图 + * + * @param id 流程实例id + * @return ApiResult + */ + @Get + @Mapping("/query-flow-chart/{id}") + public ApiResult queryFlowChart(@Path("id") Long id) { + return WarmFlowService.queryFlowChart(id); + } + + /** + * 办理人权限设置列表tabs页签 + * + * @return List + */ + @Get + @Mapping("/handler-type") + public ApiResult> handlerType() { + return WarmFlowService.handlerType(); + } + + /** + * 办理人权限设置列表结果 + * + * @return List + */ + @Get + @Mapping("/handler-result") + public ApiResult handlerResult(HandlerQuery query) { + return WarmFlowService.handlerResult(query); + } + + /** + * 办理人权限名称回显 + * + * @return HandlerSelectVo + */ + @Get + @Mapping("/handler-feedback") + public ApiResult> handlerFeedback(HandlerFeedBackDto handlerFeedBackDto) { + return WarmFlowService.handlerFeedback(handlerFeedBackDto); + } + + /** + * 办理人选择项 + * + * @return List + */ + @Get + @Mapping("/handler-dict") + public ApiResult> handlerDict() { + return WarmFlowService.handlerDict(); + } + + /** + * 已发布表单列表 该接口不需要业务系统实现 + */ + @Get + @Mapping("/published-form") + @Tran + public ApiResult> publishedForm() { + return WarmFlowService.publishedForm(); + } + + /** + * 读取表单内容 + * + * @param id + * @return + */ + @Get + @Mapping("/form-content/{id}") + public ApiResult getFormContent(@Param("id") Long id) { + return WarmFlowService.getFormContent(id); + } + + /** + * 保存表单内容,该接口不需要系统实现 + * + * @param flowDto + * @return + */ + @Post + @Mapping("/form-content") + @Tran + public ApiResult saveFormContent(FlowDto flowDto) { + return WarmFlowService.saveFormContent(flowDto); + } + + + /** + * 根据任务id获取待办任务表单及数据 + * + * @param taskId 当前任务id + * @return {@link ApiResult} + * @author liangli + * @date 2024/8/21 17:08 + **/ + @Get + @Mapping(value = "/execute/load/{taskId}") + public ApiResult load(@Param("taskId") Long taskId) { + return WarmFlowService.load(taskId); + } + + /** + * 根据任务id获取已办任务表单及数据 + * + * @param hisTaskId + * @return + */ + @Get + @Mapping(value = "/execute/hisLoad/{taskId}") + public ApiResult hisLoad(@Param("taskId") Long hisTaskId) { + return WarmFlowService.hisLoad(hisTaskId); + } + + /** + * 通用表单流程审批接口 + * + * @param formData + * @param taskId + * @param skipType + * @param message + * @param nodeCode + * @return + */ + @Tran + @Post + @Mapping(value = "/execute/handle/{taskId}") + public ApiResult handle(Map formData, @Path("taskId") Long taskId, String skipType + , String message, String nodeCode) { + return WarmFlowService.handle(formData, taskId, skipType, message, nodeCode); + } + + /** + * 获取节点扩展属性 + * + * @return List + */ + @Get + @Mapping("/node-ext") + public ApiResult> nodeExt() { + return WarmFlowService.nodeExt(); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-solon-web/src/main/java/org/dromara/warm/flow/ui/controller/WarmFlowUiController.java b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-solon-web/src/main/java/org/dromara/warm/flow/ui/controller/WarmFlowUiController.java new file mode 100644 index 0000000..336d5d4 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-solon-web/src/main/java/org/dromara/warm/flow/ui/controller/WarmFlowUiController.java @@ -0,0 +1,45 @@ +/* + * Copyright 2024-2025, Warm-Flow (290631660@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.warm.flow.ui.controller; + +import org.dromara.warm.flow.core.dto.ApiResult; +import org.dromara.warm.flow.ui.service.WarmFlowService; +import org.dromara.warm.flow.ui.vo.WarmFlowVo; +import org.noear.solon.annotation.Controller; +import org.noear.solon.annotation.Get; +import org.noear.solon.annotation.Mapping; + +/** + * 设计器Controller 匿名访问 + * + * @author warm + */ +@Controller +@Mapping("/warm-flow-ui") +public class WarmFlowUiController { + + /** + * 返回流程定义的配置 + * + * @return ApiResult + */ + @Get + @Mapping("/config") + public ApiResult config() { + return WarmFlowService.config(); + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-solon-web/src/main/resources/META-INF/solon/org.dromara.warm.flow.ui.properties b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-solon-web/src/main/resources/META-INF/solon/org.dromara.warm.flow.ui.properties new file mode 100644 index 0000000..4b49d22 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-ui-solon-web/src/main/resources/META-INF/solon/org.dromara.warm.flow.ui.properties @@ -0,0 +1 @@ +solon.plugin=org.dromara.warm.flow.ui.WarmFlowUiSolonPlugin diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-vue3-ui/pom.xml b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-vue3-ui/pom.xml new file mode 100644 index 0000000..b0b1457 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-vue3-ui/pom.xml @@ -0,0 +1,24 @@ + + + 4.0.0 + + org.dromara.warm + warm-flow-plugin-ui + 1.8.4 + + + warm-flow-plugin-vue3-ui + warm-flow-plugin-vue3-ui + https://github.com/dromara/warm-flow + + + + + ${project.basedir}/src/main/resources + META-INF/resources/ + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-vue3-ui/src/main/resources/warm-flow-ui/css/index-DXVZMahU.css b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-vue3-ui/src/main/resources/warm-flow-ui/css/index-DXVZMahU.css new file mode 100644 index 0000000..4efc5e6 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-vue3-ui/src/main/resources/warm-flow-ui/css/index-DXVZMahU.css @@ -0,0 +1 @@ +:root{--el-color-white:#ffffff;--el-color-black:#000000;--el-color-primary-rgb:64,158,255;--el-color-success-rgb:103,194,58;--el-color-warning-rgb:230,162,60;--el-color-danger-rgb:245,108,108;--el-color-error-rgb:245,108,108;--el-color-info-rgb:144,147,153;--el-font-size-extra-large:20px;--el-font-size-large:18px;--el-font-size-medium:16px;--el-font-size-base:14px;--el-font-size-small:13px;--el-font-size-extra-small:12px;--el-font-family:"Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif;--el-font-weight-primary:500;--el-font-line-height-primary:24px;--el-index-normal:1;--el-index-top:1000;--el-index-popper:2000;--el-border-radius-base:4px;--el-border-radius-small:2px;--el-border-radius-round:20px;--el-border-radius-circle:100%;--el-transition-duration:.3s;--el-transition-duration-fast:.2s;--el-transition-function-ease-in-out-bezier:cubic-bezier(.645, .045, .355, 1);--el-transition-function-fast-bezier:cubic-bezier(.23, 1, .32, 1);--el-transition-all:all var(--el-transition-duration) var(--el-transition-function-ease-in-out-bezier);--el-transition-fade:opacity var(--el-transition-duration) var(--el-transition-function-fast-bezier);--el-transition-md-fade:transform var(--el-transition-duration) var(--el-transition-function-fast-bezier),opacity var(--el-transition-duration) var(--el-transition-function-fast-bezier);--el-transition-fade-linear:opacity var(--el-transition-duration-fast) linear;--el-transition-border:border-color var(--el-transition-duration-fast) var(--el-transition-function-ease-in-out-bezier);--el-transition-box-shadow:box-shadow var(--el-transition-duration-fast) var(--el-transition-function-ease-in-out-bezier);--el-transition-color:color var(--el-transition-duration-fast) var(--el-transition-function-ease-in-out-bezier);--el-component-size-large:40px;--el-component-size:32px;--el-component-size-small:24px}:root{color-scheme:light;--el-color-white:#ffffff;--el-color-black:#000000;--el-color-primary:#409eff;--el-color-primary-light-3:#79bbff;--el-color-primary-light-5:#a0cfff;--el-color-primary-light-7:#c6e2ff;--el-color-primary-light-8:#d9ecff;--el-color-primary-light-9:#ecf5ff;--el-color-primary-dark-2:#337ecc;--el-color-success:#67c23a;--el-color-success-light-3:#95d475;--el-color-success-light-5:#b3e19d;--el-color-success-light-7:#d1edc4;--el-color-success-light-8:#e1f3d8;--el-color-success-light-9:#f0f9eb;--el-color-success-dark-2:#529b2e;--el-color-warning:#e6a23c;--el-color-warning-light-3:#eebe77;--el-color-warning-light-5:#f3d19e;--el-color-warning-light-7:#f8e3c5;--el-color-warning-light-8:#faecd8;--el-color-warning-light-9:#fdf6ec;--el-color-warning-dark-2:#b88230;--el-color-danger:#f56c6c;--el-color-danger-light-3:#f89898;--el-color-danger-light-5:#fab6b6;--el-color-danger-light-7:#fcd3d3;--el-color-danger-light-8:#fde2e2;--el-color-danger-light-9:#fef0f0;--el-color-danger-dark-2:#c45656;--el-color-error:#f56c6c;--el-color-error-light-3:#f89898;--el-color-error-light-5:#fab6b6;--el-color-error-light-7:#fcd3d3;--el-color-error-light-8:#fde2e2;--el-color-error-light-9:#fef0f0;--el-color-error-dark-2:#c45656;--el-color-info:#909399;--el-color-info-light-3:#b1b3b8;--el-color-info-light-5:#c8c9cc;--el-color-info-light-7:#dedfe0;--el-color-info-light-8:#e9e9eb;--el-color-info-light-9:#f4f4f5;--el-color-info-dark-2:#73767a;--el-bg-color:#ffffff;--el-bg-color-page:#f2f3f5;--el-bg-color-overlay:#ffffff;--el-text-color-primary:#303133;--el-text-color-regular:#606266;--el-text-color-secondary:#909399;--el-text-color-placeholder:#a8abb2;--el-text-color-disabled:#c0c4cc;--el-border-color:#dcdfe6;--el-border-color-light:#e4e7ed;--el-border-color-lighter:#ebeef5;--el-border-color-extra-light:#f2f6fc;--el-border-color-dark:#d4d7de;--el-border-color-darker:#cdd0d6;--el-fill-color:#f0f2f5;--el-fill-color-light:#f5f7fa;--el-fill-color-lighter:#fafafa;--el-fill-color-extra-light:#fafcff;--el-fill-color-dark:#ebedf0;--el-fill-color-darker:#e6e8eb;--el-fill-color-blank:#ffffff;--el-box-shadow:0px 12px 32px 4px rgba(0, 0, 0, .04),0px 8px 20px rgba(0, 0, 0, .08);--el-box-shadow-light:0px 0px 12px rgba(0, 0, 0, .12);--el-box-shadow-lighter:0px 0px 6px rgba(0, 0, 0, .12);--el-box-shadow-dark:0px 16px 48px 16px rgba(0, 0, 0, .08),0px 12px 32px rgba(0, 0, 0, .12),0px 8px 16px -8px rgba(0, 0, 0, .16);--el-disabled-bg-color:var(--el-fill-color-light);--el-disabled-text-color:var(--el-text-color-placeholder);--el-disabled-border-color:var(--el-border-color-light);--el-overlay-color:rgba(0, 0, 0, .8);--el-overlay-color-light:rgba(0, 0, 0, .7);--el-overlay-color-lighter:rgba(0, 0, 0, .5);--el-mask-color:rgba(255, 255, 255, .9);--el-mask-color-extra-light:rgba(255, 255, 255, .3);--el-border-width:1px;--el-border-style:solid;--el-border-color-hover:var(--el-text-color-disabled);--el-border:var(--el-border-width) var(--el-border-style) var(--el-border-color);--el-svg-monochrome-grey:var(--el-border-color)}.fade-in-linear-enter-active,.fade-in-linear-leave-active{transition:var(--el-transition-fade-linear)}.fade-in-linear-enter-from,.fade-in-linear-leave-to{opacity:0}.el-fade-in-linear-enter-active,.el-fade-in-linear-leave-active{transition:var(--el-transition-fade-linear)}.el-fade-in-linear-enter-from,.el-fade-in-linear-leave-to{opacity:0}.el-fade-in-enter-active,.el-fade-in-leave-active{transition:all var(--el-transition-duration) cubic-bezier(.55,0,.1,1)}.el-fade-in-enter-from,.el-fade-in-leave-active{opacity:0}.el-zoom-in-center-enter-active,.el-zoom-in-center-leave-active{transition:all var(--el-transition-duration) cubic-bezier(.55,0,.1,1)}.el-zoom-in-center-enter-from,.el-zoom-in-center-leave-active{opacity:0;transform:scaleX(0)}.el-zoom-in-top-enter-active,.el-zoom-in-top-leave-active{opacity:1;transform:scaleY(1);transition:var(--el-transition-md-fade);transform-origin:center top}.el-zoom-in-top-enter-active[data-popper-placement^=top],.el-zoom-in-top-leave-active[data-popper-placement^=top]{transform-origin:center bottom}.el-zoom-in-top-enter-from,.el-zoom-in-top-leave-active{opacity:0;transform:scaleY(0)}.el-zoom-in-bottom-enter-active,.el-zoom-in-bottom-leave-active{opacity:1;transform:scaleY(1);transition:var(--el-transition-md-fade);transform-origin:center bottom}.el-zoom-in-bottom-enter-from,.el-zoom-in-bottom-leave-active{opacity:0;transform:scaleY(0)}.el-zoom-in-left-enter-active,.el-zoom-in-left-leave-active{opacity:1;transform:scale(1);transition:var(--el-transition-md-fade);transform-origin:top left}.el-zoom-in-left-enter-from,.el-zoom-in-left-leave-active{opacity:0;transform:scale(.45)}.collapse-transition{transition:var(--el-transition-duration) height ease-in-out,var(--el-transition-duration) padding-top ease-in-out,var(--el-transition-duration) padding-bottom ease-in-out}.el-collapse-transition-enter-active,.el-collapse-transition-leave-active{transition:var(--el-transition-duration) max-height ease-in-out,var(--el-transition-duration) padding-top ease-in-out,var(--el-transition-duration) padding-bottom ease-in-out}.horizontal-collapse-transition{transition:var(--el-transition-duration) width ease-in-out,var(--el-transition-duration) padding-left ease-in-out,var(--el-transition-duration) padding-right ease-in-out}.el-list-enter-active,.el-list-leave-active{transition:all 1s}.el-list-enter-from,.el-list-leave-to{opacity:0;transform:translateY(-30px)}.el-list-leave-active{position:absolute!important}.el-opacity-transition{transition:opacity var(--el-transition-duration) cubic-bezier(.55,0,.1,1)}.el-icon-loading{-webkit-animation:rotating 2s linear infinite;animation:rotating 2s linear infinite}.el-icon--right{margin-left:5px}.el-icon--left{margin-right:5px}@-webkit-keyframes rotating{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes rotating{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.el-icon{--color:inherit;height:1em;width:1em;line-height:1em;display:inline-flex;justify-content:center;align-items:center;position:relative;fill:currentColor;color:var(--color);font-size:inherit}.el-icon.is-loading{-webkit-animation:rotating 2s linear infinite;animation:rotating 2s linear infinite}.el-icon svg{height:1em;width:1em}.el-affix--fixed{position:fixed}.el-alert{--el-alert-padding:8px 16px;--el-alert-border-radius-base:var(--el-border-radius-base);--el-alert-title-font-size:13px;--el-alert-description-font-size:12px;--el-alert-close-font-size:12px;--el-alert-close-customed-font-size:13px;--el-alert-icon-size:16px;--el-alert-icon-large-size:28px;width:100%;padding:var(--el-alert-padding);margin:0;box-sizing:border-box;border-radius:var(--el-alert-border-radius-base);position:relative;background-color:var(--el-color-white);overflow:hidden;opacity:1;display:flex;align-items:center;transition:opacity var(--el-transition-duration-fast)}.el-alert.is-light .el-alert__close-btn{color:var(--el-text-color-placeholder)}.el-alert.is-dark .el-alert__close-btn,.el-alert.is-dark .el-alert__description{color:var(--el-color-white)}.el-alert.is-center{justify-content:center}.el-alert--success{--el-alert-bg-color:var(--el-color-success-light-9)}.el-alert--success.is-light{background-color:var(--el-alert-bg-color);color:var(--el-color-success)}.el-alert--success.is-light .el-alert__description{color:var(--el-color-success)}.el-alert--success.is-dark{background-color:var(--el-color-success);color:var(--el-color-white)}.el-alert--info{--el-alert-bg-color:var(--el-color-info-light-9)}.el-alert--info.is-light{background-color:var(--el-alert-bg-color);color:var(--el-color-info)}.el-alert--info.is-light .el-alert__description{color:var(--el-color-info)}.el-alert--info.is-dark{background-color:var(--el-color-info);color:var(--el-color-white)}.el-alert--warning{--el-alert-bg-color:var(--el-color-warning-light-9)}.el-alert--warning.is-light{background-color:var(--el-alert-bg-color);color:var(--el-color-warning)}.el-alert--warning.is-light .el-alert__description{color:var(--el-color-warning)}.el-alert--warning.is-dark{background-color:var(--el-color-warning);color:var(--el-color-white)}.el-alert--error{--el-alert-bg-color:var(--el-color-error-light-9)}.el-alert--error.is-light{background-color:var(--el-alert-bg-color);color:var(--el-color-error)}.el-alert--error.is-light .el-alert__description{color:var(--el-color-error)}.el-alert--error.is-dark{background-color:var(--el-color-error);color:var(--el-color-white)}.el-alert__content{display:table-cell;padding:0 8px}.el-alert .el-alert__icon{font-size:var(--el-alert-icon-size);width:var(--el-alert-icon-size)}.el-alert .el-alert__icon.is-big{font-size:var(--el-alert-icon-large-size);width:var(--el-alert-icon-large-size)}.el-alert__title{font-size:var(--el-alert-title-font-size);line-height:18px;vertical-align:text-top}.el-alert__title.is-bold{font-weight:700}.el-alert .el-alert__description{font-size:var(--el-alert-description-font-size);margin:5px 0 0}.el-alert .el-alert__close-btn{font-size:var(--el-alert-close-font-size);opacity:1;position:absolute;top:12px;right:15px;cursor:pointer}.el-alert .el-alert__close-btn.is-customed{font-style:normal;font-size:var(--el-alert-close-customed-font-size);top:9px}.el-alert-fade-enter-from,.el-alert-fade-leave-active{opacity:0}.el-aside{overflow:auto;box-sizing:border-box;flex-shrink:0;width:var(--el-aside-width,300px)}.el-autocomplete{position:relative;display:inline-block}.el-autocomplete__popper.el-popper{background:var(--el-bg-color-overlay);border:1px solid var(--el-border-color-light);box-shadow:var(--el-box-shadow-light)}.el-autocomplete__popper.el-popper .el-popper__arrow:before{border:1px solid var(--el-border-color-light)}.el-autocomplete__popper.el-popper[data-popper-placement^=top] .el-popper__arrow:before{border-top-color:transparent;border-left-color:transparent}.el-autocomplete__popper.el-popper[data-popper-placement^=bottom] .el-popper__arrow:before{border-bottom-color:transparent;border-right-color:transparent}.el-autocomplete__popper.el-popper[data-popper-placement^=left] .el-popper__arrow:before{border-left-color:transparent;border-bottom-color:transparent}.el-autocomplete__popper.el-popper[data-popper-placement^=right] .el-popper__arrow:before{border-right-color:transparent;border-top-color:transparent}.el-autocomplete-suggestion{border-radius:var(--el-border-radius-base);box-sizing:border-box}.el-autocomplete-suggestion__wrap{max-height:280px;padding:10px 0;box-sizing:border-box}.el-autocomplete-suggestion__list{margin:0;padding:0}.el-autocomplete-suggestion li{padding:0 20px;margin:0;line-height:34px;cursor:pointer;color:var(--el-text-color-regular);font-size:var(--el-font-size-base);list-style:none;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.el-autocomplete-suggestion li:hover,.el-autocomplete-suggestion li.highlighted{background-color:var(--el-fill-color-light)}.el-autocomplete-suggestion li.divider{margin-top:6px;border-top:1px solid var(--el-color-black)}.el-autocomplete-suggestion li.divider:last-child{margin-bottom:-6px}.el-autocomplete-suggestion.is-loading li{text-align:center;height:100px;line-height:100px;font-size:20px;color:var(--el-text-color-secondary)}.el-autocomplete-suggestion.is-loading li:after{display:inline-block;content:"";height:100%;vertical-align:middle}.el-autocomplete-suggestion.is-loading li:hover{background-color:var(--el-bg-color-overlay)}.el-autocomplete-suggestion.is-loading .el-icon-loading{vertical-align:middle}.el-avatar{--el-avatar-text-color:var(--el-color-white);--el-avatar-bg-color:var(--el-text-color-disabled);--el-avatar-text-size:14px;--el-avatar-icon-size:18px;--el-avatar-border-radius:var(--el-border-radius-base);--el-avatar-size-large:56px;--el-avatar-size-small:24px;--el-avatar-size:40px;display:inline-flex;justify-content:center;align-items:center;box-sizing:border-box;text-align:center;overflow:hidden;color:var(--el-avatar-text-color);background:var(--el-avatar-bg-color);width:var(--el-avatar-size);height:var(--el-avatar-size);font-size:var(--el-avatar-text-size)}.el-avatar>img{display:block;width:100%;height:100%}.el-avatar--circle{border-radius:50%}.el-avatar--square{border-radius:var(--el-avatar-border-radius)}.el-avatar--icon{font-size:var(--el-avatar-icon-size)}.el-avatar--small{--el-avatar-size:24px}.el-avatar--large{--el-avatar-size:56px}.el-backtop{--el-backtop-bg-color:var(--el-bg-color-overlay);--el-backtop-text-color:var(--el-color-primary);--el-backtop-hover-bg-color:var(--el-border-color-extra-light);position:fixed;background-color:var(--el-backtop-bg-color);width:40px;height:40px;border-radius:50%;color:var(--el-backtop-text-color);display:flex;align-items:center;justify-content:center;font-size:20px;box-shadow:var(--el-box-shadow-lighter);cursor:pointer;z-index:5}.el-backtop:hover{background-color:var(--el-backtop-hover-bg-color)}.el-backtop__icon{font-size:20px}.el-badge{--el-badge-bg-color:var(--el-color-danger);--el-badge-radius:10px;--el-badge-font-size:12px;--el-badge-padding:6px;--el-badge-size:18px;position:relative;vertical-align:middle;display:inline-block;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}.el-badge__content{background-color:var(--el-badge-bg-color);border-radius:var(--el-badge-radius);color:var(--el-color-white);display:inline-flex;justify-content:center;align-items:center;font-size:var(--el-badge-font-size);height:var(--el-badge-size);padding:0 var(--el-badge-padding);white-space:nowrap;border:1px solid var(--el-bg-color)}.el-badge__content.is-fixed{position:absolute;top:0;right:calc(1px + var(--el-badge-size)/ 2);transform:translateY(-50%) translate(100%);z-index:var(--el-index-normal)}.el-badge__content.is-fixed.is-dot{right:5px}.el-badge__content.is-dot{height:8px;width:8px;padding:0;right:0;border-radius:50%}.el-badge__content--primary{background-color:var(--el-color-primary)}.el-badge__content--success{background-color:var(--el-color-success)}.el-badge__content--warning{background-color:var(--el-color-warning)}.el-badge__content--info{background-color:var(--el-color-info)}.el-badge__content--danger{background-color:var(--el-color-danger)}.el-breadcrumb{font-size:14px;line-height:1}.el-breadcrumb:after,.el-breadcrumb:before{display:table;content:""}.el-breadcrumb:after{clear:both}.el-breadcrumb__separator{margin:0 9px;font-weight:700;color:var(--el-text-color-placeholder)}.el-breadcrumb__separator.el-icon{margin:0 6px;font-weight:400}.el-breadcrumb__separator.el-icon svg{vertical-align:middle}.el-breadcrumb__item{float:left;display:inline-flex;align-items:center}.el-breadcrumb__inner{color:var(--el-text-color-regular)}.el-breadcrumb__inner a,.el-breadcrumb__inner.is-link{font-weight:700;text-decoration:none;transition:var(--el-transition-color);color:var(--el-text-color-primary)}.el-breadcrumb__inner a:hover,.el-breadcrumb__inner.is-link:hover{color:var(--el-color-primary);cursor:pointer}.el-breadcrumb__item:last-child .el-breadcrumb__inner,.el-breadcrumb__item:last-child .el-breadcrumb__inner a,.el-breadcrumb__item:last-child .el-breadcrumb__inner a:hover,.el-breadcrumb__item:last-child .el-breadcrumb__inner:hover{font-weight:400;color:var(--el-text-color-regular);cursor:text}.el-breadcrumb__item:last-child .el-breadcrumb__separator{display:none}.el-button-group{display:inline-block;vertical-align:middle}.el-button-group:after,.el-button-group:before{display:table;content:""}.el-button-group:after{clear:both}.el-button-group>.el-button{float:left;position:relative}.el-button-group>.el-button+.el-button{margin-left:0}.el-button-group>.el-button:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.el-button-group>.el-button:last-child{border-top-left-radius:0;border-bottom-left-radius:0}.el-button-group>.el-button:first-child:last-child{border-top-right-radius:var(--el-border-radius-base);border-bottom-right-radius:var(--el-border-radius-base);border-top-left-radius:var(--el-border-radius-base);border-bottom-left-radius:var(--el-border-radius-base)}.el-button-group>.el-button:first-child:last-child.is-round{border-radius:var(--el-border-radius-round)}.el-button-group>.el-button:first-child:last-child.is-circle{border-radius:50%}.el-button-group>.el-button:not(:first-child):not(:last-child){border-radius:0}.el-button-group>.el-button:not(:last-child){margin-right:-1px}.el-button-group>.el-button:active,.el-button-group>.el-button:focus,.el-button-group>.el-button:hover{z-index:1}.el-button-group>.el-button.is-active{z-index:1}.el-button-group>.el-dropdown>.el-button{border-top-left-radius:0;border-bottom-left-radius:0;border-left-color:var(--el-button-divide-border-color)}.el-button-group .el-button--primary:first-child{border-right-color:var(--el-button-divide-border-color)}.el-button-group .el-button--primary:last-child{border-left-color:var(--el-button-divide-border-color)}.el-button-group .el-button--primary:not(:first-child):not(:last-child){border-left-color:var(--el-button-divide-border-color);border-right-color:var(--el-button-divide-border-color)}.el-button-group .el-button--success:first-child{border-right-color:var(--el-button-divide-border-color)}.el-button-group .el-button--success:last-child{border-left-color:var(--el-button-divide-border-color)}.el-button-group .el-button--success:not(:first-child):not(:last-child){border-left-color:var(--el-button-divide-border-color);border-right-color:var(--el-button-divide-border-color)}.el-button-group .el-button--warning:first-child{border-right-color:var(--el-button-divide-border-color)}.el-button-group .el-button--warning:last-child{border-left-color:var(--el-button-divide-border-color)}.el-button-group .el-button--warning:not(:first-child):not(:last-child){border-left-color:var(--el-button-divide-border-color);border-right-color:var(--el-button-divide-border-color)}.el-button-group .el-button--danger:first-child{border-right-color:var(--el-button-divide-border-color)}.el-button-group .el-button--danger:last-child{border-left-color:var(--el-button-divide-border-color)}.el-button-group .el-button--danger:not(:first-child):not(:last-child){border-left-color:var(--el-button-divide-border-color);border-right-color:var(--el-button-divide-border-color)}.el-button-group .el-button--info:first-child{border-right-color:var(--el-button-divide-border-color)}.el-button-group .el-button--info:last-child{border-left-color:var(--el-button-divide-border-color)}.el-button-group .el-button--info:not(:first-child):not(:last-child){border-left-color:var(--el-button-divide-border-color);border-right-color:var(--el-button-divide-border-color)}.el-button{--el-button-font-weight:var(--el-font-weight-primary);--el-button-border-color:var(--el-border-color);--el-button-bg-color:var(--el-fill-color-blank);--el-button-text-color:var(--el-text-color-regular);--el-button-disabled-text-color:var(--el-disabled-text-color);--el-button-disabled-bg-color:var(--el-fill-color-blank);--el-button-disabled-border-color:var(--el-border-color-light);--el-button-divide-border-color:rgba(255, 255, 255, .5);--el-button-hover-text-color:var(--el-color-primary);--el-button-hover-bg-color:var(--el-color-primary-light-9);--el-button-hover-border-color:var(--el-color-primary-light-7);--el-button-active-text-color:var(--el-button-hover-text-color);--el-button-active-border-color:var(--el-color-primary);--el-button-active-bg-color:var(--el-button-hover-bg-color);--el-button-outline-color:var(--el-color-primary-light-5);--el-button-hover-link-text-color:var(--el-color-info);--el-button-active-color:var(--el-text-color-primary)}.el-button{display:inline-flex;justify-content:center;align-items:center;line-height:1;height:32px;white-space:nowrap;cursor:pointer;color:var(--el-button-text-color);text-align:center;box-sizing:border-box;outline:0;transition:.1s;font-weight:var(--el-button-font-weight);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;-webkit-appearance:none;background-color:var(--el-button-bg-color);border:var(--el-border);border-color:var(--el-button-border-color);padding:8px 15px;font-size:var(--el-font-size-base);border-radius:var(--el-border-radius-base)}.el-button:focus,.el-button:hover{color:var(--el-button-hover-text-color);border-color:var(--el-button-hover-border-color);background-color:var(--el-button-hover-bg-color);outline:0}.el-button:active{color:var(--el-button-active-text-color);border-color:var(--el-button-active-border-color);background-color:var(--el-button-active-bg-color);outline:0}.el-button:focus-visible{outline:2px solid var(--el-button-outline-color);outline-offset:1px}.el-button>span{display:inline-flex;align-items:center}.el-button+.el-button{margin-left:12px}.el-button.is-round{padding:8px 15px}.el-button::-moz-focus-inner{border:0}.el-button [class*=el-icon]+span{margin-left:6px}.el-button [class*=el-icon] svg{vertical-align:bottom}.el-button.is-plain{--el-button-hover-text-color:var(--el-color-primary);--el-button-hover-bg-color:var(--el-fill-color-blank);--el-button-hover-border-color:var(--el-color-primary)}.el-button.is-active{color:var(--el-button-active-text-color);border-color:var(--el-button-active-border-color);background-color:var(--el-button-active-bg-color);outline:0}.el-button.is-disabled,.el-button.is-disabled:focus,.el-button.is-disabled:hover{color:var(--el-button-disabled-text-color);cursor:not-allowed;background-image:none;background-color:var(--el-button-disabled-bg-color);border-color:var(--el-button-disabled-border-color)}.el-button.is-loading{position:relative;pointer-events:none}.el-button.is-loading:before{z-index:1;pointer-events:none;content:"";position:absolute;left:-1px;top:-1px;right:-1px;bottom:-1px;border-radius:inherit;background-color:var(--el-mask-color-extra-light)}.el-button.is-round{border-radius:var(--el-border-radius-round)}.el-button.is-circle{width:32px;border-radius:50%;padding:8px}.el-button.is-text{color:var(--el-button-text-color);border:0 solid transparent;background-color:transparent}.el-button.is-text.is-disabled{color:var(--el-button-disabled-text-color);background-color:transparent!important}.el-button.is-text:not(.is-disabled):focus,.el-button.is-text:not(.is-disabled):hover{background-color:var(--el-fill-color-light)}.el-button.is-text:not(.is-disabled):focus-visible{outline:2px solid var(--el-button-outline-color);outline-offset:1px}.el-button.is-text:not(.is-disabled):active{background-color:var(--el-fill-color)}.el-button.is-text:not(.is-disabled).is-has-bg{background-color:var(--el-fill-color-light)}.el-button.is-text:not(.is-disabled).is-has-bg:focus,.el-button.is-text:not(.is-disabled).is-has-bg:hover{background-color:var(--el-fill-color)}.el-button.is-text:not(.is-disabled).is-has-bg:active{background-color:var(--el-fill-color-dark)}.el-button__text--expand{letter-spacing:.3em;margin-right:-.3em}.el-button.is-link{border-color:transparent;color:var(--el-button-text-color);background:0 0;padding:2px;height:auto}.el-button.is-link:focus,.el-button.is-link:hover{color:var(--el-button-hover-link-text-color)}.el-button.is-link.is-disabled{color:var(--el-button-disabled-text-color);background-color:transparent!important;border-color:transparent!important}.el-button.is-link:not(.is-disabled):focus,.el-button.is-link:not(.is-disabled):hover{border-color:transparent;background-color:transparent}.el-button.is-link:not(.is-disabled):active{color:var(--el-button-active-color);border-color:transparent;background-color:transparent}.el-button--text{border-color:transparent;background:0 0;color:var(--el-color-primary);padding-left:0;padding-right:0}.el-button--text.is-disabled{color:var(--el-button-disabled-text-color);background-color:transparent!important;border-color:transparent!important}.el-button--text:not(.is-disabled):focus,.el-button--text:not(.is-disabled):hover{color:var(--el-color-primary-light-3);border-color:transparent;background-color:transparent}.el-button--text:not(.is-disabled):active{color:var(--el-color-primary-dark-2);border-color:transparent;background-color:transparent}.el-button__link--expand{letter-spacing:.3em;margin-right:-.3em}.el-button--primary{--el-button-text-color:var(--el-color-white);--el-button-bg-color:var(--el-color-primary);--el-button-border-color:var(--el-color-primary);--el-button-outline-color:var(--el-color-primary-light-5);--el-button-active-color:var(--el-color-primary-dark-2);--el-button-hover-text-color:var(--el-color-white);--el-button-hover-link-text-color:var(--el-color-primary-light-5);--el-button-hover-bg-color:var(--el-color-primary-light-3);--el-button-hover-border-color:var(--el-color-primary-light-3);--el-button-active-bg-color:var(--el-color-primary-dark-2);--el-button-active-border-color:var(--el-color-primary-dark-2);--el-button-disabled-text-color:var(--el-color-white);--el-button-disabled-bg-color:var(--el-color-primary-light-5);--el-button-disabled-border-color:var(--el-color-primary-light-5)}.el-button--primary.is-link,.el-button--primary.is-plain,.el-button--primary.is-text{--el-button-text-color:var(--el-color-primary);--el-button-bg-color:var(--el-color-primary-light-9);--el-button-border-color:var(--el-color-primary-light-5);--el-button-hover-text-color:var(--el-color-white);--el-button-hover-bg-color:var(--el-color-primary);--el-button-hover-border-color:var(--el-color-primary);--el-button-active-text-color:var(--el-color-white)}.el-button--primary.is-link.is-disabled,.el-button--primary.is-link.is-disabled:active,.el-button--primary.is-link.is-disabled:focus,.el-button--primary.is-link.is-disabled:hover,.el-button--primary.is-plain.is-disabled,.el-button--primary.is-plain.is-disabled:active,.el-button--primary.is-plain.is-disabled:focus,.el-button--primary.is-plain.is-disabled:hover,.el-button--primary.is-text.is-disabled,.el-button--primary.is-text.is-disabled:active,.el-button--primary.is-text.is-disabled:focus,.el-button--primary.is-text.is-disabled:hover{color:var(--el-color-primary-light-5);background-color:var(--el-color-primary-light-9);border-color:var(--el-color-primary-light-8)}.el-button--success{--el-button-text-color:var(--el-color-white);--el-button-bg-color:var(--el-color-success);--el-button-border-color:var(--el-color-success);--el-button-outline-color:var(--el-color-success-light-5);--el-button-active-color:var(--el-color-success-dark-2);--el-button-hover-text-color:var(--el-color-white);--el-button-hover-link-text-color:var(--el-color-success-light-5);--el-button-hover-bg-color:var(--el-color-success-light-3);--el-button-hover-border-color:var(--el-color-success-light-3);--el-button-active-bg-color:var(--el-color-success-dark-2);--el-button-active-border-color:var(--el-color-success-dark-2);--el-button-disabled-text-color:var(--el-color-white);--el-button-disabled-bg-color:var(--el-color-success-light-5);--el-button-disabled-border-color:var(--el-color-success-light-5)}.el-button--success.is-link,.el-button--success.is-plain,.el-button--success.is-text{--el-button-text-color:var(--el-color-success);--el-button-bg-color:var(--el-color-success-light-9);--el-button-border-color:var(--el-color-success-light-5);--el-button-hover-text-color:var(--el-color-white);--el-button-hover-bg-color:var(--el-color-success);--el-button-hover-border-color:var(--el-color-success);--el-button-active-text-color:var(--el-color-white)}.el-button--success.is-link.is-disabled,.el-button--success.is-link.is-disabled:active,.el-button--success.is-link.is-disabled:focus,.el-button--success.is-link.is-disabled:hover,.el-button--success.is-plain.is-disabled,.el-button--success.is-plain.is-disabled:active,.el-button--success.is-plain.is-disabled:focus,.el-button--success.is-plain.is-disabled:hover,.el-button--success.is-text.is-disabled,.el-button--success.is-text.is-disabled:active,.el-button--success.is-text.is-disabled:focus,.el-button--success.is-text.is-disabled:hover{color:var(--el-color-success-light-5);background-color:var(--el-color-success-light-9);border-color:var(--el-color-success-light-8)}.el-button--warning{--el-button-text-color:var(--el-color-white);--el-button-bg-color:var(--el-color-warning);--el-button-border-color:var(--el-color-warning);--el-button-outline-color:var(--el-color-warning-light-5);--el-button-active-color:var(--el-color-warning-dark-2);--el-button-hover-text-color:var(--el-color-white);--el-button-hover-link-text-color:var(--el-color-warning-light-5);--el-button-hover-bg-color:var(--el-color-warning-light-3);--el-button-hover-border-color:var(--el-color-warning-light-3);--el-button-active-bg-color:var(--el-color-warning-dark-2);--el-button-active-border-color:var(--el-color-warning-dark-2);--el-button-disabled-text-color:var(--el-color-white);--el-button-disabled-bg-color:var(--el-color-warning-light-5);--el-button-disabled-border-color:var(--el-color-warning-light-5)}.el-button--warning.is-link,.el-button--warning.is-plain,.el-button--warning.is-text{--el-button-text-color:var(--el-color-warning);--el-button-bg-color:var(--el-color-warning-light-9);--el-button-border-color:var(--el-color-warning-light-5);--el-button-hover-text-color:var(--el-color-white);--el-button-hover-bg-color:var(--el-color-warning);--el-button-hover-border-color:var(--el-color-warning);--el-button-active-text-color:var(--el-color-white)}.el-button--warning.is-link.is-disabled,.el-button--warning.is-link.is-disabled:active,.el-button--warning.is-link.is-disabled:focus,.el-button--warning.is-link.is-disabled:hover,.el-button--warning.is-plain.is-disabled,.el-button--warning.is-plain.is-disabled:active,.el-button--warning.is-plain.is-disabled:focus,.el-button--warning.is-plain.is-disabled:hover,.el-button--warning.is-text.is-disabled,.el-button--warning.is-text.is-disabled:active,.el-button--warning.is-text.is-disabled:focus,.el-button--warning.is-text.is-disabled:hover{color:var(--el-color-warning-light-5);background-color:var(--el-color-warning-light-9);border-color:var(--el-color-warning-light-8)}.el-button--danger{--el-button-text-color:var(--el-color-white);--el-button-bg-color:var(--el-color-danger);--el-button-border-color:var(--el-color-danger);--el-button-outline-color:var(--el-color-danger-light-5);--el-button-active-color:var(--el-color-danger-dark-2);--el-button-hover-text-color:var(--el-color-white);--el-button-hover-link-text-color:var(--el-color-danger-light-5);--el-button-hover-bg-color:var(--el-color-danger-light-3);--el-button-hover-border-color:var(--el-color-danger-light-3);--el-button-active-bg-color:var(--el-color-danger-dark-2);--el-button-active-border-color:var(--el-color-danger-dark-2);--el-button-disabled-text-color:var(--el-color-white);--el-button-disabled-bg-color:var(--el-color-danger-light-5);--el-button-disabled-border-color:var(--el-color-danger-light-5)}.el-button--danger.is-link,.el-button--danger.is-plain,.el-button--danger.is-text{--el-button-text-color:var(--el-color-danger);--el-button-bg-color:var(--el-color-danger-light-9);--el-button-border-color:var(--el-color-danger-light-5);--el-button-hover-text-color:var(--el-color-white);--el-button-hover-bg-color:var(--el-color-danger);--el-button-hover-border-color:var(--el-color-danger);--el-button-active-text-color:var(--el-color-white)}.el-button--danger.is-link.is-disabled,.el-button--danger.is-link.is-disabled:active,.el-button--danger.is-link.is-disabled:focus,.el-button--danger.is-link.is-disabled:hover,.el-button--danger.is-plain.is-disabled,.el-button--danger.is-plain.is-disabled:active,.el-button--danger.is-plain.is-disabled:focus,.el-button--danger.is-plain.is-disabled:hover,.el-button--danger.is-text.is-disabled,.el-button--danger.is-text.is-disabled:active,.el-button--danger.is-text.is-disabled:focus,.el-button--danger.is-text.is-disabled:hover{color:var(--el-color-danger-light-5);background-color:var(--el-color-danger-light-9);border-color:var(--el-color-danger-light-8)}.el-button--info{--el-button-text-color:var(--el-color-white);--el-button-bg-color:var(--el-color-info);--el-button-border-color:var(--el-color-info);--el-button-outline-color:var(--el-color-info-light-5);--el-button-active-color:var(--el-color-info-dark-2);--el-button-hover-text-color:var(--el-color-white);--el-button-hover-link-text-color:var(--el-color-info-light-5);--el-button-hover-bg-color:var(--el-color-info-light-3);--el-button-hover-border-color:var(--el-color-info-light-3);--el-button-active-bg-color:var(--el-color-info-dark-2);--el-button-active-border-color:var(--el-color-info-dark-2);--el-button-disabled-text-color:var(--el-color-white);--el-button-disabled-bg-color:var(--el-color-info-light-5);--el-button-disabled-border-color:var(--el-color-info-light-5)}.el-button--info.is-link,.el-button--info.is-plain,.el-button--info.is-text{--el-button-text-color:var(--el-color-info);--el-button-bg-color:var(--el-color-info-light-9);--el-button-border-color:var(--el-color-info-light-5);--el-button-hover-text-color:var(--el-color-white);--el-button-hover-bg-color:var(--el-color-info);--el-button-hover-border-color:var(--el-color-info);--el-button-active-text-color:var(--el-color-white)}.el-button--info.is-link.is-disabled,.el-button--info.is-link.is-disabled:active,.el-button--info.is-link.is-disabled:focus,.el-button--info.is-link.is-disabled:hover,.el-button--info.is-plain.is-disabled,.el-button--info.is-plain.is-disabled:active,.el-button--info.is-plain.is-disabled:focus,.el-button--info.is-plain.is-disabled:hover,.el-button--info.is-text.is-disabled,.el-button--info.is-text.is-disabled:active,.el-button--info.is-text.is-disabled:focus,.el-button--info.is-text.is-disabled:hover{color:var(--el-color-info-light-5);background-color:var(--el-color-info-light-9);border-color:var(--el-color-info-light-8)}.el-button--large{--el-button-size:40px;height:var(--el-button-size);padding:12px 19px;font-size:var(--el-font-size-base);border-radius:var(--el-border-radius-base)}.el-button--large [class*=el-icon]+span{margin-left:8px}.el-button--large.is-round{padding:12px 19px}.el-button--large.is-circle{width:var(--el-button-size);padding:12px}.el-button--small{--el-button-size:24px;height:var(--el-button-size);padding:5px 11px;font-size:12px;border-radius:calc(var(--el-border-radius-base) - 1px)}.el-button--small [class*=el-icon]+span{margin-left:4px}.el-button--small.is-round{padding:5px 11px}.el-button--small.is-circle{width:var(--el-button-size);padding:5px}.el-calendar{--el-calendar-border:var(--el-table-border, 1px solid var(--el-border-color-lighter));--el-calendar-header-border-bottom:var(--el-calendar-border);--el-calendar-selected-bg-color:var(--el-color-primary-light-9);--el-calendar-cell-width:85px;background-color:var(--el-fill-color-blank)}.el-calendar__header{display:flex;justify-content:space-between;padding:12px 20px;border-bottom:var(--el-calendar-header-border-bottom)}.el-calendar__title{color:var(--el-text-color);align-self:center}.el-calendar__body{padding:12px 20px 35px}.el-calendar-table{table-layout:fixed;width:100%}.el-calendar-table thead th{padding:12px 0;color:var(--el-text-color-regular);font-weight:400}.el-calendar-table:not(.is-range) td.next,.el-calendar-table:not(.is-range) td.prev{color:var(--el-text-color-placeholder)}.el-calendar-table td{border-bottom:var(--el-calendar-border);border-right:var(--el-calendar-border);vertical-align:top;transition:background-color var(--el-transition-duration-fast) ease}.el-calendar-table td.is-selected{background-color:var(--el-calendar-selected-bg-color)}.el-calendar-table td.is-today{color:var(--el-color-primary)}.el-calendar-table tr:first-child td{border-top:var(--el-calendar-border)}.el-calendar-table tr td:first-child{border-left:var(--el-calendar-border)}.el-calendar-table tr.el-calendar-table__row--hide-border td{border-top:none}.el-calendar-table .el-calendar-day{box-sizing:border-box;padding:8px;height:var(--el-calendar-cell-width)}.el-calendar-table .el-calendar-day:hover{cursor:pointer;background-color:var(--el-calendar-selected-bg-color)}.el-card{--el-card-border-color:var(--el-border-color-light);--el-card-border-radius:4px;--el-card-padding:20px;--el-card-bg-color:var(--el-fill-color-blank)}.el-card{border-radius:var(--el-card-border-radius);border:1px solid var(--el-card-border-color);background-color:var(--el-card-bg-color);overflow:hidden;color:var(--el-text-color-primary);transition:var(--el-transition-duration)}.el-card.is-always-shadow{box-shadow:var(--el-box-shadow-light)}.el-card.is-hover-shadow:focus,.el-card.is-hover-shadow:hover{box-shadow:var(--el-box-shadow-light)}.el-card__header{padding:calc(var(--el-card-padding) - 2px) var(--el-card-padding);border-bottom:1px solid var(--el-card-border-color);box-sizing:border-box}.el-card__body{padding:var(--el-card-padding)}.el-card__footer{padding:calc(var(--el-card-padding) - 2px) var(--el-card-padding);border-top:1px solid var(--el-card-border-color);box-sizing:border-box}.el-carousel__item{position:absolute;top:0;left:0;width:100%;height:100%;display:inline-block;overflow:hidden;z-index:calc(var(--el-index-normal) - 1)}.el-carousel__item.is-active{z-index:calc(var(--el-index-normal) - 1)}.el-carousel__item.is-animating{transition:transform .4s ease-in-out}.el-carousel__item--card{width:50%;transition:transform .4s ease-in-out}.el-carousel__item--card.is-in-stage{cursor:pointer;z-index:var(--el-index-normal)}.el-carousel__item--card.is-in-stage.is-hover .el-carousel__mask,.el-carousel__item--card.is-in-stage:hover .el-carousel__mask{opacity:.12}.el-carousel__item--card.is-active{z-index:calc(var(--el-index-normal) + 1)}.el-carousel__item--card-vertical{width:100%;height:50%}.el-carousel__mask{position:absolute;width:100%;height:100%;top:0;left:0;background-color:var(--el-color-white);opacity:.24;transition:var(--el-transition-duration-fast)}.el-carousel{--el-carousel-arrow-font-size:12px;--el-carousel-arrow-size:36px;--el-carousel-arrow-background:rgba(31, 45, 61, .11);--el-carousel-arrow-hover-background:rgba(31, 45, 61, .23);--el-carousel-indicator-width:30px;--el-carousel-indicator-height:2px;--el-carousel-indicator-padding-horizontal:4px;--el-carousel-indicator-padding-vertical:12px;--el-carousel-indicator-out-color:var(--el-border-color-hover);position:relative}.el-carousel--horizontal,.el-carousel--vertical{overflow:hidden}.el-carousel__container{position:relative;height:300px}.el-carousel__arrow{border:none;outline:0;padding:0;margin:0;height:var(--el-carousel-arrow-size);width:var(--el-carousel-arrow-size);cursor:pointer;transition:var(--el-transition-duration);border-radius:50%;background-color:var(--el-carousel-arrow-background);color:#fff;position:absolute;top:50%;z-index:10;transform:translateY(-50%);text-align:center;font-size:var(--el-carousel-arrow-font-size);display:inline-flex;justify-content:center;align-items:center}.el-carousel__arrow--left{left:16px}.el-carousel__arrow--right{right:16px}.el-carousel__arrow:hover{background-color:var(--el-carousel-arrow-hover-background)}.el-carousel__arrow i{cursor:pointer}.el-carousel__indicators{position:absolute;list-style:none;margin:0;padding:0;z-index:calc(var(--el-index-normal) + 1)}.el-carousel__indicators--horizontal{bottom:0;left:50%;transform:translate(-50%)}.el-carousel__indicators--vertical{right:0;top:50%;transform:translateY(-50%)}.el-carousel__indicators--outside{bottom:calc(var(--el-carousel-indicator-height) + var(--el-carousel-indicator-padding-vertical) * 2);text-align:center;position:static;transform:none}.el-carousel__indicators--outside .el-carousel__indicator:hover button{opacity:.64}.el-carousel__indicators--outside button{background-color:var(--el-carousel-indicator-out-color);opacity:.24}.el-carousel__indicators--right{right:0}.el-carousel__indicators--labels{left:0;right:0;transform:none;text-align:center}.el-carousel__indicators--labels .el-carousel__button{height:auto;width:auto;padding:2px 18px;font-size:12px;color:#000}.el-carousel__indicators--labels .el-carousel__indicator{padding:6px 4px}.el-carousel__indicator{background-color:transparent;cursor:pointer}.el-carousel__indicator:hover button{opacity:.72}.el-carousel__indicator--horizontal{display:inline-block;padding:var(--el-carousel-indicator-padding-vertical) var(--el-carousel-indicator-padding-horizontal)}.el-carousel__indicator--vertical{padding:var(--el-carousel-indicator-padding-horizontal) var(--el-carousel-indicator-padding-vertical)}.el-carousel__indicator--vertical .el-carousel__button{width:var(--el-carousel-indicator-height);height:calc(var(--el-carousel-indicator-width)/ 2)}.el-carousel__indicator.is-active button{opacity:1}.el-carousel__button{display:block;opacity:.48;width:var(--el-carousel-indicator-width);height:var(--el-carousel-indicator-height);background-color:#fff;border:none;outline:0;padding:0;margin:0;cursor:pointer;transition:var(--el-transition-duration)}.carousel-arrow-left-enter-from,.carousel-arrow-left-leave-active{transform:translateY(-50%) translate(-10px);opacity:0}.carousel-arrow-right-enter-from,.carousel-arrow-right-leave-active{transform:translateY(-50%) translate(10px);opacity:0}.el-cascader-panel{--el-cascader-menu-text-color:var(--el-text-color-regular);--el-cascader-menu-selected-text-color:var(--el-color-primary);--el-cascader-menu-fill:var(--el-bg-color-overlay);--el-cascader-menu-font-size:var(--el-font-size-base);--el-cascader-menu-radius:var(--el-border-radius-base);--el-cascader-menu-border:solid 1px var(--el-border-color-light);--el-cascader-menu-shadow:var(--el-box-shadow-light);--el-cascader-node-background-hover:var(--el-fill-color-light);--el-cascader-node-color-disabled:var(--el-text-color-placeholder);--el-cascader-color-empty:var(--el-text-color-placeholder);--el-cascader-tag-background:var(--el-fill-color)}.el-cascader-panel{display:flex;border-radius:var(--el-cascader-menu-radius);font-size:var(--el-cascader-menu-font-size)}.el-cascader-panel.is-bordered{border:var(--el-cascader-menu-border);border-radius:var(--el-cascader-menu-radius)}.el-cascader-menu{min-width:180px;box-sizing:border-box;color:var(--el-cascader-menu-text-color);border-right:var(--el-cascader-menu-border)}.el-cascader-menu:last-child{border-right:none}.el-cascader-menu:last-child .el-cascader-node{padding-right:20px}.el-cascader-menu__wrap.el-scrollbar__wrap{height:204px}.el-cascader-menu__list{position:relative;min-height:100%;margin:0;padding:6px 0;list-style:none;box-sizing:border-box}.el-cascader-menu__hover-zone{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none}.el-cascader-menu__empty-text{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);display:flex;align-items:center;color:var(--el-cascader-color-empty)}.el-cascader-menu__empty-text .is-loading{margin-right:2px}.el-cascader-node{position:relative;display:flex;align-items:center;padding:0 30px 0 20px;height:34px;line-height:34px;outline:0}.el-cascader-node.is-selectable.in-active-path{color:var(--el-cascader-menu-text-color)}.el-cascader-node.in-active-path,.el-cascader-node.is-active,.el-cascader-node.is-selectable.in-checked-path{color:var(--el-cascader-menu-selected-text-color);font-weight:700}.el-cascader-node:not(.is-disabled){cursor:pointer}.el-cascader-node:not(.is-disabled):focus,.el-cascader-node:not(.is-disabled):hover{background:var(--el-cascader-node-background-hover)}.el-cascader-node.is-disabled{color:var(--el-cascader-node-color-disabled);cursor:not-allowed}.el-cascader-node__prefix{position:absolute;left:10px}.el-cascader-node__postfix{position:absolute;right:10px}.el-cascader-node__label{flex:1;text-align:left;padding:0 8px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.el-cascader-node>.el-checkbox{margin-right:0}.el-cascader-node>.el-radio{margin-right:0}.el-cascader-node>.el-radio .el-radio__label{padding-left:0}.el-cascader{--el-cascader-menu-text-color:var(--el-text-color-regular);--el-cascader-menu-selected-text-color:var(--el-color-primary);--el-cascader-menu-fill:var(--el-bg-color-overlay);--el-cascader-menu-font-size:var(--el-font-size-base);--el-cascader-menu-radius:var(--el-border-radius-base);--el-cascader-menu-border:solid 1px var(--el-border-color-light);--el-cascader-menu-shadow:var(--el-box-shadow-light);--el-cascader-node-background-hover:var(--el-fill-color-light);--el-cascader-node-color-disabled:var(--el-text-color-placeholder);--el-cascader-color-empty:var(--el-text-color-placeholder);--el-cascader-tag-background:var(--el-fill-color);display:inline-block;vertical-align:middle;position:relative;font-size:var(--el-font-size-base);line-height:32px;outline:0}.el-cascader:not(.is-disabled):hover .el-input__wrapper{cursor:pointer;box-shadow:0 0 0 1px var(--el-input-hover-border-color) inset}.el-cascader .el-input{display:flex;cursor:pointer}.el-cascader .el-input .el-input__inner{text-overflow:ellipsis;cursor:pointer}.el-cascader .el-input .el-input__suffix-inner .el-icon{height:calc(100% - 2px)}.el-cascader .el-input .el-input__suffix-inner .el-icon svg{vertical-align:middle}.el-cascader .el-input .icon-arrow-down{transition:transform var(--el-transition-duration);font-size:14px}.el-cascader .el-input .icon-arrow-down.is-reverse{transform:rotate(180deg)}.el-cascader .el-input .icon-circle-close:hover{color:var(--el-input-clear-hover-color,var(--el-text-color-secondary))}.el-cascader .el-input.is-focus .el-input__wrapper{box-shadow:0 0 0 1px var(--el-input-focus-border-color,var(--el-color-primary)) inset}.el-cascader--large{font-size:14px;line-height:40px}.el-cascader--small{font-size:12px;line-height:24px}.el-cascader.is-disabled .el-cascader__label{z-index:calc(var(--el-index-normal) + 1);color:var(--el-disabled-text-color)}.el-cascader__dropdown{--el-cascader-menu-text-color:var(--el-text-color-regular);--el-cascader-menu-selected-text-color:var(--el-color-primary);--el-cascader-menu-fill:var(--el-bg-color-overlay);--el-cascader-menu-font-size:var(--el-font-size-base);--el-cascader-menu-radius:var(--el-border-radius-base);--el-cascader-menu-border:solid 1px var(--el-border-color-light);--el-cascader-menu-shadow:var(--el-box-shadow-light);--el-cascader-node-background-hover:var(--el-fill-color-light);--el-cascader-node-color-disabled:var(--el-text-color-placeholder);--el-cascader-color-empty:var(--el-text-color-placeholder);--el-cascader-tag-background:var(--el-fill-color)}.el-cascader__dropdown{font-size:var(--el-cascader-menu-font-size);border-radius:var(--el-cascader-menu-radius)}.el-cascader__dropdown.el-popper{background:var(--el-cascader-menu-fill);border:var(--el-cascader-menu-border);box-shadow:var(--el-cascader-menu-shadow)}.el-cascader__dropdown.el-popper .el-popper__arrow:before{border:var(--el-cascader-menu-border)}.el-cascader__dropdown.el-popper[data-popper-placement^=top] .el-popper__arrow:before{border-top-color:transparent;border-left-color:transparent}.el-cascader__dropdown.el-popper[data-popper-placement^=bottom] .el-popper__arrow:before{border-bottom-color:transparent;border-right-color:transparent}.el-cascader__dropdown.el-popper[data-popper-placement^=left] .el-popper__arrow:before{border-left-color:transparent;border-bottom-color:transparent}.el-cascader__dropdown.el-popper[data-popper-placement^=right] .el-popper__arrow:before{border-right-color:transparent;border-top-color:transparent}.el-cascader__dropdown.el-popper{box-shadow:var(--el-cascader-menu-shadow)}.el-cascader__tags{position:absolute;left:0;right:30px;top:50%;transform:translateY(-50%);display:flex;flex-wrap:wrap;line-height:normal;text-align:left;box-sizing:border-box}.el-cascader__tags .el-tag{display:inline-flex;align-items:center;max-width:100%;margin:2px 0 2px 6px;text-overflow:ellipsis;background:var(--el-cascader-tag-background)}.el-cascader__tags .el-tag:not(.is-hit){border-color:transparent}.el-cascader__tags .el-tag>span{flex:1;overflow:hidden;text-overflow:ellipsis}.el-cascader__tags .el-tag .el-icon-close{flex:none;background-color:var(--el-text-color-placeholder);color:var(--el-color-white)}.el-cascader__tags .el-tag .el-icon-close:hover{background-color:var(--el-text-color-secondary)}.el-cascader__collapse-tags{white-space:normal;z-index:var(--el-index-normal)}.el-cascader__collapse-tags .el-tag{display:inline-flex;align-items:center;max-width:100%;margin:2px 0 2px 6px;text-overflow:ellipsis;background:var(--el-fill-color)}.el-cascader__collapse-tags .el-tag:not(.is-hit){border-color:transparent}.el-cascader__collapse-tags .el-tag>span{flex:1;overflow:hidden;text-overflow:ellipsis}.el-cascader__collapse-tags .el-tag .el-icon-close{flex:none;background-color:var(--el-text-color-placeholder);color:var(--el-color-white)}.el-cascader__collapse-tags .el-tag .el-icon-close:hover{background-color:var(--el-text-color-secondary)}.el-cascader__suggestion-panel{border-radius:var(--el-cascader-menu-radius)}.el-cascader__suggestion-list{max-height:204px;margin:0;padding:6px 0;font-size:var(--el-font-size-base);color:var(--el-cascader-menu-text-color);text-align:center}.el-cascader__suggestion-item{display:flex;justify-content:space-between;align-items:center;height:34px;padding:0 15px;text-align:left;outline:0;cursor:pointer}.el-cascader__suggestion-item:focus,.el-cascader__suggestion-item:hover{background:var(--el-cascader-node-background-hover)}.el-cascader__suggestion-item.is-checked{color:var(--el-cascader-menu-selected-text-color);font-weight:700}.el-cascader__suggestion-item>span{margin-right:10px}.el-cascader__empty-text{margin:10px 0;color:var(--el-cascader-color-empty)}.el-cascader__search-input{flex:1;height:24px;min-width:60px;margin:2px 0 2px 11px;padding:0;color:var(--el-cascader-menu-text-color);border:none;outline:0;box-sizing:border-box;background:0 0}.el-cascader__search-input::-moz-placeholder{color:transparent}.el-cascader__search-input:-ms-input-placeholder{color:transparent}.el-cascader__search-input::placeholder{color:transparent}.el-check-tag{background-color:var(--el-color-info-light-9);border-radius:var(--el-border-radius-base);color:var(--el-color-info);cursor:pointer;display:inline-block;font-size:var(--el-font-size-base);line-height:var(--el-font-size-base);padding:7px 15px;transition:var(--el-transition-all);font-weight:700}.el-check-tag:hover{background-color:var(--el-color-info-light-7)}.el-check-tag.is-checked{background-color:var(--el-color-primary-light-8);color:var(--el-color-primary)}.el-check-tag.is-checked:hover{background-color:var(--el-color-primary-light-7)}.el-checkbox-button{--el-checkbox-button-checked-bg-color:var(--el-color-primary);--el-checkbox-button-checked-text-color:var(--el-color-white);--el-checkbox-button-checked-border-color:var(--el-color-primary)}.el-checkbox-button{position:relative;display:inline-block}.el-checkbox-button__inner{display:inline-block;line-height:1;font-weight:var(--el-checkbox-font-weight);white-space:nowrap;vertical-align:middle;cursor:pointer;background:var(--el-button-bg-color,var(--el-fill-color-blank));border:var(--el-border);border-left-color:transparent;color:var(--el-button-text-color,var(--el-text-color-regular));-webkit-appearance:none;text-align:center;box-sizing:border-box;outline:0;margin:0;position:relative;transition:var(--el-transition-all);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;padding:8px 15px;font-size:var(--el-font-size-base);border-radius:0}.el-checkbox-button__inner.is-round{padding:8px 15px}.el-checkbox-button__inner:hover{color:var(--el-color-primary)}.el-checkbox-button__inner [class*=el-icon-]{line-height:.9}.el-checkbox-button__inner [class*=el-icon-]+span{margin-left:5px}.el-checkbox-button__original{opacity:0;outline:0;position:absolute;margin:0;z-index:-1}.el-checkbox-button.is-checked .el-checkbox-button__inner{color:var(--el-checkbox-button-checked-text-color);background-color:var(--el-checkbox-button-checked-bg-color);border-color:var(--el-checkbox-button-checked-border-color);box-shadow:-1px 0 0 0 var(--el-color-primary-light-7)}.el-checkbox-button.is-checked:first-child .el-checkbox-button__inner{border-left-color:var(--el-checkbox-button-checked-border-color)}.el-checkbox-button.is-disabled .el-checkbox-button__inner{color:var(--el-disabled-text-color);cursor:not-allowed;background-image:none;background-color:var(--el-button-disabled-bg-color,var(--el-fill-color-blank));border-color:var(--el-button-disabled-border-color,var(--el-border-color-light));box-shadow:none}.el-checkbox-button.is-disabled:first-child .el-checkbox-button__inner{border-left-color:var(--el-button-disabled-border-color,var(--el-border-color-light))}.el-checkbox-button:first-child .el-checkbox-button__inner{border-left:var(--el-border);border-top-left-radius:var(--el-border-radius-base);border-bottom-left-radius:var(--el-border-radius-base);box-shadow:none!important}.el-checkbox-button.is-focus .el-checkbox-button__inner{border-color:var(--el-checkbox-button-checked-border-color)}.el-checkbox-button:last-child .el-checkbox-button__inner{border-top-right-radius:var(--el-border-radius-base);border-bottom-right-radius:var(--el-border-radius-base)}.el-checkbox-button--large .el-checkbox-button__inner{padding:12px 19px;font-size:var(--el-font-size-base);border-radius:0}.el-checkbox-button--large .el-checkbox-button__inner.is-round{padding:12px 19px}.el-checkbox-button--small .el-checkbox-button__inner{padding:5px 11px;font-size:12px;border-radius:0}.el-checkbox-button--small .el-checkbox-button__inner.is-round{padding:5px 11px}.el-checkbox-group{font-size:0;line-height:0}.el-checkbox{--el-checkbox-font-size:14px;--el-checkbox-font-weight:var(--el-font-weight-primary);--el-checkbox-text-color:var(--el-text-color-regular);--el-checkbox-input-height:14px;--el-checkbox-input-width:14px;--el-checkbox-border-radius:var(--el-border-radius-small);--el-checkbox-bg-color:var(--el-fill-color-blank);--el-checkbox-input-border:var(--el-border);--el-checkbox-disabled-border-color:var(--el-border-color);--el-checkbox-disabled-input-fill:var(--el-fill-color-light);--el-checkbox-disabled-icon-color:var(--el-text-color-placeholder);--el-checkbox-disabled-checked-input-fill:var(--el-border-color-extra-light);--el-checkbox-disabled-checked-input-border-color:var(--el-border-color);--el-checkbox-disabled-checked-icon-color:var(--el-text-color-placeholder);--el-checkbox-checked-text-color:var(--el-color-primary);--el-checkbox-checked-input-border-color:var(--el-color-primary);--el-checkbox-checked-bg-color:var(--el-color-primary);--el-checkbox-checked-icon-color:var(--el-color-white);--el-checkbox-input-border-color-hover:var(--el-color-primary)}.el-checkbox{color:var(--el-checkbox-text-color);font-weight:var(--el-checkbox-font-weight);font-size:var(--el-font-size-base);position:relative;cursor:pointer;display:inline-flex;align-items:center;white-space:nowrap;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;margin-right:30px;height:var(--el-checkbox-height,32px)}.el-checkbox.is-disabled{cursor:not-allowed}.el-checkbox.is-bordered{padding:0 15px 0 9px;border-radius:var(--el-border-radius-base);border:var(--el-border);box-sizing:border-box}.el-checkbox.is-bordered.is-checked{border-color:var(--el-color-primary)}.el-checkbox.is-bordered.is-disabled{border-color:var(--el-border-color-lighter)}.el-checkbox.is-bordered.el-checkbox--large{padding:0 19px 0 11px;border-radius:var(--el-border-radius-base)}.el-checkbox.is-bordered.el-checkbox--large .el-checkbox__label{font-size:var(--el-font-size-base)}.el-checkbox.is-bordered.el-checkbox--large .el-checkbox__inner{height:14px;width:14px}.el-checkbox.is-bordered.el-checkbox--small{padding:0 11px 0 7px;border-radius:calc(var(--el-border-radius-base) - 1px)}.el-checkbox.is-bordered.el-checkbox--small .el-checkbox__label{font-size:12px}.el-checkbox.is-bordered.el-checkbox--small .el-checkbox__inner{height:12px;width:12px}.el-checkbox.is-bordered.el-checkbox--small .el-checkbox__inner:after{height:6px;width:2px}.el-checkbox input:focus-visible+.el-checkbox__inner{outline:2px solid var(--el-checkbox-input-border-color-hover);outline-offset:1px;border-radius:var(--el-checkbox-border-radius)}.el-checkbox__input{white-space:nowrap;cursor:pointer;outline:0;display:inline-flex;position:relative}.el-checkbox__input.is-disabled .el-checkbox__inner{background-color:var(--el-checkbox-disabled-input-fill);border-color:var(--el-checkbox-disabled-border-color);cursor:not-allowed}.el-checkbox__input.is-disabled .el-checkbox__inner:after{cursor:not-allowed;border-color:var(--el-checkbox-disabled-icon-color)}.el-checkbox__input.is-disabled.is-checked .el-checkbox__inner{background-color:var(--el-checkbox-disabled-checked-input-fill);border-color:var(--el-checkbox-disabled-checked-input-border-color)}.el-checkbox__input.is-disabled.is-checked .el-checkbox__inner:after{border-color:var(--el-checkbox-disabled-checked-icon-color)}.el-checkbox__input.is-disabled.is-indeterminate .el-checkbox__inner{background-color:var(--el-checkbox-disabled-checked-input-fill);border-color:var(--el-checkbox-disabled-checked-input-border-color)}.el-checkbox__input.is-disabled.is-indeterminate .el-checkbox__inner:before{background-color:var(--el-checkbox-disabled-checked-icon-color);border-color:var(--el-checkbox-disabled-checked-icon-color)}.el-checkbox__input.is-disabled+span.el-checkbox__label{color:var(--el-disabled-text-color);cursor:not-allowed}.el-checkbox__input.is-checked .el-checkbox__inner{background-color:var(--el-checkbox-checked-bg-color);border-color:var(--el-checkbox-checked-input-border-color)}.el-checkbox__input.is-checked .el-checkbox__inner:after{transform:rotate(45deg) scaleY(1);border-color:var(--el-checkbox-checked-icon-color)}.el-checkbox__input.is-checked+.el-checkbox__label{color:var(--el-checkbox-checked-text-color)}.el-checkbox__input.is-focus:not(.is-checked) .el-checkbox__original:not(:focus-visible){border-color:var(--el-checkbox-input-border-color-hover)}.el-checkbox__input.is-indeterminate .el-checkbox__inner{background-color:var(--el-checkbox-checked-bg-color);border-color:var(--el-checkbox-checked-input-border-color)}.el-checkbox__input.is-indeterminate .el-checkbox__inner:before{content:"";position:absolute;display:block;background-color:var(--el-checkbox-checked-icon-color);height:2px;transform:scale(.5);left:0;right:0;top:5px}.el-checkbox__input.is-indeterminate .el-checkbox__inner:after{display:none}.el-checkbox__inner{display:inline-block;position:relative;border:var(--el-checkbox-input-border);border-radius:var(--el-checkbox-border-radius);box-sizing:border-box;width:var(--el-checkbox-input-width);height:var(--el-checkbox-input-height);background-color:var(--el-checkbox-bg-color);z-index:var(--el-index-normal);transition:border-color .25s cubic-bezier(.71,-.46,.29,1.46),background-color .25s cubic-bezier(.71,-.46,.29,1.46),outline .25s cubic-bezier(.71,-.46,.29,1.46)}.el-checkbox__inner:hover{border-color:var(--el-checkbox-input-border-color-hover)}.el-checkbox__inner:after{box-sizing:content-box;content:"";border:1px solid transparent;border-left:0;border-top:0;height:7px;left:4px;position:absolute;top:1px;transform:rotate(45deg) scaleY(0);width:3px;transition:transform .15s ease-in 50ms;transform-origin:center}.el-checkbox__original{opacity:0;outline:0;position:absolute;margin:0;width:0;height:0;z-index:-1}.el-checkbox__label{display:inline-block;padding-left:8px;line-height:1;font-size:var(--el-checkbox-font-size)}.el-checkbox.el-checkbox--large{height:40px}.el-checkbox.el-checkbox--large .el-checkbox__label{font-size:14px}.el-checkbox.el-checkbox--large .el-checkbox__inner{width:14px;height:14px}.el-checkbox.el-checkbox--small{height:24px}.el-checkbox.el-checkbox--small .el-checkbox__label{font-size:12px}.el-checkbox.el-checkbox--small .el-checkbox__inner{width:12px;height:12px}.el-checkbox.el-checkbox--small .el-checkbox__input.is-indeterminate .el-checkbox__inner:before{top:4px}.el-checkbox.el-checkbox--small .el-checkbox__inner:after{width:2px;height:6px}.el-checkbox:last-of-type{margin-right:0}[class*=el-col-]{box-sizing:border-box}[class*=el-col-].is-guttered{display:block;min-height:1px}.el-col-0,.el-col-0.is-guttered{display:none}.el-col-0{max-width:0%;flex:0 0 0%}.el-col-offset-0{margin-left:0}.el-col-pull-0{position:relative;right:0}.el-col-push-0{position:relative;left:0}.el-col-1{max-width:4.1666666667%;flex:0 0 4.1666666667%}.el-col-offset-1{margin-left:4.1666666667%}.el-col-pull-1{position:relative;right:4.1666666667%}.el-col-push-1{position:relative;left:4.1666666667%}.el-col-2{max-width:8.3333333333%;flex:0 0 8.3333333333%}.el-col-offset-2{margin-left:8.3333333333%}.el-col-pull-2{position:relative;right:8.3333333333%}.el-col-push-2{position:relative;left:8.3333333333%}.el-col-3{max-width:12.5%;flex:0 0 12.5%}.el-col-offset-3{margin-left:12.5%}.el-col-pull-3{position:relative;right:12.5%}.el-col-push-3{position:relative;left:12.5%}.el-col-4{max-width:16.6666666667%;flex:0 0 16.6666666667%}.el-col-offset-4{margin-left:16.6666666667%}.el-col-pull-4{position:relative;right:16.6666666667%}.el-col-push-4{position:relative;left:16.6666666667%}.el-col-5{max-width:20.8333333333%;flex:0 0 20.8333333333%}.el-col-offset-5{margin-left:20.8333333333%}.el-col-pull-5{position:relative;right:20.8333333333%}.el-col-push-5{position:relative;left:20.8333333333%}.el-col-6{max-width:25%;flex:0 0 25%}.el-col-offset-6{margin-left:25%}.el-col-pull-6{position:relative;right:25%}.el-col-push-6{position:relative;left:25%}.el-col-7{max-width:29.1666666667%;flex:0 0 29.1666666667%}.el-col-offset-7{margin-left:29.1666666667%}.el-col-pull-7{position:relative;right:29.1666666667%}.el-col-push-7{position:relative;left:29.1666666667%}.el-col-8{max-width:33.3333333333%;flex:0 0 33.3333333333%}.el-col-offset-8{margin-left:33.3333333333%}.el-col-pull-8{position:relative;right:33.3333333333%}.el-col-push-8{position:relative;left:33.3333333333%}.el-col-9{max-width:37.5%;flex:0 0 37.5%}.el-col-offset-9{margin-left:37.5%}.el-col-pull-9{position:relative;right:37.5%}.el-col-push-9{position:relative;left:37.5%}.el-col-10{max-width:41.6666666667%;flex:0 0 41.6666666667%}.el-col-offset-10{margin-left:41.6666666667%}.el-col-pull-10{position:relative;right:41.6666666667%}.el-col-push-10{position:relative;left:41.6666666667%}.el-col-11{max-width:45.8333333333%;flex:0 0 45.8333333333%}.el-col-offset-11{margin-left:45.8333333333%}.el-col-pull-11{position:relative;right:45.8333333333%}.el-col-push-11{position:relative;left:45.8333333333%}.el-col-12{max-width:50%;flex:0 0 50%}.el-col-offset-12{margin-left:50%}.el-col-pull-12{position:relative;right:50%}.el-col-push-12{position:relative;left:50%}.el-col-13{max-width:54.1666666667%;flex:0 0 54.1666666667%}.el-col-offset-13{margin-left:54.1666666667%}.el-col-pull-13{position:relative;right:54.1666666667%}.el-col-push-13{position:relative;left:54.1666666667%}.el-col-14{max-width:58.3333333333%;flex:0 0 58.3333333333%}.el-col-offset-14{margin-left:58.3333333333%}.el-col-pull-14{position:relative;right:58.3333333333%}.el-col-push-14{position:relative;left:58.3333333333%}.el-col-15{max-width:62.5%;flex:0 0 62.5%}.el-col-offset-15{margin-left:62.5%}.el-col-pull-15{position:relative;right:62.5%}.el-col-push-15{position:relative;left:62.5%}.el-col-16{max-width:66.6666666667%;flex:0 0 66.6666666667%}.el-col-offset-16{margin-left:66.6666666667%}.el-col-pull-16{position:relative;right:66.6666666667%}.el-col-push-16{position:relative;left:66.6666666667%}.el-col-17{max-width:70.8333333333%;flex:0 0 70.8333333333%}.el-col-offset-17{margin-left:70.8333333333%}.el-col-pull-17{position:relative;right:70.8333333333%}.el-col-push-17{position:relative;left:70.8333333333%}.el-col-18{max-width:75%;flex:0 0 75%}.el-col-offset-18{margin-left:75%}.el-col-pull-18{position:relative;right:75%}.el-col-push-18{position:relative;left:75%}.el-col-19{max-width:79.1666666667%;flex:0 0 79.1666666667%}.el-col-offset-19{margin-left:79.1666666667%}.el-col-pull-19{position:relative;right:79.1666666667%}.el-col-push-19{position:relative;left:79.1666666667%}.el-col-20{max-width:83.3333333333%;flex:0 0 83.3333333333%}.el-col-offset-20{margin-left:83.3333333333%}.el-col-pull-20{position:relative;right:83.3333333333%}.el-col-push-20{position:relative;left:83.3333333333%}.el-col-21{max-width:87.5%;flex:0 0 87.5%}.el-col-offset-21{margin-left:87.5%}.el-col-pull-21{position:relative;right:87.5%}.el-col-push-21{position:relative;left:87.5%}.el-col-22{max-width:91.6666666667%;flex:0 0 91.6666666667%}.el-col-offset-22{margin-left:91.6666666667%}.el-col-pull-22{position:relative;right:91.6666666667%}.el-col-push-22{position:relative;left:91.6666666667%}.el-col-23{max-width:95.8333333333%;flex:0 0 95.8333333333%}.el-col-offset-23{margin-left:95.8333333333%}.el-col-pull-23{position:relative;right:95.8333333333%}.el-col-push-23{position:relative;left:95.8333333333%}.el-col-24{max-width:100%;flex:0 0 100%}.el-col-offset-24{margin-left:100%}.el-col-pull-24{position:relative;right:100%}.el-col-push-24{position:relative;left:100%}@media only screen and (max-width:768px){.el-col-xs-0,.el-col-xs-0.is-guttered{display:none}.el-col-xs-0{max-width:0%;flex:0 0 0%}.el-col-xs-offset-0{margin-left:0}.el-col-xs-pull-0{position:relative;right:0}.el-col-xs-push-0{position:relative;left:0}.el-col-xs-1{display:block;max-width:4.1666666667%;flex:0 0 4.1666666667%}.el-col-xs-offset-1{margin-left:4.1666666667%}.el-col-xs-pull-1{position:relative;right:4.1666666667%}.el-col-xs-push-1{position:relative;left:4.1666666667%}.el-col-xs-2{display:block;max-width:8.3333333333%;flex:0 0 8.3333333333%}.el-col-xs-offset-2{margin-left:8.3333333333%}.el-col-xs-pull-2{position:relative;right:8.3333333333%}.el-col-xs-push-2{position:relative;left:8.3333333333%}.el-col-xs-3{display:block;max-width:12.5%;flex:0 0 12.5%}.el-col-xs-offset-3{margin-left:12.5%}.el-col-xs-pull-3{position:relative;right:12.5%}.el-col-xs-push-3{position:relative;left:12.5%}.el-col-xs-4{display:block;max-width:16.6666666667%;flex:0 0 16.6666666667%}.el-col-xs-offset-4{margin-left:16.6666666667%}.el-col-xs-pull-4{position:relative;right:16.6666666667%}.el-col-xs-push-4{position:relative;left:16.6666666667%}.el-col-xs-5{display:block;max-width:20.8333333333%;flex:0 0 20.8333333333%}.el-col-xs-offset-5{margin-left:20.8333333333%}.el-col-xs-pull-5{position:relative;right:20.8333333333%}.el-col-xs-push-5{position:relative;left:20.8333333333%}.el-col-xs-6{display:block;max-width:25%;flex:0 0 25%}.el-col-xs-offset-6{margin-left:25%}.el-col-xs-pull-6{position:relative;right:25%}.el-col-xs-push-6{position:relative;left:25%}.el-col-xs-7{display:block;max-width:29.1666666667%;flex:0 0 29.1666666667%}.el-col-xs-offset-7{margin-left:29.1666666667%}.el-col-xs-pull-7{position:relative;right:29.1666666667%}.el-col-xs-push-7{position:relative;left:29.1666666667%}.el-col-xs-8{display:block;max-width:33.3333333333%;flex:0 0 33.3333333333%}.el-col-xs-offset-8{margin-left:33.3333333333%}.el-col-xs-pull-8{position:relative;right:33.3333333333%}.el-col-xs-push-8{position:relative;left:33.3333333333%}.el-col-xs-9{display:block;max-width:37.5%;flex:0 0 37.5%}.el-col-xs-offset-9{margin-left:37.5%}.el-col-xs-pull-9{position:relative;right:37.5%}.el-col-xs-push-9{position:relative;left:37.5%}.el-col-xs-10{display:block;max-width:41.6666666667%;flex:0 0 41.6666666667%}.el-col-xs-offset-10{margin-left:41.6666666667%}.el-col-xs-pull-10{position:relative;right:41.6666666667%}.el-col-xs-push-10{position:relative;left:41.6666666667%}.el-col-xs-11{display:block;max-width:45.8333333333%;flex:0 0 45.8333333333%}.el-col-xs-offset-11{margin-left:45.8333333333%}.el-col-xs-pull-11{position:relative;right:45.8333333333%}.el-col-xs-push-11{position:relative;left:45.8333333333%}.el-col-xs-12{display:block;max-width:50%;flex:0 0 50%}.el-col-xs-offset-12{margin-left:50%}.el-col-xs-pull-12{position:relative;right:50%}.el-col-xs-push-12{position:relative;left:50%}.el-col-xs-13{display:block;max-width:54.1666666667%;flex:0 0 54.1666666667%}.el-col-xs-offset-13{margin-left:54.1666666667%}.el-col-xs-pull-13{position:relative;right:54.1666666667%}.el-col-xs-push-13{position:relative;left:54.1666666667%}.el-col-xs-14{display:block;max-width:58.3333333333%;flex:0 0 58.3333333333%}.el-col-xs-offset-14{margin-left:58.3333333333%}.el-col-xs-pull-14{position:relative;right:58.3333333333%}.el-col-xs-push-14{position:relative;left:58.3333333333%}.el-col-xs-15{display:block;max-width:62.5%;flex:0 0 62.5%}.el-col-xs-offset-15{margin-left:62.5%}.el-col-xs-pull-15{position:relative;right:62.5%}.el-col-xs-push-15{position:relative;left:62.5%}.el-col-xs-16{display:block;max-width:66.6666666667%;flex:0 0 66.6666666667%}.el-col-xs-offset-16{margin-left:66.6666666667%}.el-col-xs-pull-16{position:relative;right:66.6666666667%}.el-col-xs-push-16{position:relative;left:66.6666666667%}.el-col-xs-17{display:block;max-width:70.8333333333%;flex:0 0 70.8333333333%}.el-col-xs-offset-17{margin-left:70.8333333333%}.el-col-xs-pull-17{position:relative;right:70.8333333333%}.el-col-xs-push-17{position:relative;left:70.8333333333%}.el-col-xs-18{display:block;max-width:75%;flex:0 0 75%}.el-col-xs-offset-18{margin-left:75%}.el-col-xs-pull-18{position:relative;right:75%}.el-col-xs-push-18{position:relative;left:75%}.el-col-xs-19{display:block;max-width:79.1666666667%;flex:0 0 79.1666666667%}.el-col-xs-offset-19{margin-left:79.1666666667%}.el-col-xs-pull-19{position:relative;right:79.1666666667%}.el-col-xs-push-19{position:relative;left:79.1666666667%}.el-col-xs-20{display:block;max-width:83.3333333333%;flex:0 0 83.3333333333%}.el-col-xs-offset-20{margin-left:83.3333333333%}.el-col-xs-pull-20{position:relative;right:83.3333333333%}.el-col-xs-push-20{position:relative;left:83.3333333333%}.el-col-xs-21{display:block;max-width:87.5%;flex:0 0 87.5%}.el-col-xs-offset-21{margin-left:87.5%}.el-col-xs-pull-21{position:relative;right:87.5%}.el-col-xs-push-21{position:relative;left:87.5%}.el-col-xs-22{display:block;max-width:91.6666666667%;flex:0 0 91.6666666667%}.el-col-xs-offset-22{margin-left:91.6666666667%}.el-col-xs-pull-22{position:relative;right:91.6666666667%}.el-col-xs-push-22{position:relative;left:91.6666666667%}.el-col-xs-23{display:block;max-width:95.8333333333%;flex:0 0 95.8333333333%}.el-col-xs-offset-23{margin-left:95.8333333333%}.el-col-xs-pull-23{position:relative;right:95.8333333333%}.el-col-xs-push-23{position:relative;left:95.8333333333%}.el-col-xs-24{display:block;max-width:100%;flex:0 0 100%}.el-col-xs-offset-24{margin-left:100%}.el-col-xs-pull-24{position:relative;right:100%}.el-col-xs-push-24{position:relative;left:100%}}@media only screen and (min-width:768px){.el-col-sm-0,.el-col-sm-0.is-guttered{display:none}.el-col-sm-0{max-width:0%;flex:0 0 0%}.el-col-sm-offset-0{margin-left:0}.el-col-sm-pull-0{position:relative;right:0}.el-col-sm-push-0{position:relative;left:0}.el-col-sm-1{display:block;max-width:4.1666666667%;flex:0 0 4.1666666667%}.el-col-sm-offset-1{margin-left:4.1666666667%}.el-col-sm-pull-1{position:relative;right:4.1666666667%}.el-col-sm-push-1{position:relative;left:4.1666666667%}.el-col-sm-2{display:block;max-width:8.3333333333%;flex:0 0 8.3333333333%}.el-col-sm-offset-2{margin-left:8.3333333333%}.el-col-sm-pull-2{position:relative;right:8.3333333333%}.el-col-sm-push-2{position:relative;left:8.3333333333%}.el-col-sm-3{display:block;max-width:12.5%;flex:0 0 12.5%}.el-col-sm-offset-3{margin-left:12.5%}.el-col-sm-pull-3{position:relative;right:12.5%}.el-col-sm-push-3{position:relative;left:12.5%}.el-col-sm-4{display:block;max-width:16.6666666667%;flex:0 0 16.6666666667%}.el-col-sm-offset-4{margin-left:16.6666666667%}.el-col-sm-pull-4{position:relative;right:16.6666666667%}.el-col-sm-push-4{position:relative;left:16.6666666667%}.el-col-sm-5{display:block;max-width:20.8333333333%;flex:0 0 20.8333333333%}.el-col-sm-offset-5{margin-left:20.8333333333%}.el-col-sm-pull-5{position:relative;right:20.8333333333%}.el-col-sm-push-5{position:relative;left:20.8333333333%}.el-col-sm-6{display:block;max-width:25%;flex:0 0 25%}.el-col-sm-offset-6{margin-left:25%}.el-col-sm-pull-6{position:relative;right:25%}.el-col-sm-push-6{position:relative;left:25%}.el-col-sm-7{display:block;max-width:29.1666666667%;flex:0 0 29.1666666667%}.el-col-sm-offset-7{margin-left:29.1666666667%}.el-col-sm-pull-7{position:relative;right:29.1666666667%}.el-col-sm-push-7{position:relative;left:29.1666666667%}.el-col-sm-8{display:block;max-width:33.3333333333%;flex:0 0 33.3333333333%}.el-col-sm-offset-8{margin-left:33.3333333333%}.el-col-sm-pull-8{position:relative;right:33.3333333333%}.el-col-sm-push-8{position:relative;left:33.3333333333%}.el-col-sm-9{display:block;max-width:37.5%;flex:0 0 37.5%}.el-col-sm-offset-9{margin-left:37.5%}.el-col-sm-pull-9{position:relative;right:37.5%}.el-col-sm-push-9{position:relative;left:37.5%}.el-col-sm-10{display:block;max-width:41.6666666667%;flex:0 0 41.6666666667%}.el-col-sm-offset-10{margin-left:41.6666666667%}.el-col-sm-pull-10{position:relative;right:41.6666666667%}.el-col-sm-push-10{position:relative;left:41.6666666667%}.el-col-sm-11{display:block;max-width:45.8333333333%;flex:0 0 45.8333333333%}.el-col-sm-offset-11{margin-left:45.8333333333%}.el-col-sm-pull-11{position:relative;right:45.8333333333%}.el-col-sm-push-11{position:relative;left:45.8333333333%}.el-col-sm-12{display:block;max-width:50%;flex:0 0 50%}.el-col-sm-offset-12{margin-left:50%}.el-col-sm-pull-12{position:relative;right:50%}.el-col-sm-push-12{position:relative;left:50%}.el-col-sm-13{display:block;max-width:54.1666666667%;flex:0 0 54.1666666667%}.el-col-sm-offset-13{margin-left:54.1666666667%}.el-col-sm-pull-13{position:relative;right:54.1666666667%}.el-col-sm-push-13{position:relative;left:54.1666666667%}.el-col-sm-14{display:block;max-width:58.3333333333%;flex:0 0 58.3333333333%}.el-col-sm-offset-14{margin-left:58.3333333333%}.el-col-sm-pull-14{position:relative;right:58.3333333333%}.el-col-sm-push-14{position:relative;left:58.3333333333%}.el-col-sm-15{display:block;max-width:62.5%;flex:0 0 62.5%}.el-col-sm-offset-15{margin-left:62.5%}.el-col-sm-pull-15{position:relative;right:62.5%}.el-col-sm-push-15{position:relative;left:62.5%}.el-col-sm-16{display:block;max-width:66.6666666667%;flex:0 0 66.6666666667%}.el-col-sm-offset-16{margin-left:66.6666666667%}.el-col-sm-pull-16{position:relative;right:66.6666666667%}.el-col-sm-push-16{position:relative;left:66.6666666667%}.el-col-sm-17{display:block;max-width:70.8333333333%;flex:0 0 70.8333333333%}.el-col-sm-offset-17{margin-left:70.8333333333%}.el-col-sm-pull-17{position:relative;right:70.8333333333%}.el-col-sm-push-17{position:relative;left:70.8333333333%}.el-col-sm-18{display:block;max-width:75%;flex:0 0 75%}.el-col-sm-offset-18{margin-left:75%}.el-col-sm-pull-18{position:relative;right:75%}.el-col-sm-push-18{position:relative;left:75%}.el-col-sm-19{display:block;max-width:79.1666666667%;flex:0 0 79.1666666667%}.el-col-sm-offset-19{margin-left:79.1666666667%}.el-col-sm-pull-19{position:relative;right:79.1666666667%}.el-col-sm-push-19{position:relative;left:79.1666666667%}.el-col-sm-20{display:block;max-width:83.3333333333%;flex:0 0 83.3333333333%}.el-col-sm-offset-20{margin-left:83.3333333333%}.el-col-sm-pull-20{position:relative;right:83.3333333333%}.el-col-sm-push-20{position:relative;left:83.3333333333%}.el-col-sm-21{display:block;max-width:87.5%;flex:0 0 87.5%}.el-col-sm-offset-21{margin-left:87.5%}.el-col-sm-pull-21{position:relative;right:87.5%}.el-col-sm-push-21{position:relative;left:87.5%}.el-col-sm-22{display:block;max-width:91.6666666667%;flex:0 0 91.6666666667%}.el-col-sm-offset-22{margin-left:91.6666666667%}.el-col-sm-pull-22{position:relative;right:91.6666666667%}.el-col-sm-push-22{position:relative;left:91.6666666667%}.el-col-sm-23{display:block;max-width:95.8333333333%;flex:0 0 95.8333333333%}.el-col-sm-offset-23{margin-left:95.8333333333%}.el-col-sm-pull-23{position:relative;right:95.8333333333%}.el-col-sm-push-23{position:relative;left:95.8333333333%}.el-col-sm-24{display:block;max-width:100%;flex:0 0 100%}.el-col-sm-offset-24{margin-left:100%}.el-col-sm-pull-24{position:relative;right:100%}.el-col-sm-push-24{position:relative;left:100%}}@media only screen and (min-width:992px){.el-col-md-0,.el-col-md-0.is-guttered{display:none}.el-col-md-0{max-width:0%;flex:0 0 0%}.el-col-md-offset-0{margin-left:0}.el-col-md-pull-0{position:relative;right:0}.el-col-md-push-0{position:relative;left:0}.el-col-md-1{display:block;max-width:4.1666666667%;flex:0 0 4.1666666667%}.el-col-md-offset-1{margin-left:4.1666666667%}.el-col-md-pull-1{position:relative;right:4.1666666667%}.el-col-md-push-1{position:relative;left:4.1666666667%}.el-col-md-2{display:block;max-width:8.3333333333%;flex:0 0 8.3333333333%}.el-col-md-offset-2{margin-left:8.3333333333%}.el-col-md-pull-2{position:relative;right:8.3333333333%}.el-col-md-push-2{position:relative;left:8.3333333333%}.el-col-md-3{display:block;max-width:12.5%;flex:0 0 12.5%}.el-col-md-offset-3{margin-left:12.5%}.el-col-md-pull-3{position:relative;right:12.5%}.el-col-md-push-3{position:relative;left:12.5%}.el-col-md-4{display:block;max-width:16.6666666667%;flex:0 0 16.6666666667%}.el-col-md-offset-4{margin-left:16.6666666667%}.el-col-md-pull-4{position:relative;right:16.6666666667%}.el-col-md-push-4{position:relative;left:16.6666666667%}.el-col-md-5{display:block;max-width:20.8333333333%;flex:0 0 20.8333333333%}.el-col-md-offset-5{margin-left:20.8333333333%}.el-col-md-pull-5{position:relative;right:20.8333333333%}.el-col-md-push-5{position:relative;left:20.8333333333%}.el-col-md-6{display:block;max-width:25%;flex:0 0 25%}.el-col-md-offset-6{margin-left:25%}.el-col-md-pull-6{position:relative;right:25%}.el-col-md-push-6{position:relative;left:25%}.el-col-md-7{display:block;max-width:29.1666666667%;flex:0 0 29.1666666667%}.el-col-md-offset-7{margin-left:29.1666666667%}.el-col-md-pull-7{position:relative;right:29.1666666667%}.el-col-md-push-7{position:relative;left:29.1666666667%}.el-col-md-8{display:block;max-width:33.3333333333%;flex:0 0 33.3333333333%}.el-col-md-offset-8{margin-left:33.3333333333%}.el-col-md-pull-8{position:relative;right:33.3333333333%}.el-col-md-push-8{position:relative;left:33.3333333333%}.el-col-md-9{display:block;max-width:37.5%;flex:0 0 37.5%}.el-col-md-offset-9{margin-left:37.5%}.el-col-md-pull-9{position:relative;right:37.5%}.el-col-md-push-9{position:relative;left:37.5%}.el-col-md-10{display:block;max-width:41.6666666667%;flex:0 0 41.6666666667%}.el-col-md-offset-10{margin-left:41.6666666667%}.el-col-md-pull-10{position:relative;right:41.6666666667%}.el-col-md-push-10{position:relative;left:41.6666666667%}.el-col-md-11{display:block;max-width:45.8333333333%;flex:0 0 45.8333333333%}.el-col-md-offset-11{margin-left:45.8333333333%}.el-col-md-pull-11{position:relative;right:45.8333333333%}.el-col-md-push-11{position:relative;left:45.8333333333%}.el-col-md-12{display:block;max-width:50%;flex:0 0 50%}.el-col-md-offset-12{margin-left:50%}.el-col-md-pull-12{position:relative;right:50%}.el-col-md-push-12{position:relative;left:50%}.el-col-md-13{display:block;max-width:54.1666666667%;flex:0 0 54.1666666667%}.el-col-md-offset-13{margin-left:54.1666666667%}.el-col-md-pull-13{position:relative;right:54.1666666667%}.el-col-md-push-13{position:relative;left:54.1666666667%}.el-col-md-14{display:block;max-width:58.3333333333%;flex:0 0 58.3333333333%}.el-col-md-offset-14{margin-left:58.3333333333%}.el-col-md-pull-14{position:relative;right:58.3333333333%}.el-col-md-push-14{position:relative;left:58.3333333333%}.el-col-md-15{display:block;max-width:62.5%;flex:0 0 62.5%}.el-col-md-offset-15{margin-left:62.5%}.el-col-md-pull-15{position:relative;right:62.5%}.el-col-md-push-15{position:relative;left:62.5%}.el-col-md-16{display:block;max-width:66.6666666667%;flex:0 0 66.6666666667%}.el-col-md-offset-16{margin-left:66.6666666667%}.el-col-md-pull-16{position:relative;right:66.6666666667%}.el-col-md-push-16{position:relative;left:66.6666666667%}.el-col-md-17{display:block;max-width:70.8333333333%;flex:0 0 70.8333333333%}.el-col-md-offset-17{margin-left:70.8333333333%}.el-col-md-pull-17{position:relative;right:70.8333333333%}.el-col-md-push-17{position:relative;left:70.8333333333%}.el-col-md-18{display:block;max-width:75%;flex:0 0 75%}.el-col-md-offset-18{margin-left:75%}.el-col-md-pull-18{position:relative;right:75%}.el-col-md-push-18{position:relative;left:75%}.el-col-md-19{display:block;max-width:79.1666666667%;flex:0 0 79.1666666667%}.el-col-md-offset-19{margin-left:79.1666666667%}.el-col-md-pull-19{position:relative;right:79.1666666667%}.el-col-md-push-19{position:relative;left:79.1666666667%}.el-col-md-20{display:block;max-width:83.3333333333%;flex:0 0 83.3333333333%}.el-col-md-offset-20{margin-left:83.3333333333%}.el-col-md-pull-20{position:relative;right:83.3333333333%}.el-col-md-push-20{position:relative;left:83.3333333333%}.el-col-md-21{display:block;max-width:87.5%;flex:0 0 87.5%}.el-col-md-offset-21{margin-left:87.5%}.el-col-md-pull-21{position:relative;right:87.5%}.el-col-md-push-21{position:relative;left:87.5%}.el-col-md-22{display:block;max-width:91.6666666667%;flex:0 0 91.6666666667%}.el-col-md-offset-22{margin-left:91.6666666667%}.el-col-md-pull-22{position:relative;right:91.6666666667%}.el-col-md-push-22{position:relative;left:91.6666666667%}.el-col-md-23{display:block;max-width:95.8333333333%;flex:0 0 95.8333333333%}.el-col-md-offset-23{margin-left:95.8333333333%}.el-col-md-pull-23{position:relative;right:95.8333333333%}.el-col-md-push-23{position:relative;left:95.8333333333%}.el-col-md-24{display:block;max-width:100%;flex:0 0 100%}.el-col-md-offset-24{margin-left:100%}.el-col-md-pull-24{position:relative;right:100%}.el-col-md-push-24{position:relative;left:100%}}@media only screen and (min-width:1200px){.el-col-lg-0,.el-col-lg-0.is-guttered{display:none}.el-col-lg-0{max-width:0%;flex:0 0 0%}.el-col-lg-offset-0{margin-left:0}.el-col-lg-pull-0{position:relative;right:0}.el-col-lg-push-0{position:relative;left:0}.el-col-lg-1{display:block;max-width:4.1666666667%;flex:0 0 4.1666666667%}.el-col-lg-offset-1{margin-left:4.1666666667%}.el-col-lg-pull-1{position:relative;right:4.1666666667%}.el-col-lg-push-1{position:relative;left:4.1666666667%}.el-col-lg-2{display:block;max-width:8.3333333333%;flex:0 0 8.3333333333%}.el-col-lg-offset-2{margin-left:8.3333333333%}.el-col-lg-pull-2{position:relative;right:8.3333333333%}.el-col-lg-push-2{position:relative;left:8.3333333333%}.el-col-lg-3{display:block;max-width:12.5%;flex:0 0 12.5%}.el-col-lg-offset-3{margin-left:12.5%}.el-col-lg-pull-3{position:relative;right:12.5%}.el-col-lg-push-3{position:relative;left:12.5%}.el-col-lg-4{display:block;max-width:16.6666666667%;flex:0 0 16.6666666667%}.el-col-lg-offset-4{margin-left:16.6666666667%}.el-col-lg-pull-4{position:relative;right:16.6666666667%}.el-col-lg-push-4{position:relative;left:16.6666666667%}.el-col-lg-5{display:block;max-width:20.8333333333%;flex:0 0 20.8333333333%}.el-col-lg-offset-5{margin-left:20.8333333333%}.el-col-lg-pull-5{position:relative;right:20.8333333333%}.el-col-lg-push-5{position:relative;left:20.8333333333%}.el-col-lg-6{display:block;max-width:25%;flex:0 0 25%}.el-col-lg-offset-6{margin-left:25%}.el-col-lg-pull-6{position:relative;right:25%}.el-col-lg-push-6{position:relative;left:25%}.el-col-lg-7{display:block;max-width:29.1666666667%;flex:0 0 29.1666666667%}.el-col-lg-offset-7{margin-left:29.1666666667%}.el-col-lg-pull-7{position:relative;right:29.1666666667%}.el-col-lg-push-7{position:relative;left:29.1666666667%}.el-col-lg-8{display:block;max-width:33.3333333333%;flex:0 0 33.3333333333%}.el-col-lg-offset-8{margin-left:33.3333333333%}.el-col-lg-pull-8{position:relative;right:33.3333333333%}.el-col-lg-push-8{position:relative;left:33.3333333333%}.el-col-lg-9{display:block;max-width:37.5%;flex:0 0 37.5%}.el-col-lg-offset-9{margin-left:37.5%}.el-col-lg-pull-9{position:relative;right:37.5%}.el-col-lg-push-9{position:relative;left:37.5%}.el-col-lg-10{display:block;max-width:41.6666666667%;flex:0 0 41.6666666667%}.el-col-lg-offset-10{margin-left:41.6666666667%}.el-col-lg-pull-10{position:relative;right:41.6666666667%}.el-col-lg-push-10{position:relative;left:41.6666666667%}.el-col-lg-11{display:block;max-width:45.8333333333%;flex:0 0 45.8333333333%}.el-col-lg-offset-11{margin-left:45.8333333333%}.el-col-lg-pull-11{position:relative;right:45.8333333333%}.el-col-lg-push-11{position:relative;left:45.8333333333%}.el-col-lg-12{display:block;max-width:50%;flex:0 0 50%}.el-col-lg-offset-12{margin-left:50%}.el-col-lg-pull-12{position:relative;right:50%}.el-col-lg-push-12{position:relative;left:50%}.el-col-lg-13{display:block;max-width:54.1666666667%;flex:0 0 54.1666666667%}.el-col-lg-offset-13{margin-left:54.1666666667%}.el-col-lg-pull-13{position:relative;right:54.1666666667%}.el-col-lg-push-13{position:relative;left:54.1666666667%}.el-col-lg-14{display:block;max-width:58.3333333333%;flex:0 0 58.3333333333%}.el-col-lg-offset-14{margin-left:58.3333333333%}.el-col-lg-pull-14{position:relative;right:58.3333333333%}.el-col-lg-push-14{position:relative;left:58.3333333333%}.el-col-lg-15{display:block;max-width:62.5%;flex:0 0 62.5%}.el-col-lg-offset-15{margin-left:62.5%}.el-col-lg-pull-15{position:relative;right:62.5%}.el-col-lg-push-15{position:relative;left:62.5%}.el-col-lg-16{display:block;max-width:66.6666666667%;flex:0 0 66.6666666667%}.el-col-lg-offset-16{margin-left:66.6666666667%}.el-col-lg-pull-16{position:relative;right:66.6666666667%}.el-col-lg-push-16{position:relative;left:66.6666666667%}.el-col-lg-17{display:block;max-width:70.8333333333%;flex:0 0 70.8333333333%}.el-col-lg-offset-17{margin-left:70.8333333333%}.el-col-lg-pull-17{position:relative;right:70.8333333333%}.el-col-lg-push-17{position:relative;left:70.8333333333%}.el-col-lg-18{display:block;max-width:75%;flex:0 0 75%}.el-col-lg-offset-18{margin-left:75%}.el-col-lg-pull-18{position:relative;right:75%}.el-col-lg-push-18{position:relative;left:75%}.el-col-lg-19{display:block;max-width:79.1666666667%;flex:0 0 79.1666666667%}.el-col-lg-offset-19{margin-left:79.1666666667%}.el-col-lg-pull-19{position:relative;right:79.1666666667%}.el-col-lg-push-19{position:relative;left:79.1666666667%}.el-col-lg-20{display:block;max-width:83.3333333333%;flex:0 0 83.3333333333%}.el-col-lg-offset-20{margin-left:83.3333333333%}.el-col-lg-pull-20{position:relative;right:83.3333333333%}.el-col-lg-push-20{position:relative;left:83.3333333333%}.el-col-lg-21{display:block;max-width:87.5%;flex:0 0 87.5%}.el-col-lg-offset-21{margin-left:87.5%}.el-col-lg-pull-21{position:relative;right:87.5%}.el-col-lg-push-21{position:relative;left:87.5%}.el-col-lg-22{display:block;max-width:91.6666666667%;flex:0 0 91.6666666667%}.el-col-lg-offset-22{margin-left:91.6666666667%}.el-col-lg-pull-22{position:relative;right:91.6666666667%}.el-col-lg-push-22{position:relative;left:91.6666666667%}.el-col-lg-23{display:block;max-width:95.8333333333%;flex:0 0 95.8333333333%}.el-col-lg-offset-23{margin-left:95.8333333333%}.el-col-lg-pull-23{position:relative;right:95.8333333333%}.el-col-lg-push-23{position:relative;left:95.8333333333%}.el-col-lg-24{display:block;max-width:100%;flex:0 0 100%}.el-col-lg-offset-24{margin-left:100%}.el-col-lg-pull-24{position:relative;right:100%}.el-col-lg-push-24{position:relative;left:100%}}@media only screen and (min-width:1920px){.el-col-xl-0,.el-col-xl-0.is-guttered{display:none}.el-col-xl-0{max-width:0%;flex:0 0 0%}.el-col-xl-offset-0{margin-left:0}.el-col-xl-pull-0{position:relative;right:0}.el-col-xl-push-0{position:relative;left:0}.el-col-xl-1{display:block;max-width:4.1666666667%;flex:0 0 4.1666666667%}.el-col-xl-offset-1{margin-left:4.1666666667%}.el-col-xl-pull-1{position:relative;right:4.1666666667%}.el-col-xl-push-1{position:relative;left:4.1666666667%}.el-col-xl-2{display:block;max-width:8.3333333333%;flex:0 0 8.3333333333%}.el-col-xl-offset-2{margin-left:8.3333333333%}.el-col-xl-pull-2{position:relative;right:8.3333333333%}.el-col-xl-push-2{position:relative;left:8.3333333333%}.el-col-xl-3{display:block;max-width:12.5%;flex:0 0 12.5%}.el-col-xl-offset-3{margin-left:12.5%}.el-col-xl-pull-3{position:relative;right:12.5%}.el-col-xl-push-3{position:relative;left:12.5%}.el-col-xl-4{display:block;max-width:16.6666666667%;flex:0 0 16.6666666667%}.el-col-xl-offset-4{margin-left:16.6666666667%}.el-col-xl-pull-4{position:relative;right:16.6666666667%}.el-col-xl-push-4{position:relative;left:16.6666666667%}.el-col-xl-5{display:block;max-width:20.8333333333%;flex:0 0 20.8333333333%}.el-col-xl-offset-5{margin-left:20.8333333333%}.el-col-xl-pull-5{position:relative;right:20.8333333333%}.el-col-xl-push-5{position:relative;left:20.8333333333%}.el-col-xl-6{display:block;max-width:25%;flex:0 0 25%}.el-col-xl-offset-6{margin-left:25%}.el-col-xl-pull-6{position:relative;right:25%}.el-col-xl-push-6{position:relative;left:25%}.el-col-xl-7{display:block;max-width:29.1666666667%;flex:0 0 29.1666666667%}.el-col-xl-offset-7{margin-left:29.1666666667%}.el-col-xl-pull-7{position:relative;right:29.1666666667%}.el-col-xl-push-7{position:relative;left:29.1666666667%}.el-col-xl-8{display:block;max-width:33.3333333333%;flex:0 0 33.3333333333%}.el-col-xl-offset-8{margin-left:33.3333333333%}.el-col-xl-pull-8{position:relative;right:33.3333333333%}.el-col-xl-push-8{position:relative;left:33.3333333333%}.el-col-xl-9{display:block;max-width:37.5%;flex:0 0 37.5%}.el-col-xl-offset-9{margin-left:37.5%}.el-col-xl-pull-9{position:relative;right:37.5%}.el-col-xl-push-9{position:relative;left:37.5%}.el-col-xl-10{display:block;max-width:41.6666666667%;flex:0 0 41.6666666667%}.el-col-xl-offset-10{margin-left:41.6666666667%}.el-col-xl-pull-10{position:relative;right:41.6666666667%}.el-col-xl-push-10{position:relative;left:41.6666666667%}.el-col-xl-11{display:block;max-width:45.8333333333%;flex:0 0 45.8333333333%}.el-col-xl-offset-11{margin-left:45.8333333333%}.el-col-xl-pull-11{position:relative;right:45.8333333333%}.el-col-xl-push-11{position:relative;left:45.8333333333%}.el-col-xl-12{display:block;max-width:50%;flex:0 0 50%}.el-col-xl-offset-12{margin-left:50%}.el-col-xl-pull-12{position:relative;right:50%}.el-col-xl-push-12{position:relative;left:50%}.el-col-xl-13{display:block;max-width:54.1666666667%;flex:0 0 54.1666666667%}.el-col-xl-offset-13{margin-left:54.1666666667%}.el-col-xl-pull-13{position:relative;right:54.1666666667%}.el-col-xl-push-13{position:relative;left:54.1666666667%}.el-col-xl-14{display:block;max-width:58.3333333333%;flex:0 0 58.3333333333%}.el-col-xl-offset-14{margin-left:58.3333333333%}.el-col-xl-pull-14{position:relative;right:58.3333333333%}.el-col-xl-push-14{position:relative;left:58.3333333333%}.el-col-xl-15{display:block;max-width:62.5%;flex:0 0 62.5%}.el-col-xl-offset-15{margin-left:62.5%}.el-col-xl-pull-15{position:relative;right:62.5%}.el-col-xl-push-15{position:relative;left:62.5%}.el-col-xl-16{display:block;max-width:66.6666666667%;flex:0 0 66.6666666667%}.el-col-xl-offset-16{margin-left:66.6666666667%}.el-col-xl-pull-16{position:relative;right:66.6666666667%}.el-col-xl-push-16{position:relative;left:66.6666666667%}.el-col-xl-17{display:block;max-width:70.8333333333%;flex:0 0 70.8333333333%}.el-col-xl-offset-17{margin-left:70.8333333333%}.el-col-xl-pull-17{position:relative;right:70.8333333333%}.el-col-xl-push-17{position:relative;left:70.8333333333%}.el-col-xl-18{display:block;max-width:75%;flex:0 0 75%}.el-col-xl-offset-18{margin-left:75%}.el-col-xl-pull-18{position:relative;right:75%}.el-col-xl-push-18{position:relative;left:75%}.el-col-xl-19{display:block;max-width:79.1666666667%;flex:0 0 79.1666666667%}.el-col-xl-offset-19{margin-left:79.1666666667%}.el-col-xl-pull-19{position:relative;right:79.1666666667%}.el-col-xl-push-19{position:relative;left:79.1666666667%}.el-col-xl-20{display:block;max-width:83.3333333333%;flex:0 0 83.3333333333%}.el-col-xl-offset-20{margin-left:83.3333333333%}.el-col-xl-pull-20{position:relative;right:83.3333333333%}.el-col-xl-push-20{position:relative;left:83.3333333333%}.el-col-xl-21{display:block;max-width:87.5%;flex:0 0 87.5%}.el-col-xl-offset-21{margin-left:87.5%}.el-col-xl-pull-21{position:relative;right:87.5%}.el-col-xl-push-21{position:relative;left:87.5%}.el-col-xl-22{display:block;max-width:91.6666666667%;flex:0 0 91.6666666667%}.el-col-xl-offset-22{margin-left:91.6666666667%}.el-col-xl-pull-22{position:relative;right:91.6666666667%}.el-col-xl-push-22{position:relative;left:91.6666666667%}.el-col-xl-23{display:block;max-width:95.8333333333%;flex:0 0 95.8333333333%}.el-col-xl-offset-23{margin-left:95.8333333333%}.el-col-xl-pull-23{position:relative;right:95.8333333333%}.el-col-xl-push-23{position:relative;left:95.8333333333%}.el-col-xl-24{display:block;max-width:100%;flex:0 0 100%}.el-col-xl-offset-24{margin-left:100%}.el-col-xl-pull-24{position:relative;right:100%}.el-col-xl-push-24{position:relative;left:100%}}.el-collapse{--el-collapse-border-color:var(--el-border-color-lighter);--el-collapse-header-height:48px;--el-collapse-header-bg-color:var(--el-fill-color-blank);--el-collapse-header-text-color:var(--el-text-color-primary);--el-collapse-header-font-size:13px;--el-collapse-content-bg-color:var(--el-fill-color-blank);--el-collapse-content-font-size:13px;--el-collapse-content-text-color:var(--el-text-color-primary);border-top:1px solid var(--el-collapse-border-color);border-bottom:1px solid var(--el-collapse-border-color)}.el-collapse-item.is-disabled .el-collapse-item__header{color:var(--el-text-color-disabled);cursor:not-allowed}.el-collapse-item__header{width:100%;padding:0;border:none;display:flex;align-items:center;height:var(--el-collapse-header-height);line-height:var(--el-collapse-header-height);background-color:var(--el-collapse-header-bg-color);color:var(--el-collapse-header-text-color);cursor:pointer;border-bottom:1px solid var(--el-collapse-border-color);font-size:var(--el-collapse-header-font-size);font-weight:500;transition:border-bottom-color var(--el-transition-duration);outline:0}.el-collapse-item__arrow{margin:0 8px 0 auto;transition:transform var(--el-transition-duration);font-weight:300}.el-collapse-item__arrow.is-active{transform:rotate(90deg)}.el-collapse-item__header.focusing:focus:not(:hover){color:var(--el-color-primary)}.el-collapse-item__header.is-active{border-bottom-color:transparent}.el-collapse-item__wrap{will-change:height;background-color:var(--el-collapse-content-bg-color);overflow:hidden;box-sizing:border-box;border-bottom:1px solid var(--el-collapse-border-color)}.el-collapse-item__content{padding-bottom:25px;font-size:var(--el-collapse-content-font-size);color:var(--el-collapse-content-text-color);line-height:1.7692307692}.el-collapse-item:last-child{margin-bottom:-1px}.el-color-predefine{display:flex;font-size:12px;margin-top:8px;width:280px}.el-color-predefine__colors{display:flex;flex:1;flex-wrap:wrap}.el-color-predefine__color-selector{margin:0 0 8px 8px;width:20px;height:20px;border-radius:4px;cursor:pointer}.el-color-predefine__color-selector:nth-child(10n+1){margin-left:0}.el-color-predefine__color-selector.selected{box-shadow:0 0 3px 2px var(--el-color-primary)}.el-color-predefine__color-selector>div{display:flex;height:100%;border-radius:3px}.el-color-predefine__color-selector.is-alpha{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==)}.el-color-hue-slider{position:relative;box-sizing:border-box;width:280px;height:12px;background-color:red;padding:0 2px;float:right}.el-color-hue-slider__bar{position:relative;background:linear-gradient(to right,red 0,#ff0 17%,#0f0 33%,#0ff,#00f 67%,#f0f 83%,red);height:100%}.el-color-hue-slider__thumb{position:absolute;cursor:pointer;box-sizing:border-box;left:0;top:0;width:4px;height:100%;border-radius:1px;background:#fff;border:1px solid var(--el-border-color-lighter);box-shadow:0 0 2px #0009;z-index:1}.el-color-hue-slider.is-vertical{width:12px;height:180px;padding:2px 0}.el-color-hue-slider.is-vertical .el-color-hue-slider__bar{background:linear-gradient(to bottom,red 0,#ff0 17%,#0f0 33%,#0ff,#00f 67%,#f0f 83%,red)}.el-color-hue-slider.is-vertical .el-color-hue-slider__thumb{left:0;top:0;width:100%;height:4px}.el-color-svpanel{position:relative;width:280px;height:180px}.el-color-svpanel__black,.el-color-svpanel__white{position:absolute;top:0;left:0;right:0;bottom:0}.el-color-svpanel__white{background:linear-gradient(to right,#fff,#fff0)}.el-color-svpanel__black{background:linear-gradient(to top,#000,#0000)}.el-color-svpanel__cursor{position:absolute}.el-color-svpanel__cursor>div{cursor:head;width:4px;height:4px;box-shadow:0 0 0 1.5px #fff,inset 0 0 1px 1px #0000004d,0 0 1px 2px #0006;border-radius:50%;transform:translate(-2px,-2px)}.el-color-alpha-slider{position:relative;box-sizing:border-box;width:280px;height:12px;background-image:linear-gradient(45deg,var(--el-color-picker-alpha-bg-a) 25%,var(--el-color-picker-alpha-bg-b) 25%),linear-gradient(135deg,var(--el-color-picker-alpha-bg-a) 25%,var(--el-color-picker-alpha-bg-b) 25%),linear-gradient(45deg,var(--el-color-picker-alpha-bg-b) 75%,var(--el-color-picker-alpha-bg-a) 75%),linear-gradient(135deg,var(--el-color-picker-alpha-bg-b) 75%,var(--el-color-picker-alpha-bg-a) 75%);background-size:12px 12px;background-position:0 0,6px 0,6px -6px,0 6px}.el-color-alpha-slider__bar{position:relative;background:linear-gradient(to right,rgba(255,255,255,0) 0,var(--el-bg-color) 100%);height:100%}.el-color-alpha-slider__thumb{position:absolute;cursor:pointer;box-sizing:border-box;left:0;top:0;width:4px;height:100%;border-radius:1px;background:#fff;border:1px solid var(--el-border-color-lighter);box-shadow:0 0 2px #0009;z-index:1}.el-color-alpha-slider.is-vertical{width:20px;height:180px}.el-color-alpha-slider.is-vertical .el-color-alpha-slider__bar{background:linear-gradient(to bottom,#fff0 0,#fff)}.el-color-alpha-slider.is-vertical .el-color-alpha-slider__thumb{left:0;top:0;width:100%;height:4px}.el-color-dropdown{width:300px}.el-color-dropdown__main-wrapper{margin-bottom:6px}.el-color-dropdown__main-wrapper:after{content:"";display:table;clear:both}.el-color-dropdown__btns{margin-top:12px;text-align:right}.el-color-dropdown__value{float:left;line-height:26px;font-size:12px;color:#000;width:160px}.el-color-picker{display:inline-block;position:relative;line-height:normal;outline:0}.el-color-picker:hover:not(.is-disabled,.is-focused) .el-color-picker__trigger{border-color:var(--el-border-color-hover)}.el-color-picker:focus-visible:not(.is-disabled) .el-color-picker__trigger{outline:2px solid var(--el-color-primary);outline-offset:1px}.el-color-picker.is-focused .el-color-picker__trigger{border-color:var(--el-color-primary)}.el-color-picker.is-disabled .el-color-picker__trigger{cursor:not-allowed}.el-color-picker--large{height:40px}.el-color-picker--large .el-color-picker__trigger{height:40px;width:40px}.el-color-picker--large .el-color-picker__mask{height:38px;width:38px}.el-color-picker--small{height:24px}.el-color-picker--small .el-color-picker__trigger{height:24px;width:24px}.el-color-picker--small .el-color-picker__mask{height:22px;width:22px}.el-color-picker--small .el-color-picker__empty,.el-color-picker--small .el-color-picker__icon{transform:scale(.8)}.el-color-picker__mask{height:30px;width:30px;border-radius:4px;position:absolute;top:1px;left:1px;z-index:1;cursor:not-allowed;background-color:#ffffffb3}.el-color-picker__trigger{display:inline-flex;justify-content:center;align-items:center;box-sizing:border-box;height:32px;width:32px;padding:4px;border:1px solid var(--el-border-color);border-radius:4px;font-size:0;position:relative;cursor:pointer}.el-color-picker__color{position:relative;display:block;box-sizing:border-box;border:1px solid var(--el-text-color-secondary);border-radius:var(--el-border-radius-small);width:100%;height:100%;text-align:center}.el-color-picker__color.is-alpha{background-image:linear-gradient(45deg,var(--el-color-picker-alpha-bg-a) 25%,var(--el-color-picker-alpha-bg-b) 25%),linear-gradient(135deg,var(--el-color-picker-alpha-bg-a) 25%,var(--el-color-picker-alpha-bg-b) 25%),linear-gradient(45deg,var(--el-color-picker-alpha-bg-b) 75%,var(--el-color-picker-alpha-bg-a) 75%),linear-gradient(135deg,var(--el-color-picker-alpha-bg-b) 75%,var(--el-color-picker-alpha-bg-a) 75%);background-size:12px 12px;background-position:0 0,6px 0,6px -6px,0 6px}.el-color-picker__color-inner{display:inline-flex;justify-content:center;align-items:center;width:100%;height:100%}.el-color-picker .el-color-picker__empty{font-size:12px;color:var(--el-text-color-secondary)}.el-color-picker .el-color-picker__icon{display:inline-flex;justify-content:center;align-items:center;color:#fff;font-size:12px}.el-color-picker__panel{position:absolute;z-index:10;padding:6px;box-sizing:content-box;background-color:#fff;border-radius:var(--el-border-radius-base);box-shadow:var(--el-box-shadow-light)}.el-color-picker__panel.el-popper{border:1px solid var(--el-border-color-lighter)}.el-color-picker,.el-color-picker__panel{--el-color-picker-alpha-bg-a:#ccc;--el-color-picker-alpha-bg-b:transparent}.dark .el-color-picker,.dark .el-color-picker__panel{--el-color-picker-alpha-bg-a:#333333}.el-container{display:flex;flex-direction:row;flex:1;flex-basis:auto;box-sizing:border-box;min-width:0}.el-container.is-vertical{flex-direction:column}.el-date-table{font-size:12px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.el-date-table.is-week-mode .el-date-table__row:hover .el-date-table-cell{background-color:var(--el-datepicker-inrange-bg-color)}.el-date-table.is-week-mode .el-date-table__row:hover td.available:hover{color:var(--el-datepicker-text-color)}.el-date-table.is-week-mode .el-date-table__row:hover td:first-child .el-date-table-cell{margin-left:5px;border-top-left-radius:15px;border-bottom-left-radius:15px}.el-date-table.is-week-mode .el-date-table__row:hover td:last-child .el-date-table-cell{margin-right:5px;border-top-right-radius:15px;border-bottom-right-radius:15px}.el-date-table.is-week-mode .el-date-table__row.current .el-date-table-cell{background-color:var(--el-datepicker-inrange-bg-color)}.el-date-table td{width:32px;height:30px;padding:4px 0;box-sizing:border-box;text-align:center;cursor:pointer;position:relative}.el-date-table td .el-date-table-cell{height:30px;padding:3px 0;box-sizing:border-box}.el-date-table td .el-date-table-cell .el-date-table-cell__text{width:24px;height:24px;display:block;margin:0 auto;line-height:24px;position:absolute;left:50%;transform:translate(-50%);border-radius:50%}.el-date-table td.next-month,.el-date-table td.prev-month{color:var(--el-datepicker-off-text-color)}.el-date-table td.today{position:relative}.el-date-table td.today .el-date-table-cell__text{color:var(--el-color-primary);font-weight:700}.el-date-table td.today.end-date .el-date-table-cell__text,.el-date-table td.today.start-date .el-date-table-cell__text{color:#fff}.el-date-table td.available:hover{color:var(--el-datepicker-hover-text-color)}.el-date-table td.in-range .el-date-table-cell{background-color:var(--el-datepicker-inrange-bg-color)}.el-date-table td.in-range .el-date-table-cell:hover{background-color:var(--el-datepicker-inrange-hover-bg-color)}.el-date-table td.current:not(.disabled) .el-date-table-cell__text{color:#fff;background-color:var(--el-datepicker-active-color)}.el-date-table td.current:not(.disabled):focus-visible .el-date-table-cell__text{outline:2px solid var(--el-datepicker-active-color);outline-offset:1px}.el-date-table td.end-date .el-date-table-cell,.el-date-table td.start-date .el-date-table-cell{color:#fff}.el-date-table td.end-date .el-date-table-cell__text,.el-date-table td.start-date .el-date-table-cell__text{background-color:var(--el-datepicker-active-color)}.el-date-table td.start-date .el-date-table-cell{margin-left:5px;border-top-left-radius:15px;border-bottom-left-radius:15px}.el-date-table td.end-date .el-date-table-cell{margin-right:5px;border-top-right-radius:15px;border-bottom-right-radius:15px}.el-date-table td.disabled .el-date-table-cell{background-color:var(--el-fill-color-light);opacity:1;cursor:not-allowed;color:var(--el-text-color-placeholder)}.el-date-table td.selected .el-date-table-cell{margin-left:5px;margin-right:5px;background-color:var(--el-datepicker-inrange-bg-color);border-radius:15px}.el-date-table td.selected .el-date-table-cell:hover{background-color:var(--el-datepicker-inrange-hover-bg-color)}.el-date-table td.selected .el-date-table-cell__text{background-color:var(--el-datepicker-active-color);color:#fff;border-radius:15px}.el-date-table td.week{font-size:80%;color:var(--el-datepicker-header-text-color)}.el-date-table td:focus{outline:0}.el-date-table th{padding:5px;color:var(--el-datepicker-header-text-color);font-weight:400;border-bottom:solid 1px var(--el-border-color-lighter)}.el-month-table{font-size:12px;margin:-1px;border-collapse:collapse}.el-month-table td{text-align:center;padding:8px 0;cursor:pointer}.el-month-table td div{height:48px;padding:6px 0;box-sizing:border-box}.el-month-table td.today .cell{color:var(--el-color-primary);font-weight:700}.el-month-table td.today.end-date .cell,.el-month-table td.today.start-date .cell{color:#fff}.el-month-table td.disabled .cell{background-color:var(--el-fill-color-light);cursor:not-allowed;color:var(--el-text-color-placeholder)}.el-month-table td.disabled .cell:hover{color:var(--el-text-color-placeholder)}.el-month-table td .cell{width:60px;height:36px;display:block;line-height:36px;color:var(--el-datepicker-text-color);margin:0 auto;border-radius:18px}.el-month-table td .cell:hover{color:var(--el-datepicker-hover-text-color)}.el-month-table td.in-range div{background-color:var(--el-datepicker-inrange-bg-color)}.el-month-table td.in-range div:hover{background-color:var(--el-datepicker-inrange-hover-bg-color)}.el-month-table td.end-date div,.el-month-table td.start-date div{color:#fff}.el-month-table td.end-date .cell,.el-month-table td.start-date .cell{color:#fff;background-color:var(--el-datepicker-active-color)}.el-month-table td.start-date div{border-top-left-radius:24px;border-bottom-left-radius:24px}.el-month-table td.end-date div{border-top-right-radius:24px;border-bottom-right-radius:24px}.el-month-table td.current:not(.disabled) .cell{color:var(--el-datepicker-active-color)}.el-month-table td:focus-visible{outline:0}.el-month-table td:focus-visible .cell{outline:2px solid var(--el-datepicker-active-color)}.el-year-table{font-size:12px;margin:-1px;border-collapse:collapse}.el-year-table .el-icon{color:var(--el-datepicker-icon-color)}.el-year-table td{text-align:center;padding:20px 3px;cursor:pointer}.el-year-table td.today .cell{color:var(--el-color-primary);font-weight:700}.el-year-table td.disabled .cell{background-color:var(--el-fill-color-light);cursor:not-allowed;color:var(--el-text-color-placeholder)}.el-year-table td.disabled .cell:hover{color:var(--el-text-color-placeholder)}.el-year-table td .cell{width:48px;height:36px;display:block;line-height:36px;color:var(--el-datepicker-text-color);border-radius:18px;margin:0 auto}.el-year-table td .cell:hover{color:var(--el-datepicker-hover-text-color)}.el-year-table td.current:not(.disabled) .cell{color:var(--el-datepicker-active-color)}.el-year-table td:focus-visible{outline:0}.el-year-table td:focus-visible .cell{outline:2px solid var(--el-datepicker-active-color)}.el-time-spinner.has-seconds .el-time-spinner__wrapper{width:33.3%}.el-time-spinner__wrapper{max-height:192px;overflow:auto;display:inline-block;width:50%;vertical-align:top;position:relative}.el-time-spinner__wrapper.el-scrollbar__wrap:not(.el-scrollbar__wrap--hidden-default){padding-bottom:15px}.el-time-spinner__wrapper.is-arrow{box-sizing:border-box;text-align:center;overflow:hidden}.el-time-spinner__wrapper.is-arrow .el-time-spinner__list{transform:translateY(-32px)}.el-time-spinner__wrapper.is-arrow .el-time-spinner__item:hover:not(.is-disabled):not(.is-active){background:var(--el-fill-color-light);cursor:default}.el-time-spinner__arrow{font-size:12px;color:var(--el-text-color-secondary);position:absolute;left:0;width:100%;z-index:var(--el-index-normal);text-align:center;height:30px;line-height:30px;cursor:pointer}.el-time-spinner__arrow:hover{color:var(--el-color-primary)}.el-time-spinner__arrow.arrow-up{top:10px}.el-time-spinner__arrow.arrow-down{bottom:10px}.el-time-spinner__input.el-input{width:70%}.el-time-spinner__input.el-input .el-input__inner{padding:0;text-align:center}.el-time-spinner__list{padding:0;margin:0;list-style:none;text-align:center}.el-time-spinner__list:after,.el-time-spinner__list:before{content:"";display:block;width:100%;height:80px}.el-time-spinner__item{height:32px;line-height:32px;font-size:12px;color:var(--el-text-color-regular)}.el-time-spinner__item:hover:not(.is-disabled):not(.is-active){background:var(--el-fill-color-light);cursor:pointer}.el-time-spinner__item.is-active:not(.is-disabled){color:var(--el-text-color-primary);font-weight:700}.el-time-spinner__item.is-disabled{color:var(--el-text-color-placeholder);cursor:not-allowed}.el-picker__popper{--el-datepicker-border-color:var(--el-disabled-border-color)}.el-picker__popper.el-popper{background:var(--el-bg-color-overlay);border:1px solid var(--el-datepicker-border-color);box-shadow:var(--el-box-shadow-light)}.el-picker__popper.el-popper .el-popper__arrow:before{border:1px solid var(--el-datepicker-border-color)}.el-picker__popper.el-popper[data-popper-placement^=top] .el-popper__arrow:before{border-top-color:transparent;border-left-color:transparent}.el-picker__popper.el-popper[data-popper-placement^=bottom] .el-popper__arrow:before{border-bottom-color:transparent;border-right-color:transparent}.el-picker__popper.el-popper[data-popper-placement^=left] .el-popper__arrow:before{border-left-color:transparent;border-bottom-color:transparent}.el-picker__popper.el-popper[data-popper-placement^=right] .el-popper__arrow:before{border-right-color:transparent;border-top-color:transparent}.el-date-editor{--el-date-editor-width:220px;--el-date-editor-monthrange-width:300px;--el-date-editor-daterange-width:350px;--el-date-editor-datetimerange-width:400px;--el-input-text-color:var(--el-text-color-regular);--el-input-border:var(--el-border);--el-input-hover-border:var(--el-border-color-hover);--el-input-focus-border:var(--el-color-primary);--el-input-transparent-border:0 0 0 1px transparent inset;--el-input-border-color:var(--el-border-color);--el-input-border-radius:var(--el-border-radius-base);--el-input-bg-color:var(--el-fill-color-blank);--el-input-icon-color:var(--el-text-color-placeholder);--el-input-placeholder-color:var(--el-text-color-placeholder);--el-input-hover-border-color:var(--el-border-color-hover);--el-input-clear-hover-color:var(--el-text-color-secondary);--el-input-focus-border-color:var(--el-color-primary);--el-input-width:100%;position:relative;text-align:left;vertical-align:middle}.el-date-editor.el-input__wrapper{box-shadow:0 0 0 1px var(--el-input-border-color,var(--el-border-color)) inset}.el-date-editor.el-input__wrapper:hover{box-shadow:0 0 0 1px var(--el-input-hover-border-color) inset}.el-date-editor.el-input,.el-date-editor.el-input__wrapper{width:var(--el-date-editor-width);height:var(--el-input-height,var(--el-component-size))}.el-date-editor--monthrange{--el-date-editor-width:var(--el-date-editor-monthrange-width)}.el-date-editor--daterange,.el-date-editor--timerange{--el-date-editor-width:var(--el-date-editor-daterange-width)}.el-date-editor--datetimerange{--el-date-editor-width:var(--el-date-editor-datetimerange-width)}.el-date-editor--dates .el-input__wrapper{text-overflow:ellipsis;white-space:nowrap}.el-date-editor .close-icon,.el-date-editor .clear-icon{cursor:pointer}.el-date-editor .clear-icon:hover{color:var(--el-text-color-secondary)}.el-date-editor .el-range__icon{height:inherit;font-size:14px;color:var(--el-text-color-placeholder);float:left}.el-date-editor .el-range__icon svg{vertical-align:middle}.el-date-editor .el-range-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:none;outline:0;display:inline-block;height:30px;line-height:30px;margin:0;padding:0;width:39%;text-align:center;font-size:var(--el-font-size-base);color:var(--el-text-color-regular);background-color:transparent}.el-date-editor .el-range-input::-moz-placeholder{color:var(--el-text-color-placeholder)}.el-date-editor .el-range-input:-ms-input-placeholder{color:var(--el-text-color-placeholder)}.el-date-editor .el-range-input::placeholder{color:var(--el-text-color-placeholder)}.el-date-editor .el-range-separator{flex:1;display:inline-flex;justify-content:center;align-items:center;height:100%;padding:0 5px;margin:0;font-size:14px;word-break:keep-all;color:var(--el-text-color-primary)}.el-date-editor .el-range__close-icon{font-size:14px;color:var(--el-text-color-placeholder);height:inherit;width:unset;cursor:pointer}.el-date-editor .el-range__close-icon:hover{color:var(--el-text-color-secondary)}.el-date-editor .el-range__close-icon svg{vertical-align:middle}.el-date-editor .el-range__close-icon--hidden{opacity:0;visibility:hidden}.el-range-editor.el-input__wrapper{display:inline-flex;align-items:center;padding:0 10px}.el-range-editor.is-active,.el-range-editor.is-active:hover{box-shadow:0 0 0 1px var(--el-input-focus-border-color) inset}.el-range-editor--large{line-height:var(--el-component-size-large)}.el-range-editor--large.el-input__wrapper{height:var(--el-component-size-large)}.el-range-editor--large .el-range-separator{line-height:40px;font-size:14px}.el-range-editor--large .el-range-input{height:38px;line-height:38px;font-size:14px}.el-range-editor--small{line-height:var(--el-component-size-small)}.el-range-editor--small.el-input__wrapper{height:var(--el-component-size-small)}.el-range-editor--small .el-range-separator{line-height:24px;font-size:12px}.el-range-editor--small .el-range-input{height:22px;line-height:22px;font-size:12px}.el-range-editor.is-disabled{background-color:var(--el-disabled-bg-color);border-color:var(--el-disabled-border-color);color:var(--el-disabled-text-color);cursor:not-allowed}.el-range-editor.is-disabled:focus,.el-range-editor.is-disabled:hover{border-color:var(--el-disabled-border-color)}.el-range-editor.is-disabled input{background-color:var(--el-disabled-bg-color);color:var(--el-disabled-text-color);cursor:not-allowed}.el-range-editor.is-disabled input::-moz-placeholder{color:var(--el-text-color-placeholder)}.el-range-editor.is-disabled input:-ms-input-placeholder{color:var(--el-text-color-placeholder)}.el-range-editor.is-disabled input::placeholder{color:var(--el-text-color-placeholder)}.el-range-editor.is-disabled .el-range-separator{color:var(--el-disabled-text-color)}.el-picker-panel{color:var(--el-text-color-regular);background:var(--el-bg-color-overlay);border-radius:var(--el-border-radius-base);line-height:30px}.el-picker-panel .el-time-panel{margin:5px 0;border:solid 1px var(--el-datepicker-border-color);background-color:var(--el-bg-color-overlay);box-shadow:var(--el-box-shadow-light)}.el-picker-panel__body-wrapper:after,.el-picker-panel__body:after{content:"";display:table;clear:both}.el-picker-panel__content{position:relative;margin:15px}.el-picker-panel__footer{border-top:1px solid var(--el-datepicker-inner-border-color);padding:4px 12px;text-align:right;background-color:var(--el-bg-color-overlay);position:relative;font-size:0}.el-picker-panel__shortcut{display:block;width:100%;border:0;background-color:transparent;line-height:28px;font-size:14px;color:var(--el-datepicker-text-color);padding-left:12px;text-align:left;outline:0;cursor:pointer}.el-picker-panel__shortcut:hover{color:var(--el-datepicker-hover-text-color)}.el-picker-panel__shortcut.active{background-color:#e6f1fe;color:var(--el-datepicker-active-color)}.el-picker-panel__btn{border:1px solid var(--el-fill-color-darker);color:var(--el-text-color-primary);line-height:24px;border-radius:2px;padding:0 20px;cursor:pointer;background-color:transparent;outline:0;font-size:12px}.el-picker-panel__btn[disabled]{color:var(--el-text-color-disabled);cursor:not-allowed}.el-picker-panel__icon-btn{font-size:12px;color:var(--el-datepicker-icon-color);border:0;background:0 0;cursor:pointer;outline:0;margin-top:8px}.el-picker-panel__icon-btn:hover{color:var(--el-datepicker-hover-text-color)}.el-picker-panel__icon-btn:focus-visible{color:var(--el-datepicker-hover-text-color)}.el-picker-panel__icon-btn.is-disabled{color:var(--el-text-color-disabled)}.el-picker-panel__icon-btn.is-disabled:hover{cursor:not-allowed}.el-picker-panel__icon-btn .el-icon{cursor:pointer;font-size:inherit}.el-picker-panel__link-btn{vertical-align:middle}.el-picker-panel [slot=sidebar],.el-picker-panel__sidebar{position:absolute;top:0;bottom:0;width:110px;border-right:1px solid var(--el-datepicker-inner-border-color);box-sizing:border-box;padding-top:6px;background-color:var(--el-bg-color-overlay);overflow:auto}.el-picker-panel [slot=sidebar]+.el-picker-panel__body,.el-picker-panel__sidebar+.el-picker-panel__body{margin-left:110px}.el-date-picker{--el-datepicker-text-color:var(--el-text-color-regular);--el-datepicker-off-text-color:var(--el-text-color-placeholder);--el-datepicker-header-text-color:var(--el-text-color-regular);--el-datepicker-icon-color:var(--el-text-color-primary);--el-datepicker-border-color:var(--el-disabled-border-color);--el-datepicker-inner-border-color:var(--el-border-color-light);--el-datepicker-inrange-bg-color:var(--el-border-color-extra-light);--el-datepicker-inrange-hover-bg-color:var(--el-border-color-extra-light);--el-datepicker-active-color:var(--el-color-primary);--el-datepicker-hover-text-color:var(--el-color-primary)}.el-date-picker{width:322px}.el-date-picker.has-sidebar.has-time{width:434px}.el-date-picker.has-sidebar{width:438px}.el-date-picker.has-time .el-picker-panel__body-wrapper{position:relative}.el-date-picker .el-picker-panel__content{width:292px}.el-date-picker table{table-layout:fixed;width:100%}.el-date-picker__editor-wrap{position:relative;display:table-cell;padding:0 5px}.el-date-picker__time-header{position:relative;border-bottom:1px solid var(--el-datepicker-inner-border-color);font-size:12px;padding:8px 5px 5px;display:table;width:100%;box-sizing:border-box}.el-date-picker__header{margin:12px;text-align:center}.el-date-picker__header--bordered{margin-bottom:0;padding-bottom:12px;border-bottom:solid 1px var(--el-border-color-lighter)}.el-date-picker__header--bordered+.el-picker-panel__content{margin-top:0}.el-date-picker__header-label{font-size:16px;font-weight:500;padding:0 5px;line-height:22px;text-align:center;cursor:pointer;color:var(--el-text-color-regular)}.el-date-picker__header-label:hover{color:var(--el-datepicker-hover-text-color)}.el-date-picker__header-label:focus-visible{outline:0;color:var(--el-datepicker-hover-text-color)}.el-date-picker__header-label.active{color:var(--el-datepicker-active-color)}.el-date-picker__prev-btn{float:left}.el-date-picker__next-btn{float:right}.el-date-picker__time-wrap{padding:10px;text-align:center}.el-date-picker__time-label{float:left;cursor:pointer;line-height:30px;margin-left:10px}.el-date-picker .el-time-panel{position:absolute}.el-date-range-picker{--el-datepicker-text-color:var(--el-text-color-regular);--el-datepicker-off-text-color:var(--el-text-color-placeholder);--el-datepicker-header-text-color:var(--el-text-color-regular);--el-datepicker-icon-color:var(--el-text-color-primary);--el-datepicker-border-color:var(--el-disabled-border-color);--el-datepicker-inner-border-color:var(--el-border-color-light);--el-datepicker-inrange-bg-color:var(--el-border-color-extra-light);--el-datepicker-inrange-hover-bg-color:var(--el-border-color-extra-light);--el-datepicker-active-color:var(--el-color-primary);--el-datepicker-hover-text-color:var(--el-color-primary)}.el-date-range-picker{width:646px}.el-date-range-picker.has-sidebar{width:756px}.el-date-range-picker.has-time .el-picker-panel__body-wrapper{position:relative}.el-date-range-picker table{table-layout:fixed;width:100%}.el-date-range-picker .el-picker-panel__body{min-width:513px}.el-date-range-picker .el-picker-panel__content{margin:0}.el-date-range-picker__header{position:relative;text-align:center;height:28px}.el-date-range-picker__header [class*=arrow-left]{float:left}.el-date-range-picker__header [class*=arrow-right]{float:right}.el-date-range-picker__header div{font-size:16px;font-weight:500;margin-right:50px}.el-date-range-picker__content{float:left;width:50%;box-sizing:border-box;margin:0;padding:16px}.el-date-range-picker__content.is-left{border-right:1px solid var(--el-datepicker-inner-border-color)}.el-date-range-picker__content .el-date-range-picker__header div{margin-left:50px;margin-right:50px}.el-date-range-picker__editors-wrap{box-sizing:border-box;display:table-cell}.el-date-range-picker__editors-wrap.is-right{text-align:right}.el-date-range-picker__time-header{position:relative;border-bottom:1px solid var(--el-datepicker-inner-border-color);font-size:12px;padding:8px 5px 5px;display:table;width:100%;box-sizing:border-box}.el-date-range-picker__time-header>.el-icon-arrow-right{font-size:20px;vertical-align:middle;display:table-cell;color:var(--el-datepicker-icon-color)}.el-date-range-picker__time-picker-wrap{position:relative;display:table-cell;padding:0 5px}.el-date-range-picker__time-picker-wrap .el-picker-panel{position:absolute;top:13px;right:0;z-index:1;background:#fff}.el-date-range-picker__time-picker-wrap .el-time-panel{position:absolute}.el-time-range-picker{width:354px;overflow:visible}.el-time-range-picker__content{position:relative;text-align:center;padding:10px;z-index:1}.el-time-range-picker__cell{box-sizing:border-box;margin:0;padding:4px 7px 7px;width:50%;display:inline-block}.el-time-range-picker__header{margin-bottom:5px;text-align:center;font-size:14px}.el-time-range-picker__body{border-radius:2px;border:1px solid var(--el-datepicker-border-color)}.el-time-panel{border-radius:2px;position:relative;width:180px;left:0;z-index:var(--el-index-top);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;box-sizing:content-box}.el-time-panel__content{font-size:0;position:relative;overflow:hidden}.el-time-panel__content:after,.el-time-panel__content:before{content:"";top:50%;position:absolute;margin-top:-16px;height:32px;z-index:-1;left:0;right:0;box-sizing:border-box;padding-top:6px;text-align:left}.el-time-panel__content:after{left:50%;margin-left:12%;margin-right:12%}.el-time-panel__content:before{padding-left:50%;margin-right:12%;margin-left:12%;border-top:1px solid var(--el-border-color-light);border-bottom:1px solid var(--el-border-color-light)}.el-time-panel__content.has-seconds:after{left:66.6666666667%}.el-time-panel__content.has-seconds:before{padding-left:33.3333333333%}.el-time-panel__footer{border-top:1px solid var(--el-timepicker-inner-border-color,var(--el-border-color-light));padding:4px;height:36px;line-height:25px;text-align:right;box-sizing:border-box}.el-time-panel__btn{border:none;line-height:28px;padding:0 5px;margin:0 5px;cursor:pointer;background-color:transparent;outline:0;font-size:12px;color:var(--el-text-color-primary)}.el-time-panel__btn.confirm{font-weight:800;color:var(--el-timepicker-active-color,var(--el-color-primary))}.el-descriptions{--el-descriptions-table-border:1px solid var(--el-border-color-lighter);--el-descriptions-item-bordered-label-background:var(--el-fill-color-light);box-sizing:border-box;font-size:var(--el-font-size-base);color:var(--el-text-color-primary)}.el-descriptions__header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.el-descriptions__title{color:var(--el-text-color-primary);font-size:16px;font-weight:700}.el-descriptions__body{background-color:var(--el-fill-color-blank)}.el-descriptions__body .el-descriptions__table{border-collapse:collapse;width:100%}.el-descriptions__body .el-descriptions__table .el-descriptions__cell{box-sizing:border-box;text-align:left;font-weight:400;line-height:23px;font-size:14px}.el-descriptions__body .el-descriptions__table .el-descriptions__cell.is-left{text-align:left}.el-descriptions__body .el-descriptions__table .el-descriptions__cell.is-center{text-align:center}.el-descriptions__body .el-descriptions__table .el-descriptions__cell.is-right{text-align:right}.el-descriptions__body .el-descriptions__table.is-bordered .el-descriptions__cell{border:var(--el-descriptions-table-border);padding:8px 11px}.el-descriptions__body .el-descriptions__table:not(.is-bordered) .el-descriptions__cell{padding-bottom:12px}.el-descriptions--large{font-size:14px}.el-descriptions--large .el-descriptions__header{margin-bottom:20px}.el-descriptions--large .el-descriptions__header .el-descriptions__title{font-size:16px}.el-descriptions--large .el-descriptions__body .el-descriptions__table .el-descriptions__cell{font-size:14px}.el-descriptions--large .el-descriptions__body .el-descriptions__table.is-bordered .el-descriptions__cell{padding:12px 15px}.el-descriptions--large .el-descriptions__body .el-descriptions__table:not(.is-bordered) .el-descriptions__cell{padding-bottom:16px}.el-descriptions--small{font-size:12px}.el-descriptions--small .el-descriptions__header{margin-bottom:12px}.el-descriptions--small .el-descriptions__header .el-descriptions__title{font-size:14px}.el-descriptions--small .el-descriptions__body .el-descriptions__table .el-descriptions__cell{font-size:12px}.el-descriptions--small .el-descriptions__body .el-descriptions__table.is-bordered .el-descriptions__cell{padding:4px 7px}.el-descriptions--small .el-descriptions__body .el-descriptions__table:not(.is-bordered) .el-descriptions__cell{padding-bottom:8px}.el-descriptions__label.el-descriptions__cell.is-bordered-label{font-weight:700;color:var(--el-text-color-regular);background:var(--el-descriptions-item-bordered-label-background)}.el-descriptions__label:not(.is-bordered-label){color:var(--el-text-color-primary);margin-right:16px}.el-descriptions__label.el-descriptions__cell:not(.is-bordered-label).is-vertical-label{padding-bottom:6px}.el-descriptions__content.el-descriptions__cell.is-bordered-content{color:var(--el-text-color-primary)}.el-descriptions__content:not(.is-bordered-label){color:var(--el-text-color-regular)}.el-descriptions--large .el-descriptions__label:not(.is-bordered-label){margin-right:16px}.el-descriptions--large .el-descriptions__label.el-descriptions__cell:not(.is-bordered-label).is-vertical-label{padding-bottom:8px}.el-descriptions--small .el-descriptions__label:not(.is-bordered-label){margin-right:12px}.el-descriptions--small .el-descriptions__label.el-descriptions__cell:not(.is-bordered-label).is-vertical-label{padding-bottom:4px}:root{--el-popup-modal-bg-color:var(--el-color-black);--el-popup-modal-opacity:.5}.v-modal-enter{-webkit-animation:v-modal-in var(--el-transition-duration-fast) ease;animation:v-modal-in var(--el-transition-duration-fast) ease}.v-modal-leave{-webkit-animation:v-modal-out var(--el-transition-duration-fast) ease forwards;animation:v-modal-out var(--el-transition-duration-fast) ease forwards}@-webkit-keyframes v-modal-in{0%{opacity:0}}@keyframes v-modal-in{0%{opacity:0}}@-webkit-keyframes v-modal-out{to{opacity:0}}@keyframes v-modal-out{to{opacity:0}}.v-modal{position:fixed;left:0;top:0;width:100%;height:100%;opacity:var(--el-popup-modal-opacity);background:var(--el-popup-modal-bg-color)}.el-popup-parent--hidden{overflow:hidden}.el-dialog{--el-dialog-width:50%;--el-dialog-margin-top:15vh;--el-dialog-bg-color:var(--el-bg-color);--el-dialog-box-shadow:var(--el-box-shadow);--el-dialog-title-font-size:var(--el-font-size-large);--el-dialog-content-font-size:14px;--el-dialog-font-line-height:var(--el-font-line-height-primary);--el-dialog-padding-primary:20px;--el-dialog-border-radius:var(--el-border-radius-small);position:relative;margin:var(--el-dialog-margin-top,15vh) auto 50px;background:var(--el-dialog-bg-color);border-radius:var(--el-dialog-border-radius);box-shadow:var(--el-dialog-box-shadow);box-sizing:border-box;width:var(--el-dialog-width,50%)}.el-dialog:focus{outline:0!important}.el-dialog.is-align-center{margin:auto}.el-dialog.is-fullscreen{--el-dialog-width:100%;--el-dialog-margin-top:0;margin-bottom:0;height:100%;overflow:auto}.el-dialog__wrapper{position:fixed;top:0;right:0;bottom:0;left:0;overflow:auto;margin:0}.el-dialog.is-draggable .el-dialog__header{cursor:move;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.el-dialog__header{padding:var(--el-dialog-padding-primary);padding-bottom:10px;margin-right:16px}.el-dialog__headerbtn{position:absolute;top:6px;right:0;padding:0;width:54px;height:54px;background:0 0;border:none;outline:0;cursor:pointer;font-size:var(--el-message-close-size,16px)}.el-dialog__headerbtn .el-dialog__close{color:var(--el-color-info);font-size:inherit}.el-dialog__headerbtn:focus .el-dialog__close,.el-dialog__headerbtn:hover .el-dialog__close{color:var(--el-color-primary)}.el-dialog__title{line-height:var(--el-dialog-font-line-height);font-size:var(--el-dialog-title-font-size);color:var(--el-text-color-primary)}.el-dialog__body{padding:calc(var(--el-dialog-padding-primary) + 10px) var(--el-dialog-padding-primary);color:var(--el-text-color-regular);font-size:var(--el-dialog-content-font-size)}.el-dialog__footer{padding:var(--el-dialog-padding-primary);padding-top:10px;text-align:right;box-sizing:border-box}.el-dialog--center{text-align:center}.el-dialog--center .el-dialog__body{text-align:initial;padding:25px calc(var(--el-dialog-padding-primary) + 5px) 30px}.el-dialog--center .el-dialog__footer{text-align:inherit}.el-overlay-dialog{position:fixed;top:0;right:0;bottom:0;left:0;overflow:auto}.dialog-fade-enter-active{-webkit-animation:modal-fade-in var(--el-transition-duration);animation:modal-fade-in var(--el-transition-duration)}.dialog-fade-enter-active .el-overlay-dialog{-webkit-animation:dialog-fade-in var(--el-transition-duration);animation:dialog-fade-in var(--el-transition-duration)}.dialog-fade-leave-active{-webkit-animation:modal-fade-out var(--el-transition-duration);animation:modal-fade-out var(--el-transition-duration)}.dialog-fade-leave-active .el-overlay-dialog{-webkit-animation:dialog-fade-out var(--el-transition-duration);animation:dialog-fade-out var(--el-transition-duration)}@-webkit-keyframes dialog-fade-in{0%{transform:translate3d(0,-20px,0);opacity:0}to{transform:translateZ(0);opacity:1}}@keyframes dialog-fade-in{0%{transform:translate3d(0,-20px,0);opacity:0}to{transform:translateZ(0);opacity:1}}@-webkit-keyframes dialog-fade-out{0%{transform:translateZ(0);opacity:1}to{transform:translate3d(0,-20px,0);opacity:0}}@keyframes dialog-fade-out{0%{transform:translateZ(0);opacity:1}to{transform:translate3d(0,-20px,0);opacity:0}}@-webkit-keyframes modal-fade-in{0%{opacity:0}to{opacity:1}}@keyframes modal-fade-in{0%{opacity:0}to{opacity:1}}@-webkit-keyframes modal-fade-out{0%{opacity:1}to{opacity:0}}@keyframes modal-fade-out{0%{opacity:1}to{opacity:0}}.el-divider{position:relative}.el-divider--horizontal{display:block;height:1px;width:100%;margin:24px 0;border-top:1px var(--el-border-color) var(--el-border-style)}.el-divider--vertical{display:inline-block;width:1px;height:1em;margin:0 8px;vertical-align:middle;position:relative;border-left:1px var(--el-border-color) var(--el-border-style)}.el-divider__text{position:absolute;background-color:var(--el-bg-color);padding:0 20px;font-weight:500;color:var(--el-text-color-primary);font-size:14px}.el-divider__text.is-left{left:20px;transform:translateY(-50%)}.el-divider__text.is-center{left:50%;transform:translate(-50%) translateY(-50%)}.el-divider__text.is-right{right:20px;transform:translateY(-50%)}.el-drawer{--el-drawer-bg-color:var(--el-dialog-bg-color, var(--el-bg-color));--el-drawer-padding-primary:var(--el-dialog-padding-primary, 20px)}.el-drawer{position:absolute;box-sizing:border-box;background-color:var(--el-drawer-bg-color);display:flex;flex-direction:column;box-shadow:var(--el-box-shadow-dark);overflow:hidden;transition:all var(--el-transition-duration)}.el-drawer .rtl,.el-drawer .ltr,.el-drawer .ttb,.el-drawer .btt{transform:translate(0)}.el-drawer__sr-focus:focus{outline:0!important}.el-drawer__header{align-items:center;color:#72767b;display:flex;margin-bottom:32px;padding:var(--el-drawer-padding-primary);padding-bottom:0}.el-drawer__header>:first-child{flex:1}.el-drawer__title{margin:0;flex:1;line-height:inherit;font-size:1rem}.el-drawer__footer{padding:var(--el-drawer-padding-primary);padding-top:10px;text-align:right}.el-drawer__close-btn{display:inline-flex;border:none;cursor:pointer;font-size:var(--el-font-size-extra-large);color:inherit;background-color:transparent;outline:0}.el-drawer__close-btn:focus i,.el-drawer__close-btn:hover i{color:var(--el-color-primary)}.el-drawer__body{flex:1;padding:var(--el-drawer-padding-primary);overflow:auto}.el-drawer__body>*{box-sizing:border-box}.el-drawer.ltr,.el-drawer.rtl{height:100%;top:0;bottom:0}.el-drawer.btt,.el-drawer.ttb{width:100%;left:0;right:0}.el-drawer.ltr{left:0}.el-drawer.rtl{right:0}.el-drawer.ttb{top:0}.el-drawer.btt{bottom:0}.el-drawer-fade-enter-active,.el-drawer-fade-leave-active{transition:all var(--el-transition-duration)}.el-drawer-fade-enter-active,.el-drawer-fade-enter-from,.el-drawer-fade-enter-to,.el-drawer-fade-leave-active,.el-drawer-fade-leave-from,.el-drawer-fade-leave-to{overflow:hidden!important}.el-drawer-fade-enter-from,.el-drawer-fade-leave-to{opacity:0}.el-drawer-fade-enter-to,.el-drawer-fade-leave-from{opacity:1}.el-drawer-fade-enter-from .rtl,.el-drawer-fade-leave-to .rtl{transform:translate(100%)}.el-drawer-fade-enter-from .ltr,.el-drawer-fade-leave-to .ltr{transform:translate(-100%)}.el-drawer-fade-enter-from .ttb,.el-drawer-fade-leave-to .ttb{transform:translateY(-100%)}.el-drawer-fade-enter-from .btt,.el-drawer-fade-leave-to .btt{transform:translateY(100%)}.el-dropdown{--el-dropdown-menu-box-shadow:var(--el-box-shadow-light);--el-dropdown-menuItem-hover-fill:var(--el-color-primary-light-9);--el-dropdown-menuItem-hover-color:var(--el-color-primary);--el-dropdown-menu-index:10;display:inline-flex;position:relative;color:var(--el-text-color-regular);font-size:var(--el-font-size-base);line-height:1;vertical-align:top}.el-dropdown.is-disabled{color:var(--el-text-color-placeholder);cursor:not-allowed}.el-dropdown__popper{--el-dropdown-menu-box-shadow:var(--el-box-shadow-light);--el-dropdown-menuItem-hover-fill:var(--el-color-primary-light-9);--el-dropdown-menuItem-hover-color:var(--el-color-primary);--el-dropdown-menu-index:10}.el-dropdown__popper.el-popper{background:var(--el-bg-color-overlay);border:1px solid var(--el-border-color-light);box-shadow:var(--el-dropdown-menu-box-shadow)}.el-dropdown__popper.el-popper .el-popper__arrow:before{border:1px solid var(--el-border-color-light)}.el-dropdown__popper.el-popper[data-popper-placement^=top] .el-popper__arrow:before{border-top-color:transparent;border-left-color:transparent}.el-dropdown__popper.el-popper[data-popper-placement^=bottom] .el-popper__arrow:before{border-bottom-color:transparent;border-right-color:transparent}.el-dropdown__popper.el-popper[data-popper-placement^=left] .el-popper__arrow:before{border-left-color:transparent;border-bottom-color:transparent}.el-dropdown__popper.el-popper[data-popper-placement^=right] .el-popper__arrow:before{border-right-color:transparent;border-top-color:transparent}.el-dropdown__popper .el-dropdown-menu{border:none}.el-dropdown__popper .el-dropdown__popper-selfdefine{outline:0}.el-dropdown__popper .el-scrollbar__bar{z-index:calc(var(--el-dropdown-menu-index) + 1)}.el-dropdown__popper .el-dropdown__list{list-style:none;padding:0;margin:0;box-sizing:border-box}.el-dropdown .el-dropdown__caret-button{padding-left:0;padding-right:0;display:inline-flex;justify-content:center;align-items:center;width:32px;border-left:none}.el-dropdown .el-dropdown__caret-button>span{display:inline-flex}.el-dropdown .el-dropdown__caret-button:before{content:"";position:absolute;display:block;width:1px;top:-1px;bottom:-1px;left:0;background:var(--el-overlay-color-lighter)}.el-dropdown .el-dropdown__caret-button.el-button:before{background:var(--el-border-color);opacity:.5}.el-dropdown .el-dropdown__caret-button .el-dropdown__icon{font-size:inherit;padding-left:0}.el-dropdown .el-dropdown-selfdefine{outline:0}.el-dropdown--large .el-dropdown__caret-button{width:40px}.el-dropdown--small .el-dropdown__caret-button{width:24px}.el-dropdown-menu{position:relative;top:0;left:0;z-index:var(--el-dropdown-menu-index);padding:5px 0;margin:0;background-color:var(--el-bg-color-overlay);border:none;border-radius:var(--el-border-radius-base);box-shadow:none;list-style:none}.el-dropdown-menu__item{display:flex;align-items:center;white-space:nowrap;list-style:none;line-height:22px;padding:5px 16px;margin:0;font-size:var(--el-font-size-base);color:var(--el-text-color-regular);cursor:pointer;outline:0}.el-dropdown-menu__item:not(.is-disabled):focus{background-color:var(--el-dropdown-menuItem-hover-fill);color:var(--el-dropdown-menuItem-hover-color)}.el-dropdown-menu__item i{margin-right:5px}.el-dropdown-menu__item--divided{margin:6px 0;border-top:1px solid var(--el-border-color-lighter)}.el-dropdown-menu__item.is-disabled{cursor:not-allowed;color:var(--el-text-color-disabled)}.el-dropdown-menu--large{padding:7px 0}.el-dropdown-menu--large .el-dropdown-menu__item{padding:7px 20px;line-height:22px;font-size:14px}.el-dropdown-menu--large .el-dropdown-menu__item--divided{margin:8px 0}.el-dropdown-menu--small{padding:3px 0}.el-dropdown-menu--small .el-dropdown-menu__item{padding:2px 12px;line-height:20px;font-size:12px}.el-dropdown-menu--small .el-dropdown-menu__item--divided{margin:4px 0}.el-empty{--el-empty-padding:40px 0;--el-empty-image-width:160px;--el-empty-description-margin-top:20px;--el-empty-bottom-margin-top:20px;--el-empty-fill-color-0:var(--el-color-white);--el-empty-fill-color-1:#fcfcfd;--el-empty-fill-color-2:#f8f9fb;--el-empty-fill-color-3:#f7f8fc;--el-empty-fill-color-4:#eeeff3;--el-empty-fill-color-5:#edeef2;--el-empty-fill-color-6:#e9ebef;--el-empty-fill-color-7:#e5e7e9;--el-empty-fill-color-8:#e0e3e9;--el-empty-fill-color-9:#d5d7de;display:flex;justify-content:center;align-items:center;flex-direction:column;text-align:center;box-sizing:border-box;padding:var(--el-empty-padding)}.el-empty__image{width:var(--el-empty-image-width)}.el-empty__image img{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;width:100%;height:100%;vertical-align:top;-o-object-fit:contain;object-fit:contain}.el-empty__image svg{color:var(--el-svg-monochrome-grey);fill:currentColor;width:100%;height:100%;vertical-align:top}.el-empty__description{margin-top:var(--el-empty-description-margin-top)}.el-empty__description p{margin:0;font-size:var(--el-font-size-base);color:var(--el-text-color-secondary)}.el-empty__bottom{margin-top:var(--el-empty-bottom-margin-top)}.el-footer{--el-footer-padding:0 20px;--el-footer-height:60px;padding:var(--el-footer-padding);box-sizing:border-box;flex-shrink:0;height:var(--el-footer-height)}.el-form{--el-form-label-font-size:var(--el-font-size-base);--el-form-inline-content-width:220px}.el-form--label-left .el-form-item__label{justify-content:flex-start}.el-form--label-top .el-form-item{display:block}.el-form--label-top .el-form-item .el-form-item__label{display:block;height:auto;text-align:left;margin-bottom:8px;line-height:22px}.el-form--inline .el-form-item{display:inline-flex;vertical-align:middle;margin-right:32px}.el-form--inline.el-form--label-top{display:flex;flex-wrap:wrap}.el-form--inline.el-form--label-top .el-form-item{display:block}.el-form--large.el-form--label-top .el-form-item .el-form-item__label{margin-bottom:12px;line-height:22px}.el-form--default.el-form--label-top .el-form-item .el-form-item__label{margin-bottom:8px;line-height:22px}.el-form--small.el-form--label-top .el-form-item .el-form-item__label{margin-bottom:4px;line-height:20px}.el-form-item{display:flex;--font-size:14px;margin-bottom:18px}.el-form-item .el-form-item{margin-bottom:0}.el-form-item .el-input__validateIcon{display:none}.el-form-item--large{--font-size:14px;--el-form-label-font-size:var(--font-size);margin-bottom:22px}.el-form-item--large .el-form-item__label{height:40px;line-height:40px}.el-form-item--large .el-form-item__content{line-height:40px}.el-form-item--large .el-form-item__error{padding-top:4px}.el-form-item--default{--font-size:14px;--el-form-label-font-size:var(--font-size);margin-bottom:18px}.el-form-item--default .el-form-item__label{height:32px;line-height:32px}.el-form-item--default .el-form-item__content{line-height:32px}.el-form-item--default .el-form-item__error{padding-top:2px}.el-form-item--small{--font-size:12px;--el-form-label-font-size:var(--font-size);margin-bottom:18px}.el-form-item--small .el-form-item__label{height:24px;line-height:24px}.el-form-item--small .el-form-item__content{line-height:24px}.el-form-item--small .el-form-item__error{padding-top:2px}.el-form-item__label-wrap{display:flex}.el-form-item__label{display:inline-flex;justify-content:flex-end;align-items:flex-start;flex:0 0 auto;font-size:var(--el-form-label-font-size);color:var(--el-text-color-regular);height:32px;line-height:32px;padding:0 12px 0 0;box-sizing:border-box}.el-form-item__content{display:flex;flex-wrap:wrap;align-items:center;flex:1;line-height:32px;position:relative;font-size:var(--font-size);min-width:0}.el-form-item__content .el-input-group{vertical-align:top}.el-form-item__error{color:var(--el-color-danger);font-size:12px;line-height:1;padding-top:2px;position:absolute;top:100%;left:0}.el-form-item__error--inline{position:relative;top:auto;left:auto;display:inline-block;margin-left:10px}.el-form-item.is-required:not(.is-no-asterisk).asterisk-left>.el-form-item__label-wrap>.el-form-item__label:before,.el-form-item.is-required:not(.is-no-asterisk).asterisk-left>.el-form-item__label:before{content:"*";color:var(--el-color-danger);margin-right:4px}.el-form-item.is-required:not(.is-no-asterisk).asterisk-right>.el-form-item__label-wrap>.el-form-item__label:after,.el-form-item.is-required:not(.is-no-asterisk).asterisk-right>.el-form-item__label:after{content:"*";color:var(--el-color-danger);margin-left:4px}.el-form-item.is-error .el-select-v2__wrapper.is-focused{border-color:transparent}.el-form-item.is-error .el-select-v2__wrapper,.el-form-item.is-error .el-select-v2__wrapper:focus,.el-form-item.is-error .el-textarea__inner,.el-form-item.is-error .el-textarea__inner:focus{box-shadow:0 0 0 1px var(--el-color-danger) inset}.el-form-item.is-error .el-input__wrapper,.el-form-item.is-error .el-input__wrapper:hover{box-shadow:0 0 0 1px var(--el-color-danger) inset}.el-form-item.is-error .el-input__wrapper.is-focus{box-shadow:0 0 0 1px var(--el-color-danger) inset!important}.el-form-item.is-error .el-select:hover{box-shadow:0 0 0 1px transparent}.el-form-item.is-error .el-select .el-input .el-input__wrapper:hover{box-shadow:0 0 0 1px var(--el-color-danger) inset}.el-form-item.is-error .el-select .el-input.is-focus .el-input__wrapper{box-shadow:0 0 0 1px var(--el-color-danger) inset!important}.el-form-item.is-error .el-input-group__append .el-input__wrapper,.el-form-item.is-error .el-input-group__prepend .el-input__wrapper{box-shadow:0 0 0 1px transparent inset}.el-form-item.is-error .el-input__validateIcon{color:var(--el-color-danger)}.el-form-item--feedback .el-input__validateIcon{display:inline-flex}.el-header{--el-header-padding:0 20px;--el-header-height:60px;padding:var(--el-header-padding);box-sizing:border-box;flex-shrink:0;height:var(--el-header-height)}.el-image-viewer__wrapper{position:fixed;top:0;right:0;bottom:0;left:0}.el-image-viewer__btn{position:absolute;z-index:1;display:flex;align-items:center;justify-content:center;border-radius:50%;opacity:.8;cursor:pointer;box-sizing:border-box;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.el-image-viewer__btn .el-icon{font-size:inherit;cursor:pointer}.el-image-viewer__close{top:40px;right:40px;width:40px;height:40px;font-size:40px}.el-image-viewer__canvas{position:static;width:100%;height:100%;display:flex;justify-content:center;align-items:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.el-image-viewer__actions{left:50%;bottom:30px;transform:translate(-50%);width:282px;height:44px;padding:0 23px;background-color:var(--el-text-color-regular);border-color:#fff;border-radius:22px}.el-image-viewer__actions__inner{width:100%;height:100%;text-align:justify;cursor:default;font-size:23px;color:#fff;display:flex;align-items:center;justify-content:space-around}.el-image-viewer__prev{top:50%;transform:translateY(-50%);left:40px;width:44px;height:44px;font-size:24px;color:#fff;background-color:var(--el-text-color-regular);border-color:#fff}.el-image-viewer__next{top:50%;transform:translateY(-50%);right:40px;text-indent:2px;width:44px;height:44px;font-size:24px;color:#fff;background-color:var(--el-text-color-regular);border-color:#fff}.el-image-viewer__close{width:44px;height:44px;font-size:24px;color:#fff;background-color:var(--el-text-color-regular);border-color:#fff}.el-image-viewer__mask{position:absolute;width:100%;height:100%;top:0;left:0;opacity:.5;background:#000}.viewer-fade-enter-active{-webkit-animation:viewer-fade-in var(--el-transition-duration);animation:viewer-fade-in var(--el-transition-duration)}.viewer-fade-leave-active{-webkit-animation:viewer-fade-out var(--el-transition-duration);animation:viewer-fade-out var(--el-transition-duration)}@-webkit-keyframes viewer-fade-in{0%{transform:translate3d(0,-20px,0);opacity:0}to{transform:translateZ(0);opacity:1}}@keyframes viewer-fade-in{0%{transform:translate3d(0,-20px,0);opacity:0}to{transform:translateZ(0);opacity:1}}@-webkit-keyframes viewer-fade-out{0%{transform:translateZ(0);opacity:1}to{transform:translate3d(0,-20px,0);opacity:0}}@keyframes viewer-fade-out{0%{transform:translateZ(0);opacity:1}to{transform:translate3d(0,-20px,0);opacity:0}}.el-image__error,.el-image__inner,.el-image__placeholder,.el-image__wrapper{width:100%;height:100%}.el-image{position:relative;display:inline-block;overflow:hidden}.el-image__inner{vertical-align:top;opacity:1}.el-image__inner.is-loading{opacity:0}.el-image__wrapper{position:absolute;top:0;left:0}.el-image__placeholder{background:var(--el-fill-color-light)}.el-image__error{display:flex;justify-content:center;align-items:center;font-size:14px;background:var(--el-fill-color-light);color:var(--el-text-color-placeholder);vertical-align:middle}.el-image__preview{cursor:pointer}.el-input-number{position:relative;display:inline-flex;width:150px;line-height:30px}.el-input-number .el-input__wrapper{padding-left:42px;padding-right:42px}.el-input-number .el-input__inner{-webkit-appearance:none;-moz-appearance:textfield;text-align:center;line-height:1}.el-input-number .el-input__inner::-webkit-inner-spin-button,.el-input-number .el-input__inner::-webkit-outer-spin-button{margin:0;-webkit-appearance:none}.el-input-number__decrease,.el-input-number__increase{display:flex;justify-content:center;align-items:center;height:auto;position:absolute;z-index:1;top:1px;bottom:1px;width:32px;background:var(--el-fill-color-light);color:var(--el-text-color-regular);cursor:pointer;font-size:13px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.el-input-number__decrease:hover,.el-input-number__increase:hover{color:var(--el-color-primary)}.el-input-number__decrease:hover~.el-input:not(.is-disabled) .el-input__wrapper,.el-input-number__increase:hover~.el-input:not(.is-disabled) .el-input__wrapper{box-shadow:0 0 0 1px var(--el-input-focus-border-color,var(--el-color-primary)) inset}.el-input-number__decrease.is-disabled,.el-input-number__increase.is-disabled{color:var(--el-disabled-text-color);cursor:not-allowed}.el-input-number__increase{right:1px;border-radius:0 var(--el-border-radius-base) var(--el-border-radius-base) 0;border-left:var(--el-border)}.el-input-number__decrease{left:1px;border-radius:var(--el-border-radius-base) 0 0 var(--el-border-radius-base);border-right:var(--el-border)}.el-input-number.is-disabled .el-input-number__decrease,.el-input-number.is-disabled .el-input-number__increase{border-color:var(--el-disabled-border-color);color:var(--el-disabled-border-color)}.el-input-number.is-disabled .el-input-number__decrease:hover,.el-input-number.is-disabled .el-input-number__increase:hover{color:var(--el-disabled-border-color);cursor:not-allowed}.el-input-number--large{width:180px;line-height:38px}.el-input-number--large .el-input-number__decrease,.el-input-number--large .el-input-number__increase{width:40px;font-size:14px}.el-input-number--large .el-input__wrapper{padding-left:47px;padding-right:47px}.el-input-number--small{width:120px;line-height:22px}.el-input-number--small .el-input-number__decrease,.el-input-number--small .el-input-number__increase{width:24px;font-size:12px}.el-input-number--small .el-input__wrapper{padding-left:31px;padding-right:31px}.el-input-number--small .el-input-number__decrease [class*=el-icon],.el-input-number--small .el-input-number__increase [class*=el-icon]{transform:scale(.9)}.el-input-number.is-without-controls .el-input__wrapper{padding-left:15px;padding-right:15px}.el-input-number.is-controls-right .el-input__wrapper{padding-left:15px;padding-right:42px}.el-input-number.is-controls-right .el-input-number__decrease,.el-input-number.is-controls-right .el-input-number__increase{--el-input-number-controls-height:15px;height:var(--el-input-number-controls-height);line-height:var(--el-input-number-controls-height)}.el-input-number.is-controls-right .el-input-number__decrease [class*=el-icon],.el-input-number.is-controls-right .el-input-number__increase [class*=el-icon]{transform:scale(.8)}.el-input-number.is-controls-right .el-input-number__increase{bottom:auto;left:auto;border-radius:0 var(--el-border-radius-base) 0 0;border-bottom:var(--el-border)}.el-input-number.is-controls-right .el-input-number__decrease{right:1px;top:auto;left:auto;border-right:none;border-left:var(--el-border);border-radius:0 0 var(--el-border-radius-base) 0}.el-input-number.is-controls-right[class*=large] [class*=decrease],.el-input-number.is-controls-right[class*=large] [class*=increase]{--el-input-number-controls-height:19px}.el-input-number.is-controls-right[class*=small] [class*=decrease],.el-input-number.is-controls-right[class*=small] [class*=increase]{--el-input-number-controls-height:11px}.el-textarea{--el-input-text-color:var(--el-text-color-regular);--el-input-border:var(--el-border);--el-input-hover-border:var(--el-border-color-hover);--el-input-focus-border:var(--el-color-primary);--el-input-transparent-border:0 0 0 1px transparent inset;--el-input-border-color:var(--el-border-color);--el-input-border-radius:var(--el-border-radius-base);--el-input-bg-color:var(--el-fill-color-blank);--el-input-icon-color:var(--el-text-color-placeholder);--el-input-placeholder-color:var(--el-text-color-placeholder);--el-input-hover-border-color:var(--el-border-color-hover);--el-input-clear-hover-color:var(--el-text-color-secondary);--el-input-focus-border-color:var(--el-color-primary);--el-input-width:100%}.el-textarea{position:relative;display:inline-block;width:100%;vertical-align:bottom;font-size:var(--el-font-size-base)}.el-textarea__inner{position:relative;display:block;resize:vertical;padding:5px 11px;line-height:1.5;box-sizing:border-box;width:100%;font-size:inherit;font-family:inherit;color:var(--el-input-text-color,var(--el-text-color-regular));background-color:var(--el-input-bg-color,var(--el-fill-color-blank));background-image:none;-webkit-appearance:none;box-shadow:0 0 0 1px var(--el-input-border-color,var(--el-border-color)) inset;border-radius:var(--el-input-border-radius,var(--el-border-radius-base));transition:var(--el-transition-box-shadow);border:none}.el-textarea__inner::-moz-placeholder{color:var(--el-input-placeholder-color,var(--el-text-color-placeholder))}.el-textarea__inner:-ms-input-placeholder{color:var(--el-input-placeholder-color,var(--el-text-color-placeholder))}.el-textarea__inner::placeholder{color:var(--el-input-placeholder-color,var(--el-text-color-placeholder))}.el-textarea__inner:hover{box-shadow:0 0 0 1px var(--el-input-hover-border-color) inset}.el-textarea__inner:focus{outline:0;box-shadow:0 0 0 1px var(--el-input-focus-border-color) inset}.el-textarea .el-input__count{color:var(--el-color-info);background:var(--el-fill-color-blank);position:absolute;font-size:12px;line-height:14px;bottom:5px;right:10px}.el-textarea.is-disabled .el-textarea__inner{box-shadow:0 0 0 1px var(--el-disabled-border-color) inset;background-color:var(--el-disabled-bg-color);color:var(--el-disabled-text-color);cursor:not-allowed}.el-textarea.is-disabled .el-textarea__inner::-moz-placeholder{color:var(--el-text-color-placeholder)}.el-textarea.is-disabled .el-textarea__inner:-ms-input-placeholder{color:var(--el-text-color-placeholder)}.el-textarea.is-disabled .el-textarea__inner::placeholder{color:var(--el-text-color-placeholder)}.el-textarea.is-exceed .el-textarea__inner{box-shadow:0 0 0 1px var(--el-color-danger) inset}.el-textarea.is-exceed .el-input__count{color:var(--el-color-danger)}.el-input{--el-input-text-color:var(--el-text-color-regular);--el-input-border:var(--el-border);--el-input-hover-border:var(--el-border-color-hover);--el-input-focus-border:var(--el-color-primary);--el-input-transparent-border:0 0 0 1px transparent inset;--el-input-border-color:var(--el-border-color);--el-input-border-radius:var(--el-border-radius-base);--el-input-bg-color:var(--el-fill-color-blank);--el-input-icon-color:var(--el-text-color-placeholder);--el-input-placeholder-color:var(--el-text-color-placeholder);--el-input-hover-border-color:var(--el-border-color-hover);--el-input-clear-hover-color:var(--el-text-color-secondary);--el-input-focus-border-color:var(--el-color-primary);--el-input-width:100%}.el-input{--el-input-height:var(--el-component-size);position:relative;font-size:var(--el-font-size-base);display:inline-flex;width:var(--el-input-width);line-height:var(--el-input-height);box-sizing:border-box;vertical-align:middle}.el-input::-webkit-scrollbar{z-index:11;width:6px}.el-input::-webkit-scrollbar:horizontal{height:6px}.el-input::-webkit-scrollbar-thumb{border-radius:5px;width:6px;background:var(--el-text-color-disabled)}.el-input::-webkit-scrollbar-corner{background:var(--el-fill-color-blank)}.el-input::-webkit-scrollbar-track{background:var(--el-fill-color-blank)}.el-input::-webkit-scrollbar-track-piece{background:var(--el-fill-color-blank);width:6px}.el-input .el-input__clear,.el-input .el-input__password{color:var(--el-input-icon-color);font-size:14px;cursor:pointer}.el-input .el-input__clear:hover,.el-input .el-input__password:hover{color:var(--el-input-clear-hover-color)}.el-input .el-input__count{height:100%;display:inline-flex;align-items:center;color:var(--el-color-info);font-size:12px}.el-input .el-input__count .el-input__count-inner{background:var(--el-fill-color-blank);line-height:initial;display:inline-block;padding-left:8px}.el-input__wrapper{display:inline-flex;flex-grow:1;align-items:center;justify-content:center;padding:1px 11px;background-color:var(--el-input-bg-color,var(--el-fill-color-blank));background-image:none;border-radius:var(--el-input-border-radius,var(--el-border-radius-base));cursor:text;transition:var(--el-transition-box-shadow);transform:translateZ(0);box-shadow:0 0 0 1px var(--el-input-border-color,var(--el-border-color)) inset}.el-input__wrapper:hover{box-shadow:0 0 0 1px var(--el-input-hover-border-color) inset}.el-input__wrapper.is-focus{box-shadow:0 0 0 1px var(--el-input-focus-border-color) inset}.el-input__inner{--el-input-inner-height:calc(var(--el-input-height, 32px) - 2px);width:100%;flex-grow:1;-webkit-appearance:none;color:var(--el-input-text-color,var(--el-text-color-regular));font-size:inherit;height:var(--el-input-inner-height);line-height:var(--el-input-inner-height);padding:0;outline:0;border:none;background:0 0;box-sizing:border-box}.el-input__inner:focus{outline:0}.el-input__inner::-moz-placeholder{color:var(--el-input-placeholder-color,var(--el-text-color-placeholder))}.el-input__inner:-ms-input-placeholder{color:var(--el-input-placeholder-color,var(--el-text-color-placeholder))}.el-input__inner::placeholder{color:var(--el-input-placeholder-color,var(--el-text-color-placeholder))}.el-input__inner[type=password]::-ms-reveal{display:none}.el-input__prefix{display:inline-flex;white-space:nowrap;flex-shrink:0;flex-wrap:nowrap;height:100%;text-align:center;color:var(--el-input-icon-color,var(--el-text-color-placeholder));transition:all var(--el-transition-duration);pointer-events:none}.el-input__prefix-inner{pointer-events:all;display:inline-flex;align-items:center;justify-content:center}.el-input__prefix-inner>:last-child{margin-right:8px}.el-input__prefix-inner>:first-child,.el-input__prefix-inner>:first-child.el-input__icon{margin-left:0}.el-input__suffix{display:inline-flex;white-space:nowrap;flex-shrink:0;flex-wrap:nowrap;height:100%;text-align:center;color:var(--el-input-icon-color,var(--el-text-color-placeholder));transition:all var(--el-transition-duration);pointer-events:none}.el-input__suffix-inner{pointer-events:all;display:inline-flex;align-items:center;justify-content:center}.el-input__suffix-inner>:first-child{margin-left:8px}.el-input .el-input__icon{height:inherit;line-height:inherit;display:flex;justify-content:center;align-items:center;transition:all var(--el-transition-duration);margin-left:8px}.el-input__validateIcon{pointer-events:none}.el-input.is-active .el-input__wrapper{box-shadow:0 0 0 1px var(--el-input-focus-color,) inset}.el-input.is-disabled{cursor:not-allowed}.el-input.is-disabled .el-input__wrapper{background-color:var(--el-disabled-bg-color);box-shadow:0 0 0 1px var(--el-disabled-border-color) inset}.el-input.is-disabled .el-input__inner{color:var(--el-disabled-text-color);-webkit-text-fill-color:var(--el-disabled-text-color);cursor:not-allowed}.el-input.is-disabled .el-input__inner::-moz-placeholder{color:var(--el-text-color-placeholder)}.el-input.is-disabled .el-input__inner:-ms-input-placeholder{color:var(--el-text-color-placeholder)}.el-input.is-disabled .el-input__inner::placeholder{color:var(--el-text-color-placeholder)}.el-input.is-disabled .el-input__icon{cursor:not-allowed}.el-input.is-exceed .el-input__wrapper{box-shadow:0 0 0 1px var(--el-color-danger) inset}.el-input.is-exceed .el-input__suffix .el-input__count{color:var(--el-color-danger)}.el-input--large{--el-input-height:var(--el-component-size-large);font-size:14px}.el-input--large .el-input__wrapper{padding:1px 15px}.el-input--large .el-input__inner{--el-input-inner-height:calc(var(--el-input-height, 40px) - 2px)}.el-input--small{--el-input-height:var(--el-component-size-small);font-size:12px}.el-input--small .el-input__wrapper{padding:1px 7px}.el-input--small .el-input__inner{--el-input-inner-height:calc(var(--el-input-height, 24px) - 2px)}.el-input-group{display:inline-flex;width:100%;align-items:stretch}.el-input-group__append,.el-input-group__prepend{background-color:var(--el-fill-color-light);color:var(--el-color-info);position:relative;display:inline-flex;align-items:center;justify-content:center;min-height:100%;border-radius:var(--el-input-border-radius);padding:0 20px;white-space:nowrap}.el-input-group__append:focus,.el-input-group__prepend:focus{outline:0}.el-input-group__append .el-button,.el-input-group__append .el-select,.el-input-group__prepend .el-button,.el-input-group__prepend .el-select{display:inline-block;margin:0 -20px}.el-input-group__append button.el-button,.el-input-group__append button.el-button:hover,.el-input-group__append div.el-select .el-input__wrapper,.el-input-group__append div.el-select:hover .el-input__wrapper,.el-input-group__prepend button.el-button,.el-input-group__prepend button.el-button:hover,.el-input-group__prepend div.el-select .el-input__wrapper,.el-input-group__prepend div.el-select:hover .el-input__wrapper{border-color:transparent;background-color:transparent;color:inherit}.el-input-group__append .el-button,.el-input-group__append .el-input,.el-input-group__prepend .el-button,.el-input-group__prepend .el-input{font-size:inherit}.el-input-group__prepend{border-right:0;border-top-right-radius:0;border-bottom-right-radius:0;box-shadow:1px 0 0 0 var(--el-input-border-color) inset,0 1px 0 0 var(--el-input-border-color) inset,0 -1px 0 0 var(--el-input-border-color) inset}.el-input-group__append{border-left:0;border-top-left-radius:0;border-bottom-left-radius:0;box-shadow:0 1px 0 0 var(--el-input-border-color) inset,0 -1px 0 0 var(--el-input-border-color) inset,-1px 0 0 0 var(--el-input-border-color) inset}.el-input-group--prepend>.el-input__wrapper{border-top-left-radius:0;border-bottom-left-radius:0}.el-input-group--prepend .el-input-group__prepend .el-select .el-input .el-input__inner{box-shadow:none!important}.el-input-group--prepend .el-input-group__prepend .el-select .el-input .el-input__wrapper{border-top-right-radius:0;border-bottom-right-radius:0;box-shadow:1px 0 0 0 var(--el-input-border-color) inset,0 1px 0 0 var(--el-input-border-color) inset,0 -1px 0 0 var(--el-input-border-color) inset}.el-input-group--prepend .el-input-group__prepend .el-select .el-input.is-focus .el-input__inner{box-shadow:none!important}.el-input-group--prepend .el-input-group__prepend .el-select .el-input.is-focus .el-input__wrapper{box-shadow:1px 0 0 0 var(--el-input-focus-border-color) inset,1px 0 0 0 var(--el-input-focus-border-color),0 1px 0 0 var(--el-input-focus-border-color) inset,0 -1px 0 0 var(--el-input-focus-border-color) inset!important;z-index:2}.el-input-group--prepend .el-input-group__prepend .el-select .el-input.is-focus .el-input__wrapper:focus{outline:0;z-index:2;box-shadow:1px 0 0 0 var(--el-input-focus-border-color) inset,1px 0 0 0 var(--el-input-focus-border-color),0 1px 0 0 var(--el-input-focus-border-color) inset,0 -1px 0 0 var(--el-input-focus-border-color) inset!important}.el-input-group--prepend .el-input-group__prepend .el-select:hover .el-input__inner{box-shadow:none!important}.el-input-group--prepend .el-input-group__prepend .el-select:hover .el-input__wrapper{z-index:1;box-shadow:1px 0 0 0 var(--el-input-hover-border-color) inset,1px 0 0 0 var(--el-input-hover-border-color),0 1px 0 0 var(--el-input-hover-border-color) inset,0 -1px 0 0 var(--el-input-hover-border-color) inset!important}.el-input-group--append>.el-input__wrapper{border-top-right-radius:0;border-bottom-right-radius:0}.el-input-group--append .el-input-group__append .el-select .el-input .el-input__inner{box-shadow:none!important}.el-input-group--append .el-input-group__append .el-select .el-input .el-input__wrapper{border-top-left-radius:0;border-bottom-left-radius:0;box-shadow:0 1px 0 0 var(--el-input-border-color) inset,0 -1px 0 0 var(--el-input-border-color) inset,-1px 0 0 0 var(--el-input-border-color) inset}.el-input-group--append .el-input-group__append .el-select .el-input.is-focus .el-input__inner{box-shadow:none!important}.el-input-group--append .el-input-group__append .el-select .el-input.is-focus .el-input__wrapper{z-index:2;box-shadow:-1px 0 0 0 var(--el-input-focus-border-color),-1px 0 0 0 var(--el-input-focus-border-color) inset,0 1px 0 0 var(--el-input-focus-border-color) inset,0 -1px 0 0 var(--el-input-focus-border-color) inset!important}.el-input-group--append .el-input-group__append .el-select:hover .el-input__inner{box-shadow:none!important}.el-input-group--append .el-input-group__append .el-select:hover .el-input__wrapper{z-index:1;box-shadow:-1px 0 0 0 var(--el-input-hover-border-color),-1px 0 0 0 var(--el-input-hover-border-color) inset,0 1px 0 0 var(--el-input-hover-border-color) inset,0 -1px 0 0 var(--el-input-hover-border-color) inset!important}.el-link{--el-link-font-size:var(--el-font-size-base);--el-link-font-weight:var(--el-font-weight-primary);--el-link-text-color:var(--el-text-color-regular);--el-link-hover-text-color:var(--el-color-primary);--el-link-disabled-text-color:var(--el-text-color-placeholder)}.el-link{display:inline-flex;flex-direction:row;align-items:center;justify-content:center;vertical-align:middle;position:relative;text-decoration:none;outline:0;cursor:pointer;padding:0;font-size:var(--el-link-font-size);font-weight:var(--el-link-font-weight);color:var(--el-link-text-color)}.el-link:hover{color:var(--el-link-hover-text-color)}.el-link.is-underline:hover:after{content:"";position:absolute;left:0;right:0;height:0;bottom:0;border-bottom:1px solid var(--el-link-hover-text-color)}.el-link.is-disabled{color:var(--el-link-disabled-text-color);cursor:not-allowed}.el-link [class*=el-icon-]+span{margin-left:5px}.el-link.el-link--default:after{border-color:var(--el-link-hover-text-color)}.el-link__inner{display:inline-flex;justify-content:center;align-items:center}.el-link.el-link--primary{--el-link-text-color:var(--el-color-primary);--el-link-hover-text-color:var(--el-color-primary-light-3);--el-link-disabled-text-color:var(--el-color-primary-light-5)}.el-link.el-link--primary:after{border-color:var(--el-link-text-color)}.el-link.el-link--primary.is-underline:hover:after{border-color:var(--el-link-text-color)}.el-link.el-link--success{--el-link-text-color:var(--el-color-success);--el-link-hover-text-color:var(--el-color-success-light-3);--el-link-disabled-text-color:var(--el-color-success-light-5)}.el-link.el-link--success:after{border-color:var(--el-link-text-color)}.el-link.el-link--success.is-underline:hover:after{border-color:var(--el-link-text-color)}.el-link.el-link--warning{--el-link-text-color:var(--el-color-warning);--el-link-hover-text-color:var(--el-color-warning-light-3);--el-link-disabled-text-color:var(--el-color-warning-light-5)}.el-link.el-link--warning:after{border-color:var(--el-link-text-color)}.el-link.el-link--warning.is-underline:hover:after{border-color:var(--el-link-text-color)}.el-link.el-link--danger{--el-link-text-color:var(--el-color-danger);--el-link-hover-text-color:var(--el-color-danger-light-3);--el-link-disabled-text-color:var(--el-color-danger-light-5)}.el-link.el-link--danger:after{border-color:var(--el-link-text-color)}.el-link.el-link--danger.is-underline:hover:after{border-color:var(--el-link-text-color)}.el-link.el-link--error{--el-link-text-color:var(--el-color-error);--el-link-hover-text-color:var(--el-color-error-light-3);--el-link-disabled-text-color:var(--el-color-error-light-5)}.el-link.el-link--error:after{border-color:var(--el-link-text-color)}.el-link.el-link--error.is-underline:hover:after{border-color:var(--el-link-text-color)}.el-link.el-link--info{--el-link-text-color:var(--el-color-info);--el-link-hover-text-color:var(--el-color-info-light-3);--el-link-disabled-text-color:var(--el-color-info-light-5)}.el-link.el-link--info:after{border-color:var(--el-link-text-color)}.el-link.el-link--info.is-underline:hover:after{border-color:var(--el-link-text-color)}:root{--el-loading-spinner-size:42px;--el-loading-fullscreen-spinner-size:50px}.el-loading-parent--relative{position:relative!important}.el-loading-parent--hidden{overflow:hidden!important}.el-loading-mask{position:absolute;z-index:2000;background-color:var(--el-mask-color);margin:0;top:0;right:0;bottom:0;left:0;transition:opacity var(--el-transition-duration)}.el-loading-mask.is-fullscreen{position:fixed}.el-loading-mask.is-fullscreen .el-loading-spinner{margin-top:calc((0px - var(--el-loading-fullscreen-spinner-size))/ 2)}.el-loading-mask.is-fullscreen .el-loading-spinner .circular{height:var(--el-loading-fullscreen-spinner-size);width:var(--el-loading-fullscreen-spinner-size)}.el-loading-spinner{top:50%;margin-top:calc((0px - var(--el-loading-spinner-size))/ 2);width:100%;text-align:center;position:absolute}.el-loading-spinner .el-loading-text{color:var(--el-color-primary);margin:3px 0;font-size:14px}.el-loading-spinner .circular{display:inline;height:var(--el-loading-spinner-size);width:var(--el-loading-spinner-size);-webkit-animation:loading-rotate 2s linear infinite;animation:loading-rotate 2s linear infinite}.el-loading-spinner .path{-webkit-animation:loading-dash 1.5s ease-in-out infinite;animation:loading-dash 1.5s ease-in-out infinite;stroke-dasharray:90,150;stroke-dashoffset:0;stroke-width:2;stroke:var(--el-color-primary);stroke-linecap:round}.el-loading-spinner i{color:var(--el-color-primary)}.el-loading-fade-enter-from,.el-loading-fade-leave-to{opacity:0}@-webkit-keyframes loading-rotate{to{transform:rotate(360deg)}}@keyframes loading-rotate{to{transform:rotate(360deg)}}@-webkit-keyframes loading-dash{0%{stroke-dasharray:1,200;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-40px}to{stroke-dasharray:90,150;stroke-dashoffset:-120px}}@keyframes loading-dash{0%{stroke-dasharray:1,200;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-40px}to{stroke-dasharray:90,150;stroke-dashoffset:-120px}}.el-main{--el-main-padding:20px;display:block;flex:1;flex-basis:auto;overflow:auto;box-sizing:border-box;padding:var(--el-main-padding)}:root{--el-menu-active-color:var(--el-color-primary);--el-menu-text-color:var(--el-text-color-primary);--el-menu-hover-text-color:var(--el-color-primary);--el-menu-bg-color:var(--el-fill-color-blank);--el-menu-hover-bg-color:var(--el-color-primary-light-9);--el-menu-item-height:56px;--el-menu-sub-item-height:calc(var(--el-menu-item-height) - 6px);--el-menu-horizontal-height:60px;--el-menu-horizontal-sub-item-height:36px;--el-menu-item-font-size:var(--el-font-size-base);--el-menu-item-hover-fill:var(--el-color-primary-light-9);--el-menu-border-color:var(--el-border-color);--el-menu-base-level-padding:20px;--el-menu-level-padding:20px;--el-menu-icon-width:24px}.el-menu{border-right:solid 1px var(--el-menu-border-color);list-style:none;position:relative;margin:0;padding-left:0;background-color:var(--el-menu-bg-color);box-sizing:border-box}.el-menu--vertical:not(.el-menu--collapse):not(.el-menu--popup-container) .el-menu-item,.el-menu--vertical:not(.el-menu--collapse):not(.el-menu--popup-container) .el-menu-item-group__title,.el-menu--vertical:not(.el-menu--collapse):not(.el-menu--popup-container) .el-sub-menu__title{white-space:nowrap;padding-left:calc(var(--el-menu-base-level-padding) + var(--el-menu-level) * var(--el-menu-level-padding))}.el-menu:not(.el-menu--collapse) .el-sub-menu__title{padding-right:calc(var(--el-menu-base-level-padding) + var(--el-menu-icon-width))}.el-menu--horizontal{display:flex;flex-wrap:nowrap;border-right:none;height:var(--el-menu-horizontal-height)}.el-menu--horizontal.el-menu--popup-container{height:unset}.el-menu--horizontal.el-menu{border-bottom:solid 1px var(--el-menu-border-color)}.el-menu--horizontal>.el-menu-item{display:inline-flex;justify-content:center;align-items:center;height:100%;margin:0;border-bottom:2px solid transparent;color:var(--el-menu-text-color)}.el-menu--horizontal>.el-menu-item a,.el-menu--horizontal>.el-menu-item a:hover{color:inherit}.el-menu--horizontal>.el-sub-menu:focus,.el-menu--horizontal>.el-sub-menu:hover{outline:0}.el-menu--horizontal>.el-sub-menu:hover .el-sub-menu__title{color:var(--el-menu-hover-text-color)}.el-menu--horizontal>.el-sub-menu.is-active .el-sub-menu__title{border-bottom:2px solid var(--el-menu-active-color);color:var(--el-menu-active-color)}.el-menu--horizontal>.el-sub-menu .el-sub-menu__title{height:100%;border-bottom:2px solid transparent;color:var(--el-menu-text-color)}.el-menu--horizontal>.el-sub-menu .el-sub-menu__title:hover{background-color:var(--el-menu-bg-color)}.el-menu--horizontal .el-menu .el-menu-item,.el-menu--horizontal .el-menu .el-sub-menu__title{background-color:var(--el-menu-bg-color);display:flex;align-items:center;height:var(--el-menu-horizontal-sub-item-height);line-height:var(--el-menu-horizontal-sub-item-height);padding:0 10px;color:var(--el-menu-text-color)}.el-menu--horizontal .el-menu .el-sub-menu__title{padding-right:40px}.el-menu--horizontal .el-menu .el-menu-item.is-active,.el-menu--horizontal .el-menu .el-sub-menu.is-active>.el-sub-menu__title{color:var(--el-menu-active-color)}.el-menu--horizontal .el-menu-item:not(.is-disabled):focus,.el-menu--horizontal .el-menu-item:not(.is-disabled):hover{outline:0;color:var(--el-menu-hover-text-color);background-color:var(--el-menu-hover-bg-color)}.el-menu--horizontal>.el-menu-item.is-active{border-bottom:2px solid var(--el-menu-active-color);color:var(--el-menu-active-color)!important}.el-menu--collapse{width:calc(var(--el-menu-icon-width) + var(--el-menu-base-level-padding) * 2)}.el-menu--collapse>.el-menu-item [class^=el-icon],.el-menu--collapse>.el-menu-item-group>ul>.el-sub-menu>.el-sub-menu__title [class^=el-icon],.el-menu--collapse>.el-sub-menu>.el-sub-menu__title [class^=el-icon]{margin:0;vertical-align:middle;width:var(--el-menu-icon-width);text-align:center}.el-menu--collapse>.el-menu-item .el-sub-menu__icon-arrow,.el-menu--collapse>.el-menu-item-group>ul>.el-sub-menu>.el-sub-menu__title .el-sub-menu__icon-arrow,.el-menu--collapse>.el-sub-menu>.el-sub-menu__title .el-sub-menu__icon-arrow{display:none}.el-menu--collapse>.el-menu-item-group>ul>.el-sub-menu>.el-sub-menu__title>span,.el-menu--collapse>.el-menu-item>span,.el-menu--collapse>.el-sub-menu>.el-sub-menu__title>span{height:0;width:0;overflow:hidden;visibility:hidden;display:inline-block}.el-menu--collapse>.el-menu-item.is-active i{color:inherit}.el-menu--collapse .el-menu .el-sub-menu{min-width:200px}.el-menu--popup{z-index:100;min-width:200px;border:none;padding:5px 0;border-radius:var(--el-border-radius-small);box-shadow:var(--el-box-shadow-light)}.el-menu .el-icon{flex-shrink:0}.el-menu-item{display:flex;align-items:center;height:var(--el-menu-item-height);line-height:var(--el-menu-item-height);font-size:var(--el-menu-item-font-size);color:var(--el-menu-text-color);padding:0 var(--el-menu-base-level-padding);list-style:none;cursor:pointer;position:relative;transition:border-color var(--el-transition-duration),background-color var(--el-transition-duration),color var(--el-transition-duration);box-sizing:border-box;white-space:nowrap}.el-menu-item *{vertical-align:bottom}.el-menu-item i{color:inherit}.el-menu-item:focus,.el-menu-item:hover{outline:0}.el-menu-item:hover{background-color:var(--el-menu-hover-bg-color)}.el-menu-item.is-disabled{opacity:.25;cursor:not-allowed;background:0 0!important}.el-menu-item [class^=el-icon]{margin-right:5px;width:var(--el-menu-icon-width);text-align:center;font-size:18px;vertical-align:middle}.el-menu-item.is-active{color:var(--el-menu-active-color)}.el-menu-item.is-active i{color:inherit}.el-menu-item .el-menu-tooltip__trigger{position:absolute;left:0;top:0;height:100%;width:100%;display:inline-flex;align-items:center;box-sizing:border-box;padding:0 var(--el-menu-base-level-padding)}.el-sub-menu{list-style:none;margin:0;padding-left:0}.el-sub-menu__title{display:flex;align-items:center;height:var(--el-menu-item-height);line-height:var(--el-menu-item-height);font-size:var(--el-menu-item-font-size);color:var(--el-menu-text-color);padding:0 var(--el-menu-base-level-padding);list-style:none;cursor:pointer;position:relative;transition:border-color var(--el-transition-duration),background-color var(--el-transition-duration),color var(--el-transition-duration);box-sizing:border-box;white-space:nowrap}.el-sub-menu__title *{vertical-align:bottom}.el-sub-menu__title i{color:inherit}.el-sub-menu__title:focus,.el-sub-menu__title:hover{outline:0}.el-sub-menu__title.is-disabled{opacity:.25;cursor:not-allowed;background:0 0!important}.el-sub-menu__title:hover{background-color:var(--el-menu-hover-bg-color)}.el-sub-menu .el-menu{border:none}.el-sub-menu .el-menu-item{height:var(--el-menu-sub-item-height);line-height:var(--el-menu-sub-item-height)}.el-sub-menu__hide-arrow .el-sub-menu__icon-arrow{display:none!important}.el-sub-menu.is-active .el-sub-menu__title{border-bottom-color:var(--el-menu-active-color)}.el-sub-menu.is-disabled .el-menu-item,.el-sub-menu.is-disabled .el-sub-menu__title{opacity:.25;cursor:not-allowed;background:0 0!important}.el-sub-menu .el-icon{vertical-align:middle;margin-right:5px;width:var(--el-menu-icon-width);text-align:center;font-size:18px}.el-sub-menu .el-icon.el-sub-menu__icon-more{margin-right:0!important}.el-sub-menu .el-sub-menu__icon-arrow{position:absolute;top:50%;right:var(--el-menu-base-level-padding);margin-top:-6px;transition:transform var(--el-transition-duration);font-size:12px;margin-right:0;width:inherit}.el-menu-item-group>ul{padding:0}.el-menu-item-group__title{padding:7px 0 7px var(--el-menu-base-level-padding);line-height:normal;font-size:12px;color:var(--el-text-color-secondary)}.horizontal-collapse-transition .el-sub-menu__title .el-sub-menu__icon-arrow{transition:var(--el-transition-duration-fast);opacity:0}.el-message-box{--el-messagebox-title-color:var(--el-text-color-primary);--el-messagebox-width:420px;--el-messagebox-border-radius:4px;--el-messagebox-font-size:var(--el-font-size-large);--el-messagebox-content-font-size:var(--el-font-size-base);--el-messagebox-content-color:var(--el-text-color-regular);--el-messagebox-error-font-size:12px;--el-messagebox-padding-primary:15px}.el-message-box{display:inline-block;max-width:var(--el-messagebox-width);width:100%;padding-bottom:10px;vertical-align:middle;background-color:var(--el-bg-color);border-radius:var(--el-messagebox-border-radius);border:1px solid var(--el-border-color-lighter);font-size:var(--el-messagebox-font-size);box-shadow:var(--el-box-shadow-light);text-align:left;overflow:hidden;-webkit-backface-visibility:hidden;backface-visibility:hidden;box-sizing:border-box}.el-message-box:focus{outline:0!important}.el-overlay.is-message-box .el-overlay-message-box{text-align:center;position:fixed;top:0;right:0;bottom:0;left:0;padding:16px;overflow:auto}.el-overlay.is-message-box .el-overlay-message-box:after{content:"";display:inline-block;height:100%;width:0;vertical-align:middle}.el-message-box.is-draggable .el-message-box__header{cursor:move;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.el-message-box__header{position:relative;padding:var(--el-messagebox-padding-primary);padding-bottom:10px}.el-message-box__title{padding-left:0;margin-bottom:0;font-size:var(--el-messagebox-font-size);line-height:1;color:var(--el-messagebox-title-color)}.el-message-box__headerbtn{position:absolute;top:var(--el-messagebox-padding-primary);right:var(--el-messagebox-padding-primary);padding:0;border:none;outline:0;background:0 0;font-size:var(--el-message-close-size,16px);cursor:pointer}.el-message-box__headerbtn .el-message-box__close{color:var(--el-color-info);font-size:inherit}.el-message-box__headerbtn:focus .el-message-box__close,.el-message-box__headerbtn:hover .el-message-box__close{color:var(--el-color-primary)}.el-message-box__content{padding:10px var(--el-messagebox-padding-primary);color:var(--el-messagebox-content-color);font-size:var(--el-messagebox-content-font-size)}.el-message-box__container{position:relative}.el-message-box__input{padding-top:15px}.el-message-box__input div.invalid>input{border-color:var(--el-color-error)}.el-message-box__input div.invalid>input:focus{border-color:var(--el-color-error)}.el-message-box__status{position:absolute;top:50%;transform:translateY(-50%);font-size:24px!important}.el-message-box__status:before{padding-left:1px}.el-message-box__status.el-icon{position:absolute}.el-message-box__status+.el-message-box__message{padding-left:36px;padding-right:12px;word-break:break-word}.el-message-box__status.el-message-box-icon--success{--el-messagebox-color:var(--el-color-success);color:var(--el-messagebox-color)}.el-message-box__status.el-message-box-icon--info{--el-messagebox-color:var(--el-color-info);color:var(--el-messagebox-color)}.el-message-box__status.el-message-box-icon--warning{--el-messagebox-color:var(--el-color-warning);color:var(--el-messagebox-color)}.el-message-box__status.el-message-box-icon--error{--el-messagebox-color:var(--el-color-error);color:var(--el-messagebox-color)}.el-message-box__message{margin:0}.el-message-box__message p{margin:0;line-height:24px}.el-message-box__errormsg{color:var(--el-color-error);font-size:var(--el-messagebox-error-font-size);min-height:18px;margin-top:2px}.el-message-box__btns{padding:5px 15px 0;display:flex;flex-wrap:wrap;justify-content:flex-end;align-items:center}.el-message-box__btns button:nth-child(2){margin-left:10px}.el-message-box__btns-reverse{flex-direction:row-reverse}.el-message-box--center .el-message-box__title{position:relative;display:flex;align-items:center;justify-content:center}.el-message-box--center .el-message-box__status{position:relative;top:auto;padding-right:5px;text-align:center;transform:translateY(-1px)}.el-message-box--center .el-message-box__message{margin-left:0}.el-message-box--center .el-message-box__btns{justify-content:center}.el-message-box--center .el-message-box__content{padding-left:calc(var(--el-messagebox-padding-primary) + 12px);padding-right:calc(var(--el-messagebox-padding-primary) + 12px);text-align:center}.fade-in-linear-enter-active .el-overlay-message-box{-webkit-animation:msgbox-fade-in var(--el-transition-duration);animation:msgbox-fade-in var(--el-transition-duration)}.fade-in-linear-leave-active .el-overlay-message-box{animation:msgbox-fade-in var(--el-transition-duration) reverse}@-webkit-keyframes msgbox-fade-in{0%{transform:translate3d(0,-20px,0);opacity:0}to{transform:translateZ(0);opacity:1}}@keyframes msgbox-fade-in{0%{transform:translate3d(0,-20px,0);opacity:0}to{transform:translateZ(0);opacity:1}}.el-message{--el-message-bg-color:var(--el-color-info-light-9);--el-message-border-color:var(--el-border-color-lighter);--el-message-padding:15px 19px;--el-message-close-size:16px;--el-message-close-icon-color:var(--el-text-color-placeholder);--el-message-close-hover-color:var(--el-text-color-secondary)}.el-message{width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;max-width:calc(100% - 32px);box-sizing:border-box;border-radius:var(--el-border-radius-base);border-width:var(--el-border-width);border-style:var(--el-border-style);border-color:var(--el-message-border-color);position:fixed;left:50%;top:20px;transform:translate(-50%);background-color:var(--el-message-bg-color);transition:opacity var(--el-transition-duration),transform .4s,top .4s;padding:var(--el-message-padding);display:flex;align-items:center}.el-message.is-center{justify-content:center}.el-message.is-closable .el-message__content{padding-right:31px}.el-message p{margin:0}.el-message--success{--el-message-bg-color:var(--el-color-success-light-9);--el-message-border-color:var(--el-color-success-light-8);--el-message-text-color:var(--el-color-success)}.el-message--success .el-message__content{color:var(--el-message-text-color);overflow-wrap:anywhere}.el-message .el-message-icon--success{color:var(--el-message-text-color)}.el-message--info{--el-message-bg-color:var(--el-color-info-light-9);--el-message-border-color:var(--el-color-info-light-8);--el-message-text-color:var(--el-color-info)}.el-message--info .el-message__content{color:var(--el-message-text-color);overflow-wrap:anywhere}.el-message .el-message-icon--info{color:var(--el-message-text-color)}.el-message--warning{--el-message-bg-color:var(--el-color-warning-light-9);--el-message-border-color:var(--el-color-warning-light-8);--el-message-text-color:var(--el-color-warning)}.el-message--warning .el-message__content{color:var(--el-message-text-color);overflow-wrap:anywhere}.el-message .el-message-icon--warning{color:var(--el-message-text-color)}.el-message--error{--el-message-bg-color:var(--el-color-error-light-9);--el-message-border-color:var(--el-color-error-light-8);--el-message-text-color:var(--el-color-error)}.el-message--error .el-message__content{color:var(--el-message-text-color);overflow-wrap:anywhere}.el-message .el-message-icon--error{color:var(--el-message-text-color)}.el-message__icon{margin-right:10px}.el-message .el-message__badge{position:absolute;top:-8px;right:-8px}.el-message__content{padding:0;font-size:14px;line-height:1}.el-message__content:focus{outline-width:0}.el-message .el-message__closeBtn{position:absolute;top:50%;right:19px;transform:translateY(-50%);cursor:pointer;color:var(--el-message-close-icon-color);font-size:var(--el-message-close-size)}.el-message .el-message__closeBtn:focus{outline-width:0}.el-message .el-message__closeBtn:hover{color:var(--el-message-close-hover-color)}.el-message-fade-enter-from,.el-message-fade-leave-to{opacity:0;transform:translate(-50%,-100%)}.el-notification{--el-notification-width:330px;--el-notification-padding:14px 26px 14px 13px;--el-notification-radius:8px;--el-notification-shadow:var(--el-box-shadow-light);--el-notification-border-color:var(--el-border-color-lighter);--el-notification-icon-size:24px;--el-notification-close-font-size:var(--el-message-close-size, 16px);--el-notification-group-margin-left:13px;--el-notification-group-margin-right:8px;--el-notification-content-font-size:var(--el-font-size-base);--el-notification-content-color:var(--el-text-color-regular);--el-notification-title-font-size:16px;--el-notification-title-color:var(--el-text-color-primary);--el-notification-close-color:var(--el-text-color-secondary);--el-notification-close-hover-color:var(--el-text-color-regular)}.el-notification{display:flex;width:var(--el-notification-width);padding:var(--el-notification-padding);border-radius:var(--el-notification-radius);box-sizing:border-box;border:1px solid var(--el-notification-border-color);position:fixed;background-color:var(--el-bg-color-overlay);box-shadow:var(--el-notification-shadow);transition:opacity var(--el-transition-duration),transform var(--el-transition-duration),left var(--el-transition-duration),right var(--el-transition-duration),top .4s,bottom var(--el-transition-duration);overflow-wrap:anywhere;overflow:hidden;z-index:9999}.el-notification.right{right:16px}.el-notification.left{left:16px}.el-notification__group{margin-left:var(--el-notification-group-margin-left);margin-right:var(--el-notification-group-margin-right)}.el-notification__title{font-weight:700;font-size:var(--el-notification-title-font-size);line-height:var(--el-notification-icon-size);color:var(--el-notification-title-color);margin:0}.el-notification__content{font-size:var(--el-notification-content-font-size);line-height:24px;margin:6px 0 0;color:var(--el-notification-content-color);text-align:justify}.el-notification__content p{margin:0}.el-notification .el-notification__icon{height:var(--el-notification-icon-size);width:var(--el-notification-icon-size);font-size:var(--el-notification-icon-size)}.el-notification .el-notification__closeBtn{position:absolute;top:18px;right:15px;cursor:pointer;color:var(--el-notification-close-color);font-size:var(--el-notification-close-font-size)}.el-notification .el-notification__closeBtn:hover{color:var(--el-notification-close-hover-color)}.el-notification .el-notification--success{--el-notification-icon-color:var(--el-color-success);color:var(--el-notification-icon-color)}.el-notification .el-notification--info{--el-notification-icon-color:var(--el-color-info);color:var(--el-notification-icon-color)}.el-notification .el-notification--warning{--el-notification-icon-color:var(--el-color-warning);color:var(--el-notification-icon-color)}.el-notification .el-notification--error{--el-notification-icon-color:var(--el-color-error);color:var(--el-notification-icon-color)}.el-notification-fade-enter-from.right{right:0;transform:translate(100%)}.el-notification-fade-enter-from.left{left:0;transform:translate(-100%)}.el-notification-fade-leave-to{opacity:0}.el-overlay{position:fixed;top:0;right:0;bottom:0;left:0;z-index:2000;height:100%;background-color:var(--el-overlay-color-lighter);overflow:auto}.el-overlay .el-overlay-root{height:0}.el-page-header.is-contentful .el-page-header__main{border-top:1px solid var(--el-border-color-light);margin-top:16px}.el-page-header__header{display:flex;align-items:center;justify-content:space-between;line-height:24px}.el-page-header__left{display:flex;align-items:center;margin-right:40px;position:relative}.el-page-header__back{display:flex;align-items:center;cursor:pointer}.el-page-header__left .el-divider--vertical{margin:0 16px}.el-page-header__icon{font-size:16px;margin-right:10px;display:flex;align-items:center}.el-page-header__icon .el-icon{font-size:inherit}.el-page-header__title{font-size:14px;font-weight:500}.el-page-header__content{font-size:18px;color:var(--el-text-color-primary)}.el-page-header__breadcrumb{margin-bottom:16px}.el-pagination{--el-pagination-font-size:14px;--el-pagination-bg-color:var(--el-fill-color-blank);--el-pagination-text-color:var(--el-text-color-primary);--el-pagination-border-radius:2px;--el-pagination-button-color:var(--el-text-color-primary);--el-pagination-button-width:32px;--el-pagination-button-height:32px;--el-pagination-button-disabled-color:var(--el-text-color-placeholder);--el-pagination-button-disabled-bg-color:var(--el-fill-color-blank);--el-pagination-button-bg-color:var(--el-fill-color);--el-pagination-hover-color:var(--el-color-primary);--el-pagination-font-size-small:12px;--el-pagination-button-width-small:24px;--el-pagination-button-height-small:24px;--el-pagination-item-gap:16px;white-space:nowrap;color:var(--el-pagination-text-color);font-size:var(--el-pagination-font-size);font-weight:400;display:flex;align-items:center}.el-pagination .el-input__inner{text-align:center;-moz-appearance:textfield}.el-pagination .el-select .el-input{width:128px}.el-pagination button{display:flex;justify-content:center;align-items:center;font-size:var(--el-pagination-font-size);min-width:var(--el-pagination-button-width);height:var(--el-pagination-button-height);line-height:var(--el-pagination-button-height);color:var(--el-pagination-button-color);background:var(--el-pagination-bg-color);padding:0 4px;border:none;border-radius:var(--el-pagination-border-radius);cursor:pointer;text-align:center;box-sizing:border-box}.el-pagination button *{pointer-events:none}.el-pagination button:focus{outline:0}.el-pagination button:hover{color:var(--el-pagination-hover-color)}.el-pagination button.is-active{color:var(--el-pagination-hover-color);cursor:default;font-weight:700}.el-pagination button.is-active.is-disabled{font-weight:700;color:var(--el-text-color-secondary)}.el-pagination button.is-disabled,.el-pagination button:disabled{color:var(--el-pagination-button-disabled-color);background-color:var(--el-pagination-button-disabled-bg-color);cursor:not-allowed}.el-pagination button:focus-visible{outline:1px solid var(--el-pagination-hover-color);outline-offset:-1px}.el-pagination .btn-next .el-icon,.el-pagination .btn-prev .el-icon{display:block;font-size:12px;font-weight:700;width:inherit}.el-pagination>.is-first{margin-left:0!important}.el-pagination>.is-last{margin-right:0!important}.el-pagination .btn-prev{margin-left:var(--el-pagination-item-gap)}.el-pagination__sizes,.el-pagination__total{margin-left:var(--el-pagination-item-gap);font-weight:400;color:var(--el-text-color-regular)}.el-pagination__total[disabled=true]{color:var(--el-text-color-placeholder)}.el-pagination__jump{display:flex;align-items:center;margin-left:var(--el-pagination-item-gap);font-weight:400;color:var(--el-text-color-regular)}.el-pagination__jump[disabled=true]{color:var(--el-text-color-placeholder)}.el-pagination__goto{margin-right:8px}.el-pagination__editor{text-align:center;box-sizing:border-box}.el-pagination__editor.el-input{width:56px}.el-pagination__editor .el-input__inner::-webkit-inner-spin-button,.el-pagination__editor .el-input__inner::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.el-pagination__classifier{margin-left:8px}.el-pagination__rightwrapper{flex:1;display:flex;align-items:center;justify-content:flex-end}.el-pagination.is-background .btn-next,.el-pagination.is-background .btn-prev,.el-pagination.is-background .el-pager li{margin:0 4px;background-color:var(--el-pagination-button-bg-color)}.el-pagination.is-background .btn-next.is-active,.el-pagination.is-background .btn-prev.is-active,.el-pagination.is-background .el-pager li.is-active{background-color:var(--el-color-primary);color:var(--el-color-white)}.el-pagination.is-background .btn-next.is-disabled,.el-pagination.is-background .btn-next:disabled,.el-pagination.is-background .btn-prev.is-disabled,.el-pagination.is-background .btn-prev:disabled,.el-pagination.is-background .el-pager li.is-disabled,.el-pagination.is-background .el-pager li:disabled{color:var(--el-text-color-placeholder);background-color:var(--el-disabled-bg-color)}.el-pagination.is-background .btn-next.is-disabled.is-active,.el-pagination.is-background .btn-next:disabled.is-active,.el-pagination.is-background .btn-prev.is-disabled.is-active,.el-pagination.is-background .btn-prev:disabled.is-active,.el-pagination.is-background .el-pager li.is-disabled.is-active,.el-pagination.is-background .el-pager li:disabled.is-active{color:var(--el-text-color-secondary);background-color:var(--el-fill-color-dark)}.el-pagination.is-background .btn-prev{margin-left:var(--el-pagination-item-gap)}.el-pagination--small .btn-next,.el-pagination--small .btn-prev,.el-pagination--small .el-pager li{height:var(--el-pagination-button-height-small);line-height:var(--el-pagination-button-height-small);font-size:var(--el-pagination-font-size-small);min-width:var(--el-pagination-button-width-small)}.el-pagination--small button,.el-pagination--small span:not([class*=suffix]){font-size:var(--el-pagination-font-size-small)}.el-pagination--small .el-select .el-input{width:100px}.el-pager{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;list-style:none;font-size:0;padding:0;margin:0;display:flex;align-items:center}.el-pager li{display:flex;justify-content:center;align-items:center;font-size:var(--el-pagination-font-size);min-width:var(--el-pagination-button-width);height:var(--el-pagination-button-height);line-height:var(--el-pagination-button-height);color:var(--el-pagination-button-color);background:var(--el-pagination-bg-color);padding:0 4px;border:none;border-radius:var(--el-pagination-border-radius);cursor:pointer;text-align:center;box-sizing:border-box}.el-pager li *{pointer-events:none}.el-pager li:focus{outline:0}.el-pager li:hover{color:var(--el-pagination-hover-color)}.el-pager li.is-active{color:var(--el-pagination-hover-color);cursor:default;font-weight:700}.el-pager li.is-active.is-disabled{font-weight:700;color:var(--el-text-color-secondary)}.el-pager li.is-disabled,.el-pager li:disabled{color:var(--el-pagination-button-disabled-color);background-color:var(--el-pagination-button-disabled-bg-color);cursor:not-allowed}.el-pager li:focus-visible{outline:1px solid var(--el-pagination-hover-color);outline-offset:-1px}.el-popconfirm__main{display:flex;align-items:center}.el-popconfirm__icon{margin-right:5px}.el-popconfirm__action{text-align:right;margin-top:8px}.el-popover{--el-popover-bg-color:var(--el-bg-color-overlay);--el-popover-font-size:var(--el-font-size-base);--el-popover-border-color:var(--el-border-color-lighter);--el-popover-padding:12px;--el-popover-padding-large:18px 20px;--el-popover-title-font-size:16px;--el-popover-title-text-color:var(--el-text-color-primary);--el-popover-border-radius:4px}.el-popover.el-popper{background:var(--el-popover-bg-color);min-width:150px;border-radius:var(--el-popover-border-radius);border:1px solid var(--el-popover-border-color);padding:var(--el-popover-padding);z-index:var(--el-index-popper);color:var(--el-text-color-regular);line-height:1.4;text-align:justify;font-size:var(--el-popover-font-size);box-shadow:var(--el-box-shadow-light);word-break:break-all;box-sizing:border-box}.el-popover.el-popper--plain{padding:var(--el-popover-padding-large)}.el-popover__title{color:var(--el-popover-title-text-color);font-size:var(--el-popover-title-font-size);line-height:1;margin-bottom:12px}.el-popover__reference:focus:hover,.el-popover__reference:focus:not(.focusing){outline-width:0}.el-popover.el-popper.is-dark{--el-popover-bg-color:var(--el-text-color-primary);--el-popover-border-color:var(--el-text-color-primary);--el-popover-title-text-color:var(--el-bg-color);color:var(--el-bg-color)}.el-popover.el-popper:focus,.el-popover.el-popper:focus:active{outline-width:0}.el-progress{position:relative;line-height:1;display:flex;align-items:center}.el-progress__text{font-size:14px;color:var(--el-text-color-regular);margin-left:5px;min-width:50px;line-height:1}.el-progress__text i{vertical-align:middle;display:block}.el-progress--circle,.el-progress--dashboard{display:inline-block}.el-progress--circle .el-progress__text,.el-progress--dashboard .el-progress__text{position:absolute;top:50%;left:0;width:100%;text-align:center;margin:0;transform:translateY(-50%)}.el-progress--circle .el-progress__text i,.el-progress--dashboard .el-progress__text i{vertical-align:middle;display:inline-block}.el-progress--without-text .el-progress__text{display:none}.el-progress--without-text .el-progress-bar{padding-right:0;margin-right:0;display:block}.el-progress--text-inside .el-progress-bar{padding-right:0;margin-right:0}.el-progress.is-success .el-progress-bar__inner{background-color:var(--el-color-success)}.el-progress.is-success .el-progress__text{color:var(--el-color-success)}.el-progress.is-warning .el-progress-bar__inner{background-color:var(--el-color-warning)}.el-progress.is-warning .el-progress__text{color:var(--el-color-warning)}.el-progress.is-exception .el-progress-bar__inner{background-color:var(--el-color-danger)}.el-progress.is-exception .el-progress__text{color:var(--el-color-danger)}.el-progress-bar{flex-grow:1;box-sizing:border-box}.el-progress-bar__outer{height:6px;border-radius:100px;background-color:var(--el-border-color-lighter);overflow:hidden;position:relative;vertical-align:middle}.el-progress-bar__inner{position:absolute;left:0;top:0;height:100%;background-color:var(--el-color-primary);text-align:right;border-radius:100px;line-height:1;white-space:nowrap;transition:width .6s ease}.el-progress-bar__inner:after{display:inline-block;content:"";height:100%;vertical-align:middle}.el-progress-bar__inner--indeterminate{transform:translateZ(0);-webkit-animation:indeterminate 3s infinite;animation:indeterminate 3s infinite}.el-progress-bar__inner--striped{background-image:linear-gradient(45deg,rgba(0,0,0,.1) 25%,transparent 25%,transparent 50%,rgba(0,0,0,.1) 50%,rgba(0,0,0,.1) 75%,transparent 75%,transparent);background-size:1.25em 1.25em}.el-progress-bar__inner--striped.el-progress-bar__inner--striped-flow{-webkit-animation:striped-flow 3s linear infinite;animation:striped-flow 3s linear infinite}.el-progress-bar__innerText{display:inline-block;vertical-align:middle;color:#fff;font-size:12px;margin:0 5px}@-webkit-keyframes progress{0%{background-position:0 0}to{background-position:32px 0}}@keyframes progress{0%{background-position:0 0}to{background-position:32px 0}}@-webkit-keyframes indeterminate{0%{left:-100%}to{left:100%}}@keyframes indeterminate{0%{left:-100%}to{left:100%}}@-webkit-keyframes striped-flow{0%{background-position:-100%}to{background-position:100%}}@keyframes striped-flow{0%{background-position:-100%}to{background-position:100%}}.el-radio-button{--el-radio-button-checked-bg-color:var(--el-color-primary);--el-radio-button-checked-text-color:var(--el-color-white);--el-radio-button-checked-border-color:var(--el-color-primary);--el-radio-button-disabled-checked-fill:var(--el-border-color-extra-light)}.el-radio-button{position:relative;display:inline-block;outline:0}.el-radio-button__inner{display:inline-block;line-height:1;white-space:nowrap;vertical-align:middle;background:var(--el-button-bg-color,var(--el-fill-color-blank));border:var(--el-border);font-weight:var(--el-button-font-weight,var(--el-font-weight-primary));border-left:0;color:var(--el-button-text-color,var(--el-text-color-regular));-webkit-appearance:none;text-align:center;box-sizing:border-box;outline:0;margin:0;position:relative;cursor:pointer;transition:var(--el-transition-all);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;padding:8px 15px;font-size:var(--el-font-size-base);border-radius:0}.el-radio-button__inner.is-round{padding:8px 15px}.el-radio-button__inner:hover{color:var(--el-color-primary)}.el-radio-button__inner [class*=el-icon-]{line-height:.9}.el-radio-button__inner [class*=el-icon-]+span{margin-left:5px}.el-radio-button:first-child .el-radio-button__inner{border-left:var(--el-border);border-radius:var(--el-border-radius-base) 0 0 var(--el-border-radius-base);box-shadow:none!important}.el-radio-button__original-radio{opacity:0;outline:0;position:absolute;z-index:-1}.el-radio-button__original-radio:checked+.el-radio-button__inner{color:var(--el-radio-button-checked-text-color,var(--el-color-white));background-color:var(--el-radio-button-checked-bg-color,var(--el-color-primary));border-color:var(--el-radio-button-checked-border-color,var(--el-color-primary));box-shadow:-1px 0 0 0 var(--el-radio-button-checked-border-color,var(--el-color-primary))}.el-radio-button__original-radio:focus-visible+.el-radio-button__inner{border-left:var(--el-border);border-left-color:var(--el-radio-button-checked-border-color,var(--el-color-primary));outline:2px solid var(--el-radio-button-checked-border-color);outline-offset:1px;z-index:2;border-radius:var(--el-border-radius-base);box-shadow:none}.el-radio-button__original-radio:disabled+.el-radio-button__inner{color:var(--el-disabled-text-color);cursor:not-allowed;background-image:none;background-color:var(--el-button-disabled-bg-color,var(--el-fill-color-blank));border-color:var(--el-button-disabled-border-color,var(--el-border-color-light));box-shadow:none}.el-radio-button__original-radio:disabled:checked+.el-radio-button__inner{background-color:var(--el-radio-button-disabled-checked-fill)}.el-radio-button:last-child .el-radio-button__inner{border-radius:0 var(--el-border-radius-base) var(--el-border-radius-base) 0}.el-radio-button:first-child:last-child .el-radio-button__inner{border-radius:var(--el-border-radius-base)}.el-radio-button--large .el-radio-button__inner{padding:12px 19px;font-size:var(--el-font-size-base);border-radius:0}.el-radio-button--large .el-radio-button__inner.is-round{padding:12px 19px}.el-radio-button--small .el-radio-button__inner{padding:5px 11px;font-size:12px;border-radius:0}.el-radio-button--small .el-radio-button__inner.is-round{padding:5px 11px}.el-radio-group{display:inline-flex;align-items:center;flex-wrap:wrap;font-size:0}.el-radio{--el-radio-font-size:var(--el-font-size-base);--el-radio-text-color:var(--el-text-color-regular);--el-radio-font-weight:var(--el-font-weight-primary);--el-radio-input-height:14px;--el-radio-input-width:14px;--el-radio-input-border-radius:var(--el-border-radius-circle);--el-radio-input-bg-color:var(--el-fill-color-blank);--el-radio-input-border:var(--el-border);--el-radio-input-border-color:var(--el-border-color);--el-radio-input-border-color-hover:var(--el-color-primary)}.el-radio{color:var(--el-radio-text-color);font-weight:var(--el-radio-font-weight);position:relative;cursor:pointer;display:inline-flex;align-items:center;white-space:nowrap;outline:0;font-size:var(--el-font-size-base);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;margin-right:32px;height:32px}.el-radio.el-radio--large{height:40px}.el-radio.el-radio--small{height:24px}.el-radio.is-bordered{padding:0 15px 0 9px;border-radius:var(--el-border-radius-base);border:var(--el-border);box-sizing:border-box}.el-radio.is-bordered.is-checked{border-color:var(--el-color-primary)}.el-radio.is-bordered.is-disabled{cursor:not-allowed;border-color:var(--el-border-color-lighter)}.el-radio.is-bordered.el-radio--large{padding:0 19px 0 11px;border-radius:var(--el-border-radius-base)}.el-radio.is-bordered.el-radio--large .el-radio__label{font-size:var(--el-font-size-base)}.el-radio.is-bordered.el-radio--large .el-radio__inner{height:14px;width:14px}.el-radio.is-bordered.el-radio--small{padding:0 11px 0 7px;border-radius:var(--el-border-radius-base)}.el-radio.is-bordered.el-radio--small .el-radio__label{font-size:12px}.el-radio.is-bordered.el-radio--small .el-radio__inner{height:12px;width:12px}.el-radio:last-child{margin-right:0}.el-radio__input{white-space:nowrap;cursor:pointer;outline:0;display:inline-flex;position:relative;vertical-align:middle}.el-radio__input.is-disabled .el-radio__inner{background-color:var(--el-disabled-bg-color);border-color:var(--el-disabled-border-color);cursor:not-allowed}.el-radio__input.is-disabled .el-radio__inner:after{cursor:not-allowed;background-color:var(--el-disabled-bg-color)}.el-radio__input.is-disabled .el-radio__inner+.el-radio__label{cursor:not-allowed}.el-radio__input.is-disabled.is-checked .el-radio__inner{background-color:var(--el-disabled-bg-color);border-color:var(--el-disabled-border-color)}.el-radio__input.is-disabled.is-checked .el-radio__inner:after{background-color:var(--el-text-color-placeholder)}.el-radio__input.is-disabled+span.el-radio__label{color:var(--el-text-color-placeholder);cursor:not-allowed}.el-radio__input.is-checked .el-radio__inner{border-color:var(--el-color-primary);background:var(--el-color-primary)}.el-radio__input.is-checked .el-radio__inner:after{transform:translate(-50%,-50%) scale(1)}.el-radio__input.is-checked+.el-radio__label{color:var(--el-color-primary)}.el-radio__input.is-focus .el-radio__inner{border-color:var(--el-radio-input-border-color-hover)}.el-radio__inner{border:var(--el-radio-input-border);border-radius:var(--el-radio-input-border-radius);width:var(--el-radio-input-width);height:var(--el-radio-input-height);background-color:var(--el-radio-input-bg-color);position:relative;cursor:pointer;display:inline-block;box-sizing:border-box}.el-radio__inner:hover{border-color:var(--el-radio-input-border-color-hover)}.el-radio__inner:after{width:4px;height:4px;border-radius:var(--el-radio-input-border-radius);background-color:var(--el-color-white);content:"";position:absolute;left:50%;top:50%;transform:translate(-50%,-50%) scale(0);transition:transform .15s ease-in}.el-radio__original{opacity:0;outline:0;position:absolute;z-index:-1;top:0;left:0;right:0;bottom:0;margin:0}.el-radio__original:focus-visible+.el-radio__inner{outline:2px solid var(--el-radio-input-border-color-hover);outline-offset:1px;border-radius:var(--el-radio-input-border-radius)}.el-radio:focus:not(:focus-visible):not(.is-focus):not(:active):not(.is-disabled) .el-radio__inner{box-shadow:0 0 2px 2px var(--el-radio-input-border-color-hover)}.el-radio__label{font-size:var(--el-radio-font-size);padding-left:8px}.el-radio.el-radio--large .el-radio__label{font-size:14px}.el-radio.el-radio--large .el-radio__inner{width:14px;height:14px}.el-radio.el-radio--small .el-radio__label{font-size:12px}.el-radio.el-radio--small .el-radio__inner{width:12px;height:12px}.el-rate{--el-rate-height:20px;--el-rate-font-size:var(--el-font-size-base);--el-rate-icon-size:18px;--el-rate-icon-margin:6px;--el-rate-void-color:var(--el-border-color-darker);--el-rate-fill-color:#f7ba2a;--el-rate-disabled-void-color:var(--el-fill-color);--el-rate-text-color:var(--el-text-color-primary)}.el-rate{display:inline-flex;align-items:center;height:32px}.el-rate:active,.el-rate:focus{outline:0}.el-rate__item{cursor:pointer;display:inline-block;position:relative;font-size:0;vertical-align:middle;color:var(--el-rate-void-color);line-height:normal}.el-rate .el-rate__icon{position:relative;display:inline-block;font-size:var(--el-rate-icon-size);margin-right:var(--el-rate-icon-margin);transition:var(--el-transition-duration)}.el-rate .el-rate__icon.hover{transform:scale(1.15)}.el-rate .el-rate__icon .path2{position:absolute;left:0;top:0}.el-rate .el-rate__icon.is-active{color:var(--el-rate-fill-color)}.el-rate__decimal{position:absolute;top:0;left:0;display:inline-block;overflow:hidden;color:var(--el-rate-fill-color)}.el-rate__decimal--box{position:absolute;top:0;left:0}.el-rate__text{font-size:var(--el-rate-font-size);vertical-align:middle;color:var(--el-rate-text-color)}.el-rate--large{height:40px}.el-rate--small{height:24px}.el-rate--small .el-rate__icon{font-size:14px}.el-rate.is-disabled .el-rate__item{cursor:auto;color:var(--el-rate-disabled-void-color)}.el-result{--el-result-padding:40px 30px;--el-result-icon-font-size:64px;--el-result-title-font-size:20px;--el-result-title-margin-top:20px;--el-result-subtitle-margin-top:10px;--el-result-extra-margin-top:30px}.el-result{display:flex;justify-content:center;align-items:center;flex-direction:column;text-align:center;box-sizing:border-box;padding:var(--el-result-padding)}.el-result__icon svg{width:var(--el-result-icon-font-size);height:var(--el-result-icon-font-size)}.el-result__title{margin-top:var(--el-result-title-margin-top)}.el-result__title p{margin:0;font-size:var(--el-result-title-font-size);color:var(--el-text-color-primary);line-height:1.3}.el-result__subtitle{margin-top:var(--el-result-subtitle-margin-top)}.el-result__subtitle p{margin:0;font-size:var(--el-font-size-base);color:var(--el-text-color-regular);line-height:1.3}.el-result__extra{margin-top:var(--el-result-extra-margin-top)}.el-result .icon-primary{--el-result-color:var(--el-color-primary);color:var(--el-result-color)}.el-result .icon-success{--el-result-color:var(--el-color-success);color:var(--el-result-color)}.el-result .icon-warning{--el-result-color:var(--el-color-warning);color:var(--el-result-color)}.el-result .icon-danger{--el-result-color:var(--el-color-danger);color:var(--el-result-color)}.el-result .icon-error{--el-result-color:var(--el-color-error);color:var(--el-result-color)}.el-result .icon-info{--el-result-color:var(--el-color-info);color:var(--el-result-color)}.el-row{display:flex;flex-wrap:wrap;position:relative;box-sizing:border-box}.el-row.is-justify-center{justify-content:center}.el-row.is-justify-end{justify-content:flex-end}.el-row.is-justify-space-between{justify-content:space-between}.el-row.is-justify-space-around{justify-content:space-around}.el-row.is-justify-space-evenly{justify-content:space-evenly}.el-row.is-align-top{align-items:flex-start}.el-row.is-align-middle{align-items:center}.el-row.is-align-bottom{align-items:flex-end}.el-scrollbar{--el-scrollbar-opacity:.3;--el-scrollbar-bg-color:var(--el-text-color-secondary);--el-scrollbar-hover-opacity:.5;--el-scrollbar-hover-bg-color:var(--el-text-color-secondary)}.el-scrollbar{overflow:hidden;position:relative;height:100%}.el-scrollbar__wrap{overflow:auto;height:100%}.el-scrollbar__wrap--hidden-default{scrollbar-width:none}.el-scrollbar__wrap--hidden-default::-webkit-scrollbar{display:none}.el-scrollbar__thumb{position:relative;display:block;width:0;height:0;cursor:pointer;border-radius:inherit;background-color:var(--el-scrollbar-bg-color,var(--el-text-color-secondary));transition:var(--el-transition-duration) background-color;opacity:var(--el-scrollbar-opacity,.3)}.el-scrollbar__thumb:hover{background-color:var(--el-scrollbar-hover-bg-color,var(--el-text-color-secondary));opacity:var(--el-scrollbar-hover-opacity,.5)}.el-scrollbar__bar{position:absolute;right:2px;bottom:2px;z-index:1;border-radius:4px}.el-scrollbar__bar.is-vertical{width:6px;top:2px}.el-scrollbar__bar.is-vertical>div{width:100%}.el-scrollbar__bar.is-horizontal{height:6px;left:2px}.el-scrollbar__bar.is-horizontal>div{height:100%}.el-scrollbar-fade-enter-active{transition:opacity .34s ease-out}.el-scrollbar-fade-leave-active{transition:opacity .12s ease-out}.el-scrollbar-fade-enter-from,.el-scrollbar-fade-leave-active{opacity:0}.el-select-dropdown__option-item.is-selected:not(.is-multiple).is-disabled{color:var(--el-text-color-disabled)}.el-select-dropdown__option-item.is-selected:not(.is-multiple).is-disabled:after{background-color:var(--el-text-color-disabled)}.el-select-dropdown__option-item:hover:not(.hover){background-color:transparent}.el-select-dropdown.is-multiple .el-select-dropdown__option-item.is-disabled.is-selected{color:var(--el-text-color-disabled)}.el-select-dropdown__list{list-style:none;margin:6px 0!important;padding:0!important;box-sizing:border-box}.el-select-dropdown__option-item{font-size:var(--el-select-font-size);padding:0 32px 0 20px;position:relative;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--el-text-color-regular);height:34px;line-height:34px;box-sizing:border-box;cursor:pointer}.el-select-dropdown__option-item.is-disabled{color:var(--el-text-color-placeholder);cursor:not-allowed}.el-select-dropdown__option-item.is-disabled:hover{background-color:var(--el-bg-color)}.el-select-dropdown__option-item.is-selected{background-color:var(--el-fill-color-light);font-weight:700}.el-select-dropdown__option-item.is-selected:not(.is-multiple){color:var(--el-color-primary)}.el-select-dropdown__option-item.hover{background-color:var(--el-fill-color-light)!important}.el-select-dropdown__option-item:hover{background-color:var(--el-fill-color-light)}.el-select-dropdown.is-multiple .el-select-dropdown__option-item.is-selected{color:var(--el-color-primary);background-color:var(--el-bg-color-overlay)}.el-select-dropdown.is-multiple .el-select-dropdown__option-item.is-selected .el-icon{position:absolute;right:20px;top:0;height:inherit;font-size:12px}.el-select-dropdown.is-multiple .el-select-dropdown__option-item.is-selected .el-icon svg{height:inherit;vertical-align:middle}.el-select-group{margin:0;padding:0}.el-select-group__wrap{position:relative;list-style:none;margin:0;padding:0}.el-select-group__wrap:not(:last-of-type){padding-bottom:24px}.el-select-group__wrap:not(:last-of-type):after{content:"";position:absolute;display:block;left:20px;right:20px;bottom:12px;height:1px;background:var(--el-border-color-light)}.el-select-group__split-dash{position:absolute;left:20px;right:20px;height:1px;background:var(--el-border-color-light)}.el-select-group__title{padding-left:20px;font-size:12px;color:var(--el-color-info);line-height:30px}.el-select-group .el-select-dropdown__item{padding-left:20px}.el-select-v2{--el-select-border-color-hover:var(--el-border-color-hover);--el-select-disabled-border:var(--el-disabled-border-color);--el-select-font-size:var(--el-font-size-base);--el-select-close-hover-color:var(--el-text-color-secondary);--el-select-input-color:var(--el-text-color-placeholder);--el-select-multiple-input-color:var(--el-text-color-regular);--el-select-input-focus-border-color:var(--el-color-primary);--el-select-input-font-size:14px}.el-select-v2{display:inline-block;position:relative;vertical-align:middle;font-size:14px}.el-select-v2__wrapper{display:flex;align-items:center;flex-wrap:wrap;position:relative;box-sizing:border-box;cursor:pointer;padding:1px 30px 1px 0;border:1px solid var(--el-border-color);border-radius:var(--el-border-radius-base);background-color:var(--el-fill-color-blank);transition:var(--el-transition-duration)}.el-select-v2__wrapper:hover{border-color:var(--el-text-color-placeholder)}.el-select-v2__wrapper.is-filterable{cursor:text}.el-select-v2__wrapper.is-focused{border-color:var(--el-color-primary)}.el-select-v2__wrapper.is-hovering:not(.is-focused){border-color:var(--el-border-color-hover)}.el-select-v2__wrapper.is-disabled{cursor:not-allowed;background-color:var(--el-fill-color-light);color:var(--el-text-color-placeholder);border-color:var(--el-select-disabled-border)}.el-select-v2__wrapper.is-disabled:hover{border-color:var(--el-select-disabled-border)}.el-select-v2__wrapper.is-disabled.is-focus{border-color:var(--el-input-focus-border-color)}.el-select-v2__wrapper.is-disabled .is-transparent{opacity:1;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.el-select-v2__wrapper.is-disabled .el-select-v2__caret,.el-select-v2__wrapper.is-disabled .el-select-v2__combobox-input{cursor:not-allowed}.el-select-v2__wrapper .el-select-v2__input-wrapper{box-sizing:border-box;position:relative;-webkit-margin-start:12px;margin-inline-start:12px;max-width:100%;overflow:hidden}.el-select-v2__wrapper,.el-select-v2__wrapper .el-select-v2__input-wrapper{line-height:32px}.el-select-v2__wrapper .el-select-v2__input-wrapper input{--el-input-inner-height:calc(var(--el-component-size, 32px) - 8px);height:var(--el-input-inner-height);line-height:var(--el-input-inner-height);min-width:4px;width:100%;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;border:none;margin:2px 0;outline:0;padding:0}.el-select-v2 .el-select-v2__tags-text{display:inline-block;line-height:normal;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.el-select-v2__empty{padding:10px 0;margin:0;text-align:center;color:var(--el-text-color-secondary);font-size:14px}.el-select-v2__popper.el-popper{background:var(--el-bg-color-overlay);border:1px solid var(--el-border-color-light);box-shadow:var(--el-box-shadow-light)}.el-select-v2__popper.el-popper .el-popper__arrow:before{border:1px solid var(--el-border-color-light)}.el-select-v2__popper.el-popper[data-popper-placement^=top] .el-popper__arrow:before{border-top-color:transparent;border-left-color:transparent}.el-select-v2__popper.el-popper[data-popper-placement^=bottom] .el-popper__arrow:before{border-bottom-color:transparent;border-right-color:transparent}.el-select-v2__popper.el-popper[data-popper-placement^=left] .el-popper__arrow:before{border-left-color:transparent;border-bottom-color:transparent}.el-select-v2__popper.el-popper[data-popper-placement^=right] .el-popper__arrow:before{border-right-color:transparent;border-top-color:transparent}.el-select-v2--large .el-select-v2__wrapper .el-select-v2__combobox-input{height:32px}.el-select-v2--large .el-select-v2__caret,.el-select-v2--large .el-select-v2__suffix{height:40px}.el-select-v2--large .el-select-v2__placeholder{font-size:14px;line-height:40px}.el-select-v2--small .el-select-v2__wrapper .el-select-v2__combobox-input{height:16px}.el-select-v2--small .el-select-v2__caret,.el-select-v2--small .el-select-v2__suffix{height:24px}.el-select-v2--small .el-select-v2__placeholder{font-size:12px;line-height:24px}.el-select-v2 .el-select-v2__selection>span{display:inline-block}.el-select-v2:hover .el-select-v2__combobox-input{border-color:var(--el-select-border-color-hover)}.el-select-v2 .el-select__selection-text{text-overflow:ellipsis;display:inline-block;overflow-x:hidden;vertical-align:bottom}.el-select-v2 .el-select-v2__combobox-input{padding-right:35px;display:block;color:var(--el-text-color-regular)}.el-select-v2 .el-select-v2__combobox-input:focus{border-color:var(--el-select-input-focus-border-color)}.el-select-v2__input{border:none;outline:0;padding:0;margin-left:15px;color:var(--el-select-multiple-input-color);font-size:var(--el-select-font-size);-webkit-appearance:none;-moz-appearance:none;appearance:none;height:28px}.el-select-v2__input.is-small{height:14px}.el-select-v2__close{cursor:pointer;position:absolute;top:8px;z-index:var(--el-index-top);right:25px;color:var(--el-select-input-color);line-height:18px;font-size:var(--el-select-input-font-size)}.el-select-v2__close:hover{color:var(--el-select-close-hover-color)}.el-select-v2__suffix{display:inline-flex;position:absolute;right:12px;height:32px;top:50%;transform:translateY(-50%);color:var(--el-input-icon-color,var(--el-text-color-placeholder))}.el-select-v2__suffix .el-input__icon{height:inherit}.el-select-v2__suffix .el-input__icon:not(:first-child){margin-left:8px}.el-select-v2__caret{color:var(--el-select-input-color);font-size:var(--el-select-input-font-size);transition:var(--el-transition-duration);transform:rotate(180deg);cursor:pointer}.el-select-v2__caret.is-reverse{transform:rotate(0)}.el-select-v2__caret.is-show-close{font-size:var(--el-select-font-size);text-align:center;transform:rotate(180deg);border-radius:var(--el-border-radius-circle);color:var(--el-select-input-color);transition:var(--el-transition-color)}.el-select-v2__caret.is-show-close:hover{color:var(--el-select-close-hover-color)}.el-select-v2__caret.el-icon{height:inherit}.el-select-v2__caret.el-icon svg{vertical-align:middle}.el-select-v2__selection{white-space:normal;z-index:var(--el-index-normal);display:flex;align-items:center;flex-wrap:wrap;width:100%}.el-select-v2__input-calculator{left:0;position:absolute;top:0;visibility:hidden;white-space:pre;z-index:999}.el-select-v2__selected-item{line-height:inherit;height:inherit;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;display:flex;flex-wrap:wrap}.el-select-v2__placeholder{position:absolute;top:50%;transform:translateY(-50%);-webkit-margin-start:12px;margin-inline-start:12px;width:calc(100% - 52px);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--el-input-text-color,var(--el-text-color-regular))}.el-select-v2__placeholder.is-transparent{color:var(--el-text-color-placeholder)}.el-select-v2 .el-select-v2__selection .el-tag{box-sizing:border-box;border-color:transparent;margin:2px 0 2px 6px;background-color:var(--el-fill-color)}.el-select-v2 .el-select-v2__selection .el-tag .el-icon-close{background-color:var(--el-text-color-placeholder);right:-7px;color:var(--el-color-white)}.el-select-v2 .el-select-v2__selection .el-tag .el-icon-close:hover{background-color:var(--el-text-color-secondary)}.el-select-v2 .el-select-v2__selection .el-tag .el-icon-close:before{display:block;transform:translateY(.5px)}.el-select-v2.el-select-v2--small .el-select-v2__selection .el-tag{margin:1px 0 1px 6px;height:18px}.el-select-dropdown{z-index:calc(var(--el-index-top) + 1);border-radius:var(--el-border-radius-base);box-sizing:border-box}.el-select-dropdown.is-multiple .el-select-dropdown__item.selected{color:var(--el-color-primary);background-color:var(--el-bg-color-overlay)}.el-select-dropdown.is-multiple .el-select-dropdown__item.selected.hover{background-color:var(--el-fill-color-light)}.el-select-dropdown.is-multiple .el-select-dropdown__item.selected:after{content:"";position:absolute;top:50%;right:20px;border-top:none;border-right:none;background-repeat:no-repeat;background-position:center;background-color:var(--el-color-primary);-webkit-mask:url("data:image/svg+xml;utf8,%3Csvg class='icon' width='200' height='200' viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='currentColor' d='M406.656 706.944L195.84 496.256a32 32 0 10-45.248 45.248l256 256 512-512a32 32 0 00-45.248-45.248L406.592 706.944z'%3E%3C/path%3E%3C/svg%3E") no-repeat;mask:url("data:image/svg+xml;utf8,%3Csvg class='icon' width='200' height='200' viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='currentColor' d='M406.656 706.944L195.84 496.256a32 32 0 10-45.248 45.248l256 256 512-512a32 32 0 00-45.248-45.248L406.592 706.944z'%3E%3C/path%3E%3C/svg%3E") no-repeat;mask-size:100% 100%;-webkit-mask:url("data:image/svg+xml;utf8,%3Csvg class='icon' width='200' height='200' viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='currentColor' d='M406.656 706.944L195.84 496.256a32 32 0 10-45.248 45.248l256 256 512-512a32 32 0 00-45.248-45.248L406.592 706.944z'%3E%3C/path%3E%3C/svg%3E") no-repeat;-webkit-mask-size:100% 100%;transform:translateY(-50%);width:12px;height:12px}.el-select-dropdown.is-multiple .el-select-dropdown__item.selected.is-disabled:after{background-color:var(--el-text-color-disabled)}.el-select-dropdown .el-select-dropdown__option-item.is-selected:after{content:"";position:absolute;top:50%;right:20px;border-top:none;border-right:none;background-repeat:no-repeat;background-position:center;background-color:var(--el-color-primary);-webkit-mask:url("data:image/svg+xml;utf8,%3Csvg class='icon' width='200' height='200' viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='currentColor' d='M406.656 706.944L195.84 496.256a32 32 0 10-45.248 45.248l256 256 512-512a32 32 0 00-45.248-45.248L406.592 706.944z'%3E%3C/path%3E%3C/svg%3E") no-repeat;mask:url("data:image/svg+xml;utf8,%3Csvg class='icon' width='200' height='200' viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='currentColor' d='M406.656 706.944L195.84 496.256a32 32 0 10-45.248 45.248l256 256 512-512a32 32 0 00-45.248-45.248L406.592 706.944z'%3E%3C/path%3E%3C/svg%3E") no-repeat;mask-size:100% 100%;-webkit-mask:url("data:image/svg+xml;utf8,%3Csvg class='icon' width='200' height='200' viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='currentColor' d='M406.656 706.944L195.84 496.256a32 32 0 10-45.248 45.248l256 256 512-512a32 32 0 00-45.248-45.248L406.592 706.944z'%3E%3C/path%3E%3C/svg%3E") no-repeat;-webkit-mask-size:100% 100%;transform:translateY(-50%);width:12px;height:12px}.el-select-dropdown .el-scrollbar.is-empty .el-select-dropdown__list{padding:0}.el-select-dropdown .el-select-dropdown__item.is-disabled:hover{background-color:unset}.el-select-dropdown .el-select-dropdown__item.is-disabled.selected{color:var(--el-text-color-disabled)}.el-select-dropdown__empty{padding:10px 0;margin:0;text-align:center;color:var(--el-text-color-secondary);font-size:var(--el-select-font-size)}.el-select-dropdown__wrap{max-height:274px}.el-select-dropdown__list{list-style:none;padding:6px 0;margin:0;box-sizing:border-box}.el-select-dropdown__header{padding:10px;border-bottom:1px solid var(--el-border-color-light)}.el-select-dropdown__footer{padding:10px;border-top:1px solid var(--el-border-color-light)}.el-select{--el-select-border-color-hover:var(--el-border-color-hover);--el-select-disabled-border:var(--el-disabled-border-color);--el-select-font-size:var(--el-font-size-base);--el-select-close-hover-color:var(--el-text-color-secondary);--el-select-input-color:var(--el-text-color-placeholder);--el-select-multiple-input-color:var(--el-text-color-regular);--el-select-input-focus-border-color:var(--el-color-primary);--el-select-input-font-size:14px}.el-select{display:inline-block;position:relative;vertical-align:middle;line-height:32px}.el-select__popper.el-popper{background:var(--el-bg-color-overlay);border:1px solid var(--el-border-color-light);box-shadow:var(--el-box-shadow-light)}.el-select__popper.el-popper .el-popper__arrow:before{border:1px solid var(--el-border-color-light)}.el-select__popper.el-popper[data-popper-placement^=top] .el-popper__arrow:before{border-top-color:transparent;border-left-color:transparent}.el-select__popper.el-popper[data-popper-placement^=bottom] .el-popper__arrow:before{border-bottom-color:transparent;border-right-color:transparent}.el-select__popper.el-popper[data-popper-placement^=left] .el-popper__arrow:before{border-left-color:transparent;border-bottom-color:transparent}.el-select__popper.el-popper[data-popper-placement^=right] .el-popper__arrow:before{border-right-color:transparent;border-top-color:transparent}.el-select .el-select-tags-wrapper.has-prefix{margin-left:6px}.el-select--large{line-height:40px}.el-select--large .el-select-tags-wrapper.has-prefix{margin-left:8px}.el-select--small{line-height:24px}.el-select--small .el-select-tags-wrapper.has-prefix{margin-left:4px}.el-select .el-select__tags>span{display:inline-block}.el-select:hover:not(.el-select--disabled) .el-input__wrapper{box-shadow:0 0 0 1px var(--el-select-border-color-hover) inset}.el-select .el-select__tags-text{display:inline-block;line-height:normal;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.el-select .el-input__wrapper{cursor:pointer}.el-select .el-input__wrapper.is-focus{box-shadow:0 0 0 1px var(--el-select-input-focus-border-color) inset!important}.el-select .el-input__inner{cursor:pointer}.el-select .el-input{display:flex}.el-select .el-input .el-select__caret{color:var(--el-select-input-color);font-size:var(--el-select-input-font-size);transition:transform var(--el-transition-duration);transform:rotate(0);cursor:pointer}.el-select .el-input .el-select__caret.is-reverse{transform:rotate(-180deg)}.el-select .el-input .el-select__caret.is-show-close{font-size:var(--el-select-font-size);text-align:center;transform:rotate(0);border-radius:var(--el-border-radius-circle);color:var(--el-select-input-color);transition:var(--el-transition-color)}.el-select .el-input .el-select__caret.is-show-close:hover{color:var(--el-select-close-hover-color)}.el-select .el-input .el-select__caret.el-icon{position:relative;height:inherit;z-index:2}.el-select .el-input.is-disabled .el-input__wrapper{cursor:not-allowed}.el-select .el-input.is-disabled .el-input__wrapper:hover{box-shadow:0 0 0 1px var(--el-select-disabled-border) inset}.el-select .el-input.is-disabled .el-input__inner,.el-select .el-input.is-disabled .el-select__caret{cursor:not-allowed}.el-select .el-input.is-focus .el-input__wrapper{box-shadow:0 0 0 1px var(--el-select-input-focus-border-color) inset!important}.el-select__input{border:none;outline:0;padding:0;margin-left:15px;color:var(--el-select-multiple-input-color);font-size:var(--el-select-font-size);-webkit-appearance:none;-moz-appearance:none;appearance:none;height:28px;background-color:transparent}.el-select__input.is-disabled{cursor:not-allowed}.el-select__input--iOS{position:absolute;left:0;top:0;z-index:6}.el-select__input.is-small{height:14px}.el-select__close{cursor:pointer;position:absolute;top:8px;z-index:var(--el-index-top);right:25px;color:var(--el-select-input-color);line-height:18px;font-size:var(--el-select-input-font-size)}.el-select__close:hover{color:var(--el-select-close-hover-color)}.el-select__tags{position:absolute;line-height:normal;top:50%;transform:translateY(-50%);white-space:normal;z-index:var(--el-index-normal);display:flex;align-items:center;flex-wrap:wrap;cursor:pointer}.el-select__tags .el-tag{box-sizing:border-box;border-color:transparent;margin:2px 6px 2px 0}.el-select__tags .el-tag:last-child{margin-right:0}.el-select__tags .el-tag .el-icon-close{background-color:var(--el-text-color-placeholder);right:-7px;top:0;color:#fff}.el-select__tags .el-tag .el-icon-close:hover{background-color:var(--el-text-color-secondary)}.el-select__tags .el-tag .el-icon-close:before{display:block;transform:translateY(.5px)}.el-select__tags .el-tag--info{background-color:var(--el-fill-color)}.el-select__tags.is-disabled{cursor:not-allowed}.el-select__collapse-tags{white-space:normal;z-index:var(--el-index-normal);display:flex;align-items:center;flex-wrap:wrap;cursor:pointer}.el-select__collapse-tags .el-tag{box-sizing:border-box;border-color:transparent;margin:2px 6px 2px 0}.el-select__collapse-tags .el-tag:last-child{margin-right:0}.el-select__collapse-tags .el-tag .el-icon-close{background-color:var(--el-text-color-placeholder);right:-7px;top:0;color:#fff}.el-select__collapse-tags .el-tag .el-icon-close:hover{background-color:var(--el-text-color-secondary)}.el-select__collapse-tags .el-tag .el-icon-close:before{display:block;transform:translateY(.5px)}.el-select__collapse-tags .el-tag--info{background-color:var(--el-fill-color)}.el-select__collapse-tag{line-height:inherit;height:inherit;display:flex}.el-skeleton{--el-skeleton-circle-size:var(--el-avatar-size)}.el-skeleton__item{background:var(--el-skeleton-color);display:inline-block;height:16px;border-radius:var(--el-border-radius-base);width:100%}.el-skeleton__circle{border-radius:50%;width:var(--el-skeleton-circle-size);height:var(--el-skeleton-circle-size);line-height:var(--el-skeleton-circle-size)}.el-skeleton__button{height:40px;width:64px;border-radius:4px}.el-skeleton__p{width:100%}.el-skeleton__p.is-last{width:61%}.el-skeleton__p.is-first{width:33%}.el-skeleton__text{width:100%;height:var(--el-font-size-small)}.el-skeleton__caption{height:var(--el-font-size-extra-small)}.el-skeleton__h1{height:var(--el-font-size-extra-large)}.el-skeleton__h3{height:var(--el-font-size-large)}.el-skeleton__h5{height:var(--el-font-size-medium)}.el-skeleton__image{width:unset;display:flex;align-items:center;justify-content:center;border-radius:0}.el-skeleton__image svg{color:var(--el-svg-monochrome-grey);fill:currentColor;width:22%;height:22%}.el-skeleton{--el-skeleton-color:var(--el-fill-color);--el-skeleton-to-color:var(--el-fill-color-darker)}@-webkit-keyframes el-skeleton-loading{0%{background-position:100% 50%}to{background-position:0 50%}}@keyframes el-skeleton-loading{0%{background-position:100% 50%}to{background-position:0 50%}}.el-skeleton{width:100%}.el-skeleton__first-line,.el-skeleton__paragraph{height:16px;margin-top:16px;background:var(--el-skeleton-color)}.el-skeleton.is-animated .el-skeleton__item{background:linear-gradient(90deg,var(--el-skeleton-color) 25%,var(--el-skeleton-to-color) 37%,var(--el-skeleton-color) 63%);background-size:400% 100%;-webkit-animation:el-skeleton-loading 1.4s ease infinite;animation:el-skeleton-loading 1.4s ease infinite}.el-slider{--el-slider-main-bg-color:var(--el-color-primary);--el-slider-runway-bg-color:var(--el-border-color-light);--el-slider-stop-bg-color:var(--el-color-white);--el-slider-disabled-color:var(--el-text-color-placeholder);--el-slider-border-radius:3px;--el-slider-height:6px;--el-slider-button-size:20px;--el-slider-button-wrapper-size:36px;--el-slider-button-wrapper-offset:-15px}.el-slider{width:100%;height:32px;display:flex;align-items:center}.el-slider__runway{flex:1;height:var(--el-slider-height);background-color:var(--el-slider-runway-bg-color);border-radius:var(--el-slider-border-radius);position:relative;cursor:pointer}.el-slider__runway.show-input{margin-right:30px;width:auto}.el-slider__runway.is-disabled{cursor:default}.el-slider__runway.is-disabled .el-slider__bar{background-color:var(--el-slider-disabled-color)}.el-slider__runway.is-disabled .el-slider__button{border-color:var(--el-slider-disabled-color)}.el-slider__runway.is-disabled .el-slider__button-wrapper.hover,.el-slider__runway.is-disabled .el-slider__button-wrapper:hover,.el-slider__runway.is-disabled .el-slider__button-wrapper.dragging{cursor:not-allowed}.el-slider__runway.is-disabled .el-slider__button.dragging,.el-slider__runway.is-disabled .el-slider__button.hover,.el-slider__runway.is-disabled .el-slider__button:hover{transform:scale(1)}.el-slider__runway.is-disabled .el-slider__button.hover,.el-slider__runway.is-disabled .el-slider__button:hover,.el-slider__runway.is-disabled .el-slider__button.dragging{cursor:not-allowed}.el-slider__input{flex-shrink:0;width:130px}.el-slider__bar{height:var(--el-slider-height);background-color:var(--el-slider-main-bg-color);border-top-left-radius:var(--el-slider-border-radius);border-bottom-left-radius:var(--el-slider-border-radius);position:absolute}.el-slider__button-wrapper{height:var(--el-slider-button-wrapper-size);width:var(--el-slider-button-wrapper-size);position:absolute;z-index:1;top:var(--el-slider-button-wrapper-offset);transform:translate(-50%);background-color:transparent;text-align:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;line-height:normal;outline:0}.el-slider__button-wrapper:after{display:inline-block;content:"";height:100%;vertical-align:middle}.el-slider__button-wrapper.hover,.el-slider__button-wrapper:hover{cursor:-webkit-grab;cursor:grab}.el-slider__button-wrapper.dragging{cursor:-webkit-grabbing;cursor:grabbing}.el-slider__button{display:inline-block;width:var(--el-slider-button-size);height:var(--el-slider-button-size);vertical-align:middle;border:solid 2px var(--el-slider-main-bg-color);background-color:var(--el-color-white);border-radius:50%;box-sizing:border-box;transition:var(--el-transition-duration-fast);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.el-slider__button.dragging,.el-slider__button.hover,.el-slider__button:hover{transform:scale(1.2)}.el-slider__button.hover,.el-slider__button:hover{cursor:-webkit-grab;cursor:grab}.el-slider__button.dragging{cursor:-webkit-grabbing;cursor:grabbing}.el-slider__stop{position:absolute;height:var(--el-slider-height);width:var(--el-slider-height);border-radius:var(--el-border-radius-circle);background-color:var(--el-slider-stop-bg-color);transform:translate(-50%)}.el-slider__marks{top:0;left:12px;width:18px;height:100%}.el-slider__marks-text{position:absolute;transform:translate(-50%);font-size:14px;color:var(--el-color-info);margin-top:15px;white-space:pre}.el-slider.is-vertical{position:relative;display:inline-flex;width:auto;height:100%;flex:0}.el-slider.is-vertical .el-slider__runway{width:var(--el-slider-height);height:100%;margin:0 16px}.el-slider.is-vertical .el-slider__bar{width:var(--el-slider-height);height:auto;border-radius:0 0 3px 3px}.el-slider.is-vertical .el-slider__button-wrapper{top:auto;left:var(--el-slider-button-wrapper-offset);transform:translateY(50%)}.el-slider.is-vertical .el-slider__stop{transform:translateY(50%)}.el-slider.is-vertical .el-slider__marks-text{margin-top:0;left:15px;transform:translateY(50%)}.el-slider--large{height:40px}.el-slider--small{height:24px}.el-space{display:inline-flex;vertical-align:top}.el-space__item{display:flex;flex-wrap:wrap}.el-space__item>*{flex:1}.el-space--vertical{flex-direction:column}.el-time-spinner{width:100%;white-space:nowrap}.el-spinner{display:inline-block;vertical-align:middle}.el-spinner-inner{-webkit-animation:rotate 2s linear infinite;animation:rotate 2s linear infinite;width:50px;height:50px}.el-spinner-inner .path{stroke:var(--el-border-color-lighter);stroke-linecap:round;-webkit-animation:dash 1.5s ease-in-out infinite;animation:dash 1.5s ease-in-out infinite}@-webkit-keyframes rotate{to{transform:rotate(360deg)}}@keyframes rotate{to{transform:rotate(360deg)}}@-webkit-keyframes dash{0%{stroke-dasharray:1,150;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-35}to{stroke-dasharray:90,150;stroke-dashoffset:-124}}@keyframes dash{0%{stroke-dasharray:1,150;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-35}to{stroke-dasharray:90,150;stroke-dashoffset:-124}}.el-step{position:relative;flex-shrink:1}.el-step:last-of-type .el-step__line{display:none}.el-step:last-of-type.is-flex{flex-basis:auto!important;flex-shrink:0;flex-grow:0}.el-step:last-of-type .el-step__description,.el-step:last-of-type .el-step__main{padding-right:0}.el-step__head{position:relative;width:100%}.el-step__head.is-process{color:var(--el-text-color-primary);border-color:var(--el-text-color-primary)}.el-step__head.is-wait{color:var(--el-text-color-placeholder);border-color:var(--el-text-color-placeholder)}.el-step__head.is-success{color:var(--el-color-success);border-color:var(--el-color-success)}.el-step__head.is-error{color:var(--el-color-danger);border-color:var(--el-color-danger)}.el-step__head.is-finish{color:var(--el-color-primary);border-color:var(--el-color-primary)}.el-step__icon{position:relative;z-index:1;display:inline-flex;justify-content:center;align-items:center;width:24px;height:24px;font-size:14px;box-sizing:border-box;background:var(--el-bg-color);transition:.15s ease-out}.el-step__icon.is-text{border-radius:50%;border:2px solid;border-color:inherit}.el-step__icon.is-icon{width:40px}.el-step__icon-inner{display:inline-block;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;text-align:center;font-weight:700;line-height:1;color:inherit}.el-step__icon-inner[class*=el-icon]:not(.is-status){font-size:25px;font-weight:400}.el-step__icon-inner.is-status{transform:translateY(1px)}.el-step__line{position:absolute;border-color:inherit;background-color:var(--el-text-color-placeholder)}.el-step__line-inner{display:block;border-width:1px;border-style:solid;border-color:inherit;transition:.15s ease-out;box-sizing:border-box;width:0;height:0}.el-step__main{white-space:normal;text-align:left}.el-step__title{font-size:16px;line-height:38px}.el-step__title.is-process{font-weight:700;color:var(--el-text-color-primary)}.el-step__title.is-wait{color:var(--el-text-color-placeholder)}.el-step__title.is-success{color:var(--el-color-success)}.el-step__title.is-error{color:var(--el-color-danger)}.el-step__title.is-finish{color:var(--el-color-primary)}.el-step__description{padding-right:10%;margin-top:-5px;font-size:12px;line-height:20px;font-weight:400}.el-step__description.is-process{color:var(--el-text-color-primary)}.el-step__description.is-wait{color:var(--el-text-color-placeholder)}.el-step__description.is-success{color:var(--el-color-success)}.el-step__description.is-error{color:var(--el-color-danger)}.el-step__description.is-finish{color:var(--el-color-primary)}.el-step.is-horizontal{display:inline-block}.el-step.is-horizontal .el-step__line{height:2px;top:11px;left:0;right:0}.el-step.is-vertical{display:flex}.el-step.is-vertical .el-step__head{flex-grow:0;width:24px}.el-step.is-vertical .el-step__main{padding-left:10px;flex-grow:1}.el-step.is-vertical .el-step__title{line-height:24px;padding-bottom:8px}.el-step.is-vertical .el-step__line{width:2px;top:0;bottom:0;left:11px}.el-step.is-vertical .el-step__icon.is-icon{width:24px}.el-step.is-center .el-step__head,.el-step.is-center .el-step__main{text-align:center}.el-step.is-center .el-step__description{padding-left:20%;padding-right:20%}.el-step.is-center .el-step__line{left:50%;right:-50%}.el-step.is-simple{display:flex;align-items:center}.el-step.is-simple .el-step__head{width:auto;font-size:0;padding-right:10px}.el-step.is-simple .el-step__icon{background:0 0;width:16px;height:16px;font-size:12px}.el-step.is-simple .el-step__icon-inner[class*=el-icon]:not(.is-status){font-size:18px}.el-step.is-simple .el-step__icon-inner.is-status{transform:scale(.8) translateY(1px)}.el-step.is-simple .el-step__main{position:relative;display:flex;align-items:stretch;flex-grow:1}.el-step.is-simple .el-step__title{font-size:16px;line-height:20px}.el-step.is-simple:not(:last-of-type) .el-step__title{max-width:50%;word-break:break-all}.el-step.is-simple .el-step__arrow{flex-grow:1;display:flex;align-items:center;justify-content:center}.el-step.is-simple .el-step__arrow:after,.el-step.is-simple .el-step__arrow:before{content:"";display:inline-block;position:absolute;height:15px;width:1px;background:var(--el-text-color-placeholder)}.el-step.is-simple .el-step__arrow:before{transform:rotate(-45deg) translateY(-4px);transform-origin:0 0}.el-step.is-simple .el-step__arrow:after{transform:rotate(45deg) translateY(4px);transform-origin:100% 100%}.el-step.is-simple:last-of-type .el-step__arrow{display:none}.el-steps{display:flex}.el-steps--simple{padding:13px 8%;border-radius:4px;background:var(--el-fill-color-light)}.el-steps--horizontal{white-space:nowrap}.el-steps--vertical{height:100%;flex-flow:column}.el-switch{--el-switch-on-color:var(--el-color-primary);--el-switch-off-color:var(--el-border-color)}.el-switch{display:inline-flex;align-items:center;position:relative;font-size:14px;line-height:20px;height:32px;vertical-align:middle}.el-switch.is-disabled .el-switch__core,.el-switch.is-disabled .el-switch__label{cursor:not-allowed}.el-switch__label{transition:var(--el-transition-duration-fast);height:20px;display:inline-block;font-size:14px;font-weight:500;cursor:pointer;vertical-align:middle;color:var(--el-text-color-primary)}.el-switch__label.is-active{color:var(--el-color-primary)}.el-switch__label--left{margin-right:10px}.el-switch__label--right{margin-left:10px}.el-switch__label *{line-height:1;font-size:14px;display:inline-block}.el-switch__label .el-icon{height:inherit}.el-switch__label .el-icon svg{vertical-align:middle}.el-switch__input{position:absolute;width:0;height:0;opacity:0;margin:0}.el-switch__input:focus-visible~.el-switch__core{outline:2px solid var(--el-switch-on-color);outline-offset:1px}.el-switch__core{display:inline-flex;position:relative;align-items:center;min-width:40px;height:20px;border:1px solid var(--el-switch-border-color,var(--el-switch-off-color));outline:0;border-radius:10px;box-sizing:border-box;background:var(--el-switch-off-color);cursor:pointer;transition:border-color var(--el-transition-duration),background-color var(--el-transition-duration)}.el-switch__core .el-switch__inner{width:100%;transition:all var(--el-transition-duration);height:16px;display:flex;justify-content:center;align-items:center;overflow:hidden;padding:0 4px 0 18px}.el-switch__core .el-switch__inner .is-icon,.el-switch__core .el-switch__inner .is-text{font-size:12px;color:var(--el-color-white);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.el-switch__core .el-switch__action{position:absolute;left:1px;border-radius:var(--el-border-radius-circle);transition:all var(--el-transition-duration);width:16px;height:16px;background-color:var(--el-color-white);display:flex;justify-content:center;align-items:center;color:var(--el-switch-off-color)}.el-switch.is-checked .el-switch__core{border-color:var(--el-switch-border-color,var(--el-switch-on-color));background-color:var(--el-switch-on-color)}.el-switch.is-checked .el-switch__core .el-switch__action{left:calc(100% - 17px);color:var(--el-switch-on-color)}.el-switch.is-checked .el-switch__core .el-switch__inner{padding:0 18px 0 4px}.el-switch.is-disabled{opacity:.6}.el-switch--wide .el-switch__label.el-switch__label--left span{left:10px}.el-switch--wide .el-switch__label.el-switch__label--right span{right:10px}.el-switch .label-fade-enter-from,.el-switch .label-fade-leave-active{opacity:0}.el-switch--large{font-size:14px;line-height:24px;height:40px}.el-switch--large .el-switch__label{height:24px;font-size:14px}.el-switch--large .el-switch__label *{font-size:14px}.el-switch--large .el-switch__core{min-width:50px;height:24px;border-radius:12px}.el-switch--large .el-switch__core .el-switch__inner{height:20px;padding:0 6px 0 22px}.el-switch--large .el-switch__core .el-switch__action{width:20px;height:20px}.el-switch--large.is-checked .el-switch__core .el-switch__action{left:calc(100% - 21px)}.el-switch--large.is-checked .el-switch__core .el-switch__inner{padding:0 22px 0 6px}.el-switch--small{font-size:12px;line-height:16px;height:24px}.el-switch--small .el-switch__label{height:16px;font-size:12px}.el-switch--small .el-switch__label *{font-size:12px}.el-switch--small .el-switch__core{min-width:30px;height:16px;border-radius:8px}.el-switch--small .el-switch__core .el-switch__inner{height:12px;padding:0 2px 0 14px}.el-switch--small .el-switch__core .el-switch__action{width:12px;height:12px}.el-switch--small.is-checked .el-switch__core .el-switch__action{left:calc(100% - 13px)}.el-switch--small.is-checked .el-switch__core .el-switch__inner{padding:0 14px 0 2px}.el-table-column--selection .cell{padding-left:14px;padding-right:14px}.el-table-filter{border:solid 1px var(--el-border-color-lighter);border-radius:2px;background-color:#fff;box-shadow:var(--el-box-shadow-light);box-sizing:border-box}.el-table-filter__list{padding:5px 0;margin:0;list-style:none;min-width:100px}.el-table-filter__list-item{line-height:36px;padding:0 10px;cursor:pointer;font-size:var(--el-font-size-base)}.el-table-filter__list-item:hover{background-color:var(--el-color-primary-light-9);color:var(--el-color-primary)}.el-table-filter__list-item.is-active{background-color:var(--el-color-primary);color:#fff}.el-table-filter__content{min-width:100px}.el-table-filter__bottom{border-top:1px solid var(--el-border-color-lighter);padding:8px}.el-table-filter__bottom button{background:0 0;border:none;color:var(--el-text-color-regular);cursor:pointer;font-size:var(--el-font-size-small);padding:0 3px}.el-table-filter__bottom button:hover{color:var(--el-color-primary)}.el-table-filter__bottom button:focus{outline:0}.el-table-filter__bottom button.is-disabled{color:var(--el-disabled-text-color);cursor:not-allowed}.el-table-filter__wrap{max-height:280px}.el-table-filter__checkbox-group{padding:10px}.el-table-filter__checkbox-group label.el-checkbox{display:flex;align-items:center;margin-right:5px;margin-bottom:12px;margin-left:5px;height:unset}.el-table-filter__checkbox-group .el-checkbox:last-child{margin-bottom:0}.el-table{--el-table-border-color:var(--el-border-color-lighter);--el-table-border:1px solid var(--el-table-border-color);--el-table-text-color:var(--el-text-color-regular);--el-table-header-text-color:var(--el-text-color-secondary);--el-table-row-hover-bg-color:var(--el-fill-color-light);--el-table-current-row-bg-color:var(--el-color-primary-light-9);--el-table-header-bg-color:var(--el-bg-color);--el-table-fixed-box-shadow:var(--el-box-shadow-light);--el-table-bg-color:var(--el-fill-color-blank);--el-table-tr-bg-color:var(--el-bg-color);--el-table-expanded-cell-bg-color:var(--el-fill-color-blank);--el-table-fixed-left-column:inset 10px 0 10px -10px rgba(0, 0, 0, .15);--el-table-fixed-right-column:inset -10px 0 10px -10px rgba(0, 0, 0, .15);--el-table-index:var(--el-index-normal)}.el-table{position:relative;overflow:hidden;box-sizing:border-box;height:-webkit-fit-content;height:-moz-fit-content;height:fit-content;width:100%;max-width:100%;background-color:var(--el-table-bg-color);font-size:14px;color:var(--el-table-text-color)}.el-table__inner-wrapper{position:relative;display:flex;flex-direction:column;height:100%}.el-table__inner-wrapper:before{left:0;bottom:0;width:100%;height:1px}.el-table tbody:focus-visible{outline:0}.el-table.has-footer.el-table--fluid-height tr:last-child td.el-table__cell,.el-table.has-footer.el-table--scrollable-y tr:last-child td.el-table__cell{border-bottom-color:transparent}.el-table__empty-block{position:-webkit-sticky;position:sticky;left:0;min-height:60px;text-align:center;width:100%;display:flex;justify-content:center;align-items:center}.el-table__empty-text{line-height:60px;width:50%;color:var(--el-text-color-secondary)}.el-table__expand-column .cell{padding:0;text-align:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.el-table__expand-icon{position:relative;cursor:pointer;color:var(--el-text-color-regular);font-size:12px;transition:transform var(--el-transition-duration-fast) ease-in-out;height:20px}.el-table__expand-icon--expanded{transform:rotate(90deg)}.el-table__expand-icon>.el-icon{font-size:12px}.el-table__expanded-cell{background-color:var(--el-table-expanded-cell-bg-color)}.el-table__expanded-cell[class*=cell]{padding:20px 50px}.el-table__expanded-cell:hover{background-color:transparent!important}.el-table__placeholder{display:inline-block;width:20px}.el-table__append-wrapper{overflow:hidden}.el-table--fit{border-right:0;border-bottom:0}.el-table--fit .el-table__cell.gutter{border-right-width:1px}.el-table thead{color:var(--el-table-header-text-color)}.el-table thead th{font-weight:600}.el-table thead.is-group th.el-table__cell{background:var(--el-fill-color-light)}.el-table tfoot td.el-table__cell{background-color:var(--el-table-row-hover-bg-color);color:var(--el-table-text-color)}.el-table .el-table__cell{padding:8px 0;min-width:0;box-sizing:border-box;text-overflow:ellipsis;vertical-align:middle;position:relative;text-align:left;z-index:var(--el-table-index)}.el-table .el-table__cell.is-center{text-align:center}.el-table .el-table__cell.is-right{text-align:right}.el-table .el-table__cell.gutter{width:15px;border-right-width:0;border-bottom-width:0;padding:0}.el-table .el-table__cell.is-hidden>*{visibility:hidden}.el-table .cell{box-sizing:border-box;overflow:hidden;text-overflow:ellipsis;white-space:normal;word-break:break-all;line-height:23px;padding:0 12px}.el-table .cell.el-tooltip{white-space:nowrap;min-width:50px}.el-table--large{font-size:var(--el-font-size-base)}.el-table--large .el-table__cell{padding:12px 0}.el-table--large .cell{padding:0 16px}.el-table--default{font-size:14px}.el-table--default .el-table__cell{padding:8px 0}.el-table--default .cell{padding:0 12px}.el-table--small{font-size:12px}.el-table--small .el-table__cell{padding:4px 0}.el-table--small .cell{padding:0 8px}.el-table tr{background-color:var(--el-table-tr-bg-color)}.el-table tr input[type=checkbox]{margin:0}.el-table td.el-table__cell,.el-table th.el-table__cell.is-leaf{border-bottom:var(--el-table-border)}.el-table th.el-table__cell.is-sortable{cursor:pointer}.el-table th.el-table__cell{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:var(--el-table-header-bg-color)}.el-table th.el-table__cell>.cell.highlight{color:var(--el-color-primary)}.el-table th.el-table__cell.required>div:before{display:inline-block;content:"";width:8px;height:8px;border-radius:50%;background:#ff4d51;margin-right:5px;vertical-align:middle}.el-table td.el-table__cell div{box-sizing:border-box}.el-table td.el-table__cell.gutter{width:0}.el-table--border .el-table__inner-wrapper:after,.el-table--border:after,.el-table--border:before,.el-table__inner-wrapper:before{content:"";position:absolute;background-color:var(--el-table-border-color);z-index:calc(var(--el-table-index) + 2)}.el-table--border .el-table__inner-wrapper:after{left:0;top:0;width:100%;height:1px;z-index:calc(var(--el-table-index) + 2)}.el-table--border:before{top:-1px;left:0;width:1px;height:100%}.el-table--border:after{top:-1px;right:0;width:1px;height:100%}.el-table--border .el-table__inner-wrapper{border-right:none;border-bottom:none}.el-table--border .el-table__footer-wrapper{position:relative;flex-shrink:0}.el-table--border .el-table__cell{border-right:var(--el-table-border)}.el-table--border th.el-table__cell.gutter:last-of-type{border-bottom:var(--el-table-border);border-bottom-width:1px}.el-table--border th.el-table__cell{border-bottom:var(--el-table-border)}.el-table--hidden{visibility:hidden}.el-table__body-wrapper,.el-table__footer-wrapper,.el-table__header-wrapper{width:100%}.el-table__body-wrapper tr td.el-table-fixed-column--left,.el-table__body-wrapper tr td.el-table-fixed-column--right,.el-table__body-wrapper tr th.el-table-fixed-column--left,.el-table__body-wrapper tr th.el-table-fixed-column--right,.el-table__footer-wrapper tr td.el-table-fixed-column--left,.el-table__footer-wrapper tr td.el-table-fixed-column--right,.el-table__footer-wrapper tr th.el-table-fixed-column--left,.el-table__footer-wrapper tr th.el-table-fixed-column--right,.el-table__header-wrapper tr td.el-table-fixed-column--left,.el-table__header-wrapper tr td.el-table-fixed-column--right,.el-table__header-wrapper tr th.el-table-fixed-column--left,.el-table__header-wrapper tr th.el-table-fixed-column--right{position:-webkit-sticky!important;position:sticky!important;background:inherit;z-index:calc(var(--el-table-index) + 1)}.el-table__body-wrapper tr td.el-table-fixed-column--left.is-first-column:before,.el-table__body-wrapper tr td.el-table-fixed-column--left.is-last-column:before,.el-table__body-wrapper tr td.el-table-fixed-column--right.is-first-column:before,.el-table__body-wrapper tr td.el-table-fixed-column--right.is-last-column:before,.el-table__body-wrapper tr th.el-table-fixed-column--left.is-first-column:before,.el-table__body-wrapper tr th.el-table-fixed-column--left.is-last-column:before,.el-table__body-wrapper tr th.el-table-fixed-column--right.is-first-column:before,.el-table__body-wrapper tr th.el-table-fixed-column--right.is-last-column:before,.el-table__footer-wrapper tr td.el-table-fixed-column--left.is-first-column:before,.el-table__footer-wrapper tr td.el-table-fixed-column--left.is-last-column:before,.el-table__footer-wrapper tr td.el-table-fixed-column--right.is-first-column:before,.el-table__footer-wrapper tr td.el-table-fixed-column--right.is-last-column:before,.el-table__footer-wrapper tr th.el-table-fixed-column--left.is-first-column:before,.el-table__footer-wrapper tr th.el-table-fixed-column--left.is-last-column:before,.el-table__footer-wrapper tr th.el-table-fixed-column--right.is-first-column:before,.el-table__footer-wrapper tr th.el-table-fixed-column--right.is-last-column:before,.el-table__header-wrapper tr td.el-table-fixed-column--left.is-first-column:before,.el-table__header-wrapper tr td.el-table-fixed-column--left.is-last-column:before,.el-table__header-wrapper tr td.el-table-fixed-column--right.is-first-column:before,.el-table__header-wrapper tr td.el-table-fixed-column--right.is-last-column:before,.el-table__header-wrapper tr th.el-table-fixed-column--left.is-first-column:before,.el-table__header-wrapper tr th.el-table-fixed-column--left.is-last-column:before,.el-table__header-wrapper tr th.el-table-fixed-column--right.is-first-column:before,.el-table__header-wrapper tr th.el-table-fixed-column--right.is-last-column:before{content:"";position:absolute;top:0;width:10px;bottom:-1px;overflow-x:hidden;overflow-y:hidden;box-shadow:none;touch-action:none;pointer-events:none}.el-table__body-wrapper tr td.el-table-fixed-column--left.is-first-column:before,.el-table__body-wrapper tr td.el-table-fixed-column--right.is-first-column:before,.el-table__body-wrapper tr th.el-table-fixed-column--left.is-first-column:before,.el-table__body-wrapper tr th.el-table-fixed-column--right.is-first-column:before,.el-table__footer-wrapper tr td.el-table-fixed-column--left.is-first-column:before,.el-table__footer-wrapper tr td.el-table-fixed-column--right.is-first-column:before,.el-table__footer-wrapper tr th.el-table-fixed-column--left.is-first-column:before,.el-table__footer-wrapper tr th.el-table-fixed-column--right.is-first-column:before,.el-table__header-wrapper tr td.el-table-fixed-column--left.is-first-column:before,.el-table__header-wrapper tr td.el-table-fixed-column--right.is-first-column:before,.el-table__header-wrapper tr th.el-table-fixed-column--left.is-first-column:before,.el-table__header-wrapper tr th.el-table-fixed-column--right.is-first-column:before{left:-10px}.el-table__body-wrapper tr td.el-table-fixed-column--left.is-last-column:before,.el-table__body-wrapper tr td.el-table-fixed-column--right.is-last-column:before,.el-table__body-wrapper tr th.el-table-fixed-column--left.is-last-column:before,.el-table__body-wrapper tr th.el-table-fixed-column--right.is-last-column:before,.el-table__footer-wrapper tr td.el-table-fixed-column--left.is-last-column:before,.el-table__footer-wrapper tr td.el-table-fixed-column--right.is-last-column:before,.el-table__footer-wrapper tr th.el-table-fixed-column--left.is-last-column:before,.el-table__footer-wrapper tr th.el-table-fixed-column--right.is-last-column:before,.el-table__header-wrapper tr td.el-table-fixed-column--left.is-last-column:before,.el-table__header-wrapper tr td.el-table-fixed-column--right.is-last-column:before,.el-table__header-wrapper tr th.el-table-fixed-column--left.is-last-column:before,.el-table__header-wrapper tr th.el-table-fixed-column--right.is-last-column:before{right:-10px;box-shadow:none}.el-table__body-wrapper tr td.el-table__fixed-right-patch,.el-table__body-wrapper tr th.el-table__fixed-right-patch,.el-table__footer-wrapper tr td.el-table__fixed-right-patch,.el-table__footer-wrapper tr th.el-table__fixed-right-patch,.el-table__header-wrapper tr td.el-table__fixed-right-patch,.el-table__header-wrapper tr th.el-table__fixed-right-patch{position:-webkit-sticky!important;position:sticky!important;z-index:calc(var(--el-table-index) + 1);background:#fff;right:0}.el-table__header-wrapper{flex-shrink:0}.el-table__header-wrapper tr th.el-table-fixed-column--left,.el-table__header-wrapper tr th.el-table-fixed-column--right{background-color:var(--el-table-header-bg-color)}.el-table__body,.el-table__footer,.el-table__header{table-layout:fixed;border-collapse:separate}.el-table__header-wrapper{overflow:hidden}.el-table__header-wrapper tbody td.el-table__cell{background-color:var(--el-table-row-hover-bg-color);color:var(--el-table-text-color)}.el-table__footer-wrapper{overflow:hidden;flex-shrink:0}.el-table__body-wrapper .el-table-column--selection>.cell,.el-table__header-wrapper .el-table-column--selection>.cell{display:inline-flex;align-items:center;height:23px}.el-table__body-wrapper .el-table-column--selection .el-checkbox,.el-table__header-wrapper .el-table-column--selection .el-checkbox{height:unset}.el-table.is-scrolling-left .el-table-fixed-column--right.is-first-column:before{box-shadow:var(--el-table-fixed-right-column)}.el-table.is-scrolling-left.el-table--border .el-table-fixed-column--left.is-last-column.el-table__cell{border-right:var(--el-table-border)}.el-table.is-scrolling-left th.el-table-fixed-column--left{background-color:var(--el-table-header-bg-color)}.el-table.is-scrolling-right .el-table-fixed-column--left.is-last-column:before{box-shadow:var(--el-table-fixed-left-column)}.el-table.is-scrolling-right .el-table-fixed-column--left.is-last-column.el-table__cell{border-right:none}.el-table.is-scrolling-right th.el-table-fixed-column--right{background-color:var(--el-table-header-bg-color)}.el-table.is-scrolling-middle .el-table-fixed-column--left.is-last-column.el-table__cell{border-right:none}.el-table.is-scrolling-middle .el-table-fixed-column--right.is-first-column:before{box-shadow:var(--el-table-fixed-right-column)}.el-table.is-scrolling-middle .el-table-fixed-column--left.is-last-column:before{box-shadow:var(--el-table-fixed-left-column)}.el-table.is-scrolling-none .el-table-fixed-column--left.is-first-column:before,.el-table.is-scrolling-none .el-table-fixed-column--left.is-last-column:before,.el-table.is-scrolling-none .el-table-fixed-column--right.is-first-column:before,.el-table.is-scrolling-none .el-table-fixed-column--right.is-last-column:before{box-shadow:none}.el-table.is-scrolling-none th.el-table-fixed-column--left,.el-table.is-scrolling-none th.el-table-fixed-column--right{background-color:var(--el-table-header-bg-color)}.el-table__body-wrapper{overflow:hidden;position:relative;flex:1}.el-table__body-wrapper .el-scrollbar__bar{z-index:calc(var(--el-table-index) + 2)}.el-table .caret-wrapper{display:inline-flex;flex-direction:column;align-items:center;height:14px;width:24px;vertical-align:middle;cursor:pointer;overflow:initial;position:relative}.el-table .sort-caret{width:0;height:0;border:solid 5px transparent;position:absolute;left:7px}.el-table .sort-caret.ascending{border-bottom-color:var(--el-text-color-placeholder);top:-5px}.el-table .sort-caret.descending{border-top-color:var(--el-text-color-placeholder);bottom:-3px}.el-table .ascending .sort-caret.ascending{border-bottom-color:var(--el-color-primary)}.el-table .descending .sort-caret.descending{border-top-color:var(--el-color-primary)}.el-table .hidden-columns{visibility:hidden;position:absolute;z-index:-1}.el-table--striped .el-table__body tr.el-table__row--striped td.el-table__cell{background:var(--el-fill-color-lighter)}.el-table--striped .el-table__body tr.el-table__row--striped.current-row td.el-table__cell{background-color:var(--el-table-current-row-bg-color)}.el-table__body tr.hover-row.current-row>td.el-table__cell,.el-table__body tr.hover-row.el-table__row--striped.current-row>td.el-table__cell,.el-table__body tr.hover-row.el-table__row--striped>td.el-table__cell,.el-table__body tr.hover-row>td.el-table__cell{background-color:var(--el-table-row-hover-bg-color)}.el-table__body tr.current-row>td.el-table__cell{background-color:var(--el-table-current-row-bg-color)}.el-table.el-table--scrollable-y .el-table__body-header{position:-webkit-sticky;position:sticky;top:0;z-index:calc(var(--el-table-index) + 2)}.el-table.el-table--scrollable-y .el-table__body-footer{position:-webkit-sticky;position:sticky;bottom:0;z-index:calc(var(--el-table-index) + 2)}.el-table__column-resize-proxy{position:absolute;left:200px;top:0;bottom:0;width:0;border-left:var(--el-table-border);z-index:calc(var(--el-table-index) + 9)}.el-table__column-filter-trigger{display:inline-block;cursor:pointer}.el-table__column-filter-trigger i{color:var(--el-color-info);font-size:14px;vertical-align:middle}.el-table__border-left-patch{top:0;left:0;width:1px;height:100%;z-index:calc(var(--el-table-index) + 2);position:absolute;background-color:var(--el-table-border-color)}.el-table__border-bottom-patch{left:0;height:1px;z-index:calc(var(--el-table-index) + 2);position:absolute;background-color:var(--el-table-border-color)}.el-table__border-right-patch{top:0;height:100%;width:1px;z-index:calc(var(--el-table-index) + 2);position:absolute;background-color:var(--el-table-border-color)}.el-table--enable-row-transition .el-table__body td.el-table__cell{transition:background-color .25s ease}.el-table--enable-row-hover .el-table__body tr:hover>td.el-table__cell{background-color:var(--el-table-row-hover-bg-color)}.el-table [class*=el-table__row--level] .el-table__expand-icon{display:inline-block;width:12px;line-height:12px;height:12px;text-align:center;margin-right:8px}.el-table .el-table.el-table--border .el-table__cell{border-right:var(--el-table-border)}.el-table:not(.el-table--border) .el-table__cell{border-right:none}.el-table:not(.el-table--border)>.el-table__inner-wrapper:after{content:none}.el-table-v2{--el-table-border-color:var(--el-border-color-lighter);--el-table-border:1px solid var(--el-table-border-color);--el-table-text-color:var(--el-text-color-regular);--el-table-header-text-color:var(--el-text-color-secondary);--el-table-row-hover-bg-color:var(--el-fill-color-light);--el-table-current-row-bg-color:var(--el-color-primary-light-9);--el-table-header-bg-color:var(--el-bg-color);--el-table-fixed-box-shadow:var(--el-box-shadow-light);--el-table-bg-color:var(--el-fill-color-blank);--el-table-tr-bg-color:var(--el-bg-color);--el-table-expanded-cell-bg-color:var(--el-fill-color-blank);--el-table-fixed-left-column:inset 10px 0 10px -10px rgba(0, 0, 0, .15);--el-table-fixed-right-column:inset -10px 0 10px -10px rgba(0, 0, 0, .15);--el-table-index:var(--el-index-normal)}.el-table-v2{font-size:14px}.el-table-v2 *{box-sizing:border-box}.el-table-v2__root{position:relative}.el-table-v2__root:hover .el-table-v2__main .el-virtual-scrollbar{opacity:1}.el-table-v2__main{display:flex;flex-direction:column-reverse;position:absolute;overflow:hidden;top:0;background-color:var(--el-bg-color);left:0}.el-table-v2__main .el-vl__horizontal,.el-table-v2__main .el-vl__vertical{z-index:2}.el-table-v2__left{display:flex;flex-direction:column-reverse;position:absolute;overflow:hidden;top:0;background-color:var(--el-bg-color);left:0;box-shadow:2px 0 4px #0000000f}.el-table-v2__left .el-virtual-scrollbar{opacity:0}.el-table-v2__left .el-vl__horizontal,.el-table-v2__left .el-vl__vertical{z-index:-1}.el-table-v2__right{display:flex;flex-direction:column-reverse;position:absolute;overflow:hidden;top:0;background-color:var(--el-bg-color);right:0;box-shadow:-2px 0 4px #0000000f}.el-table-v2__right .el-virtual-scrollbar{opacity:0}.el-table-v2__right .el-vl__horizontal,.el-table-v2__right .el-vl__vertical{z-index:-1}.el-table-v2__header-row,.el-table-v2__row{-webkit-padding-end:var(--el-table-scrollbar-size);padding-inline-end:var(--el-table-scrollbar-size)}.el-table-v2__header-wrapper{overflow:hidden}.el-table-v2__header{position:relative;overflow:hidden}.el-table-v2__footer{position:absolute;left:0;right:0;bottom:0;overflow:hidden}.el-table-v2__empty{position:absolute;left:0}.el-table-v2__overlay{position:absolute;left:0;right:0;top:0;bottom:0;z-index:9999}.el-table-v2__header-row{display:flex;border-bottom:var(--el-table-border)}.el-table-v2__header-cell{display:flex;align-items:center;padding:0 8px;height:100%;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;overflow:hidden;background-color:var(--el-table-header-bg-color);color:var(--el-table-header-text-color);font-weight:700}.el-table-v2__header-cell.is-align-center{justify-content:center;text-align:center}.el-table-v2__header-cell.is-align-right{justify-content:flex-end;text-align:right}.el-table-v2__header-cell.is-sortable{cursor:pointer}.el-table-v2__header-cell:hover .el-icon{display:block}.el-table-v2__sort-icon{transition:opacity,display var(--el-transition-duration);opacity:.6;display:none}.el-table-v2__sort-icon.is-sorting{display:block;opacity:1}.el-table-v2__row{border-bottom:var(--el-table-border);display:flex;align-items:center;transition:background-color var(--el-transition-duration)}.el-table-v2__row.is-hovered,.el-table-v2__row:hover{background-color:var(--el-table-row-hover-bg-color)}.el-table-v2__row-cell{height:100%;overflow:hidden;display:flex;align-items:center;padding:0 8px}.el-table-v2__row-cell.is-align-center{justify-content:center;text-align:center}.el-table-v2__row-cell.is-align-right{justify-content:flex-end;text-align:right}.el-table-v2__expand-icon{margin:0 4px;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.el-table-v2__expand-icon svg{transition:transform var(--el-transition-duration)}.el-table-v2__expand-icon.is-expanded svg{transform:rotate(90deg)}.el-table-v2:not(.is-dynamic) .el-table-v2__cell-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.el-table-v2.is-dynamic .el-table-v2__row{overflow:hidden;align-items:stretch}.el-table-v2.is-dynamic .el-table-v2__row .el-table-v2__row-cell{word-break:break-all}.el-tabs{--el-tabs-header-height:40px}.el-tabs__header{padding:0;position:relative;margin:0 0 15px}.el-tabs__active-bar{position:absolute;bottom:0;left:0;height:2px;background-color:var(--el-color-primary);z-index:1;transition:width var(--el-transition-duration) var(--el-transition-function-ease-in-out-bezier),transform var(--el-transition-duration) var(--el-transition-function-ease-in-out-bezier);list-style:none}.el-tabs__new-tab{display:flex;align-items:center;justify-content:center;float:right;border:1px solid var(--el-border-color);height:20px;width:20px;line-height:20px;margin:10px 0 10px 10px;border-radius:3px;text-align:center;font-size:12px;color:var(--el-text-color-primary);cursor:pointer;transition:all .15s}.el-tabs__new-tab .is-icon-plus{height:inherit;width:inherit;transform:scale(.8)}.el-tabs__new-tab .is-icon-plus svg{vertical-align:middle}.el-tabs__new-tab:hover{color:var(--el-color-primary)}.el-tabs__nav-wrap{overflow:hidden;margin-bottom:-1px;position:relative}.el-tabs__nav-wrap:after{content:"";position:absolute;left:0;bottom:0;width:100%;height:2px;background-color:var(--el-border-color-light);z-index:var(--el-index-normal)}.el-tabs__nav-wrap.is-scrollable{padding:0 20px;box-sizing:border-box}.el-tabs__nav-scroll{overflow:hidden}.el-tabs__nav-next,.el-tabs__nav-prev{position:absolute;cursor:pointer;line-height:44px;font-size:12px;color:var(--el-text-color-secondary);width:20px;text-align:center}.el-tabs__nav-next{right:0}.el-tabs__nav-prev{left:0}.el-tabs__nav{display:flex;white-space:nowrap;position:relative;transition:transform var(--el-transition-duration);float:left;z-index:calc(var(--el-index-normal) + 1)}.el-tabs__nav.is-stretch{min-width:100%;display:flex}.el-tabs__nav.is-stretch>*{flex:1;text-align:center}.el-tabs__item{padding:0 20px;height:var(--el-tabs-header-height);box-sizing:border-box;display:flex;align-items:center;justify-content:center;list-style:none;font-size:var(--el-font-size-base);font-weight:500;color:var(--el-text-color-primary);position:relative}.el-tabs__item:focus,.el-tabs__item:focus:active{outline:0}.el-tabs__item:focus-visible{box-shadow:0 0 2px 2px var(--el-color-primary) inset;border-radius:3px}.el-tabs__item .is-icon-close{border-radius:50%;text-align:center;transition:all var(--el-transition-duration) var(--el-transition-function-ease-in-out-bezier);margin-left:5px}.el-tabs__item .is-icon-close:before{transform:scale(.9);display:inline-block}.el-tabs__item .is-icon-close:hover{background-color:var(--el-text-color-placeholder);color:#fff}.el-tabs__item.is-active{color:var(--el-color-primary)}.el-tabs__item:hover{color:var(--el-color-primary);cursor:pointer}.el-tabs__item.is-disabled{color:var(--el-disabled-text-color);cursor:not-allowed}.el-tabs__content{overflow:hidden;position:relative}.el-tabs--card>.el-tabs__header{border-bottom:1px solid var(--el-border-color-light);height:var(--el-tabs-header-height)}.el-tabs--card>.el-tabs__header .el-tabs__nav-wrap:after{content:none}.el-tabs--card>.el-tabs__header .el-tabs__nav{border:1px solid var(--el-border-color-light);border-bottom:none;border-radius:4px 4px 0 0;box-sizing:border-box}.el-tabs--card>.el-tabs__header .el-tabs__active-bar{display:none}.el-tabs--card>.el-tabs__header .el-tabs__item .is-icon-close{position:relative;font-size:12px;width:0;height:14px;overflow:hidden;right:-2px;transform-origin:100% 50%}.el-tabs--card>.el-tabs__header .el-tabs__item{border-bottom:1px solid transparent;border-left:1px solid var(--el-border-color-light);transition:color var(--el-transition-duration) var(--el-transition-function-ease-in-out-bezier),padding var(--el-transition-duration) var(--el-transition-function-ease-in-out-bezier)}.el-tabs--card>.el-tabs__header .el-tabs__item:first-child{border-left:none}.el-tabs--card>.el-tabs__header .el-tabs__item.is-closable:hover{padding-left:13px;padding-right:13px}.el-tabs--card>.el-tabs__header .el-tabs__item.is-closable:hover .is-icon-close{width:14px}.el-tabs--card>.el-tabs__header .el-tabs__item.is-active{border-bottom-color:var(--el-bg-color)}.el-tabs--card>.el-tabs__header .el-tabs__item.is-active.is-closable{padding-left:20px;padding-right:20px}.el-tabs--card>.el-tabs__header .el-tabs__item.is-active.is-closable .is-icon-close{width:14px}.el-tabs--border-card{background:var(--el-bg-color-overlay);border:1px solid var(--el-border-color)}.el-tabs--border-card>.el-tabs__content{padding:15px}.el-tabs--border-card>.el-tabs__header{background-color:var(--el-fill-color-light);border-bottom:1px solid var(--el-border-color-light);margin:0}.el-tabs--border-card>.el-tabs__header .el-tabs__nav-wrap:after{content:none}.el-tabs--border-card>.el-tabs__header .el-tabs__item{transition:all var(--el-transition-duration) var(--el-transition-function-ease-in-out-bezier);border:1px solid transparent;margin-top:-1px;color:var(--el-text-color-secondary)}.el-tabs--border-card>.el-tabs__header .el-tabs__item:first-child{margin-left:-1px}.el-tabs--border-card>.el-tabs__header .el-tabs__item+.el-tabs__item{margin-left:-1px}.el-tabs--border-card>.el-tabs__header .el-tabs__item.is-active{color:var(--el-color-primary);background-color:var(--el-bg-color-overlay);border-right-color:var(--el-border-color);border-left-color:var(--el-border-color)}.el-tabs--border-card>.el-tabs__header .el-tabs__item:not(.is-disabled):hover{color:var(--el-color-primary)}.el-tabs--border-card>.el-tabs__header .el-tabs__item.is-disabled{color:var(--el-disabled-text-color)}.el-tabs--border-card>.el-tabs__header .is-scrollable .el-tabs__item:first-child{margin-left:0}.el-tabs--bottom .el-tabs__item.is-bottom:nth-child(2),.el-tabs--bottom .el-tabs__item.is-top:nth-child(2),.el-tabs--top .el-tabs__item.is-bottom:nth-child(2),.el-tabs--top .el-tabs__item.is-top:nth-child(2){padding-left:0}.el-tabs--bottom .el-tabs__item.is-bottom:last-child,.el-tabs--bottom .el-tabs__item.is-top:last-child,.el-tabs--top .el-tabs__item.is-bottom:last-child,.el-tabs--top .el-tabs__item.is-top:last-child{padding-right:0}.el-tabs--bottom .el-tabs--left>.el-tabs__header .el-tabs__item:nth-child(2),.el-tabs--bottom .el-tabs--right>.el-tabs__header .el-tabs__item:nth-child(2),.el-tabs--bottom.el-tabs--border-card>.el-tabs__header .el-tabs__item:nth-child(2),.el-tabs--bottom.el-tabs--card>.el-tabs__header .el-tabs__item:nth-child(2),.el-tabs--top .el-tabs--left>.el-tabs__header .el-tabs__item:nth-child(2),.el-tabs--top .el-tabs--right>.el-tabs__header .el-tabs__item:nth-child(2),.el-tabs--top.el-tabs--border-card>.el-tabs__header .el-tabs__item:nth-child(2),.el-tabs--top.el-tabs--card>.el-tabs__header .el-tabs__item:nth-child(2){padding-left:20px}.el-tabs--bottom .el-tabs--left>.el-tabs__header .el-tabs__item:nth-child(2):not(.is-active).is-closable:hover,.el-tabs--bottom .el-tabs--right>.el-tabs__header .el-tabs__item:nth-child(2):not(.is-active).is-closable:hover,.el-tabs--bottom.el-tabs--border-card>.el-tabs__header .el-tabs__item:nth-child(2):not(.is-active).is-closable:hover,.el-tabs--bottom.el-tabs--card>.el-tabs__header .el-tabs__item:nth-child(2):not(.is-active).is-closable:hover,.el-tabs--top .el-tabs--left>.el-tabs__header .el-tabs__item:nth-child(2):not(.is-active).is-closable:hover,.el-tabs--top .el-tabs--right>.el-tabs__header .el-tabs__item:nth-child(2):not(.is-active).is-closable:hover,.el-tabs--top.el-tabs--border-card>.el-tabs__header .el-tabs__item:nth-child(2):not(.is-active).is-closable:hover,.el-tabs--top.el-tabs--card>.el-tabs__header .el-tabs__item:nth-child(2):not(.is-active).is-closable:hover{padding-left:13px}.el-tabs--bottom .el-tabs--left>.el-tabs__header .el-tabs__item:last-child,.el-tabs--bottom .el-tabs--right>.el-tabs__header .el-tabs__item:last-child,.el-tabs--bottom.el-tabs--border-card>.el-tabs__header .el-tabs__item:last-child,.el-tabs--bottom.el-tabs--card>.el-tabs__header .el-tabs__item:last-child,.el-tabs--top .el-tabs--left>.el-tabs__header .el-tabs__item:last-child,.el-tabs--top .el-tabs--right>.el-tabs__header .el-tabs__item:last-child,.el-tabs--top.el-tabs--border-card>.el-tabs__header .el-tabs__item:last-child,.el-tabs--top.el-tabs--card>.el-tabs__header .el-tabs__item:last-child{padding-right:20px}.el-tabs--bottom .el-tabs--left>.el-tabs__header .el-tabs__item:last-child:not(.is-active).is-closable:hover,.el-tabs--bottom .el-tabs--right>.el-tabs__header .el-tabs__item:last-child:not(.is-active).is-closable:hover,.el-tabs--bottom.el-tabs--border-card>.el-tabs__header .el-tabs__item:last-child:not(.is-active).is-closable:hover,.el-tabs--bottom.el-tabs--card>.el-tabs__header .el-tabs__item:last-child:not(.is-active).is-closable:hover,.el-tabs--top .el-tabs--left>.el-tabs__header .el-tabs__item:last-child:not(.is-active).is-closable:hover,.el-tabs--top .el-tabs--right>.el-tabs__header .el-tabs__item:last-child:not(.is-active).is-closable:hover,.el-tabs--top.el-tabs--border-card>.el-tabs__header .el-tabs__item:last-child:not(.is-active).is-closable:hover,.el-tabs--top.el-tabs--card>.el-tabs__header .el-tabs__item:last-child:not(.is-active).is-closable:hover{padding-right:13px}.el-tabs--bottom .el-tabs__header.is-bottom{margin-bottom:0;margin-top:10px}.el-tabs--bottom.el-tabs--border-card .el-tabs__header.is-bottom{border-bottom:0;border-top:1px solid var(--el-border-color)}.el-tabs--bottom.el-tabs--border-card .el-tabs__nav-wrap.is-bottom{margin-top:-1px;margin-bottom:0}.el-tabs--bottom.el-tabs--border-card .el-tabs__item.is-bottom:not(.is-active){border:1px solid transparent}.el-tabs--bottom.el-tabs--border-card .el-tabs__item.is-bottom{margin:0 -1px -1px}.el-tabs--left,.el-tabs--right{overflow:hidden}.el-tabs--left .el-tabs__header.is-left,.el-tabs--left .el-tabs__header.is-right,.el-tabs--left .el-tabs__nav-scroll,.el-tabs--left .el-tabs__nav-wrap.is-left,.el-tabs--left .el-tabs__nav-wrap.is-right,.el-tabs--right .el-tabs__header.is-left,.el-tabs--right .el-tabs__header.is-right,.el-tabs--right .el-tabs__nav-scroll,.el-tabs--right .el-tabs__nav-wrap.is-left,.el-tabs--right .el-tabs__nav-wrap.is-right{height:100%}.el-tabs--left .el-tabs__active-bar.is-left,.el-tabs--left .el-tabs__active-bar.is-right,.el-tabs--right .el-tabs__active-bar.is-left,.el-tabs--right .el-tabs__active-bar.is-right{top:0;bottom:auto;width:2px;height:auto}.el-tabs--left .el-tabs__nav-wrap.is-left,.el-tabs--left .el-tabs__nav-wrap.is-right,.el-tabs--right .el-tabs__nav-wrap.is-left,.el-tabs--right .el-tabs__nav-wrap.is-right{margin-bottom:0}.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-next,.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-next,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-next,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-next,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev{height:30px;line-height:30px;width:100%;text-align:center;cursor:pointer}.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-next i,.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev i,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-next i,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev i,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-next i,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev i,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-next i,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev i{transform:rotate(90deg)}.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev{left:auto;top:0}.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-next,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-next,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-next,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-next{right:auto;bottom:0}.el-tabs--left .el-tabs__nav-wrap.is-left.is-scrollable,.el-tabs--left .el-tabs__nav-wrap.is-right.is-scrollable,.el-tabs--right .el-tabs__nav-wrap.is-left.is-scrollable,.el-tabs--right .el-tabs__nav-wrap.is-right.is-scrollable{padding:30px 0}.el-tabs--left .el-tabs__nav-wrap.is-left:after,.el-tabs--left .el-tabs__nav-wrap.is-right:after,.el-tabs--right .el-tabs__nav-wrap.is-left:after,.el-tabs--right .el-tabs__nav-wrap.is-right:after{height:100%;width:2px;bottom:auto;top:0}.el-tabs--left .el-tabs__nav.is-left,.el-tabs--left .el-tabs__nav.is-right,.el-tabs--right .el-tabs__nav.is-left,.el-tabs--right .el-tabs__nav.is-right{flex-direction:column}.el-tabs--left .el-tabs__item.is-left,.el-tabs--right .el-tabs__item.is-left{justify-content:flex-end}.el-tabs--left .el-tabs__item.is-right,.el-tabs--right .el-tabs__item.is-right{justify-content:flex-start}.el-tabs--left .el-tabs__header.is-left{float:left;margin-bottom:0;margin-right:10px}.el-tabs--left .el-tabs__nav-wrap.is-left{margin-right:-1px}.el-tabs--left .el-tabs__nav-wrap.is-left:after{left:auto;right:0}.el-tabs--left .el-tabs__active-bar.is-left{right:0;left:auto}.el-tabs--left .el-tabs__item.is-left{text-align:right}.el-tabs--left.el-tabs--card .el-tabs__active-bar.is-left{display:none}.el-tabs--left.el-tabs--card .el-tabs__item.is-left{border-left:none;border-right:1px solid var(--el-border-color-light);border-bottom:none;border-top:1px solid var(--el-border-color-light);text-align:left}.el-tabs--left.el-tabs--card .el-tabs__item.is-left:first-child{border-right:1px solid var(--el-border-color-light);border-top:none}.el-tabs--left.el-tabs--card .el-tabs__item.is-left.is-active{border:1px solid var(--el-border-color-light);border-right-color:#fff;border-left:none;border-bottom:none}.el-tabs--left.el-tabs--card .el-tabs__item.is-left.is-active:first-child{border-top:none}.el-tabs--left.el-tabs--card .el-tabs__item.is-left.is-active:last-child{border-bottom:none}.el-tabs--left.el-tabs--card .el-tabs__nav{border-radius:4px 0 0 4px;border-bottom:1px solid var(--el-border-color-light);border-right:none}.el-tabs--left.el-tabs--card .el-tabs__new-tab{float:none}.el-tabs--left.el-tabs--border-card .el-tabs__header.is-left{border-right:1px solid var(--el-border-color)}.el-tabs--left.el-tabs--border-card .el-tabs__item.is-left{border:1px solid transparent;margin:-1px 0 -1px -1px}.el-tabs--left.el-tabs--border-card .el-tabs__item.is-left.is-active{border-color:transparent;border-top-color:#d1dbe5;border-bottom-color:#d1dbe5}.el-tabs--right .el-tabs__header.is-right{float:right;margin-bottom:0;margin-left:10px}.el-tabs--right .el-tabs__nav-wrap.is-right{margin-left:-1px}.el-tabs--right .el-tabs__nav-wrap.is-right:after{left:0;right:auto}.el-tabs--right .el-tabs__active-bar.is-right{left:0}.el-tabs--right.el-tabs--card .el-tabs__active-bar.is-right{display:none}.el-tabs--right.el-tabs--card .el-tabs__item.is-right{border-bottom:none;border-top:1px solid var(--el-border-color-light)}.el-tabs--right.el-tabs--card .el-tabs__item.is-right:first-child{border-left:1px solid var(--el-border-color-light);border-top:none}.el-tabs--right.el-tabs--card .el-tabs__item.is-right.is-active{border:1px solid var(--el-border-color-light);border-left-color:#fff;border-right:none;border-bottom:none}.el-tabs--right.el-tabs--card .el-tabs__item.is-right.is-active:first-child{border-top:none}.el-tabs--right.el-tabs--card .el-tabs__item.is-right.is-active:last-child{border-bottom:none}.el-tabs--right.el-tabs--card .el-tabs__nav{border-radius:0 4px 4px 0;border-bottom:1px solid var(--el-border-color-light);border-left:none}.el-tabs--right.el-tabs--border-card .el-tabs__header.is-right{border-left:1px solid var(--el-border-color)}.el-tabs--right.el-tabs--border-card .el-tabs__item.is-right{border:1px solid transparent;margin:-1px -1px -1px 0}.el-tabs--right.el-tabs--border-card .el-tabs__item.is-right.is-active{border-color:transparent;border-top-color:#d1dbe5;border-bottom-color:#d1dbe5}.slideInLeft-transition,.slideInRight-transition{display:inline-block}.slideInRight-enter{-webkit-animation:slideInRight-enter var(--el-transition-duration);animation:slideInRight-enter var(--el-transition-duration)}.slideInRight-leave{position:absolute;left:0;right:0;-webkit-animation:slideInRight-leave var(--el-transition-duration);animation:slideInRight-leave var(--el-transition-duration)}.slideInLeft-enter{-webkit-animation:slideInLeft-enter var(--el-transition-duration);animation:slideInLeft-enter var(--el-transition-duration)}.slideInLeft-leave{position:absolute;left:0;right:0;-webkit-animation:slideInLeft-leave var(--el-transition-duration);animation:slideInLeft-leave var(--el-transition-duration)}@-webkit-keyframes slideInRight-enter{0%{opacity:0;transform-origin:0 0;transform:translate(100%)}to{opacity:1;transform-origin:0 0;transform:translate(0)}}@keyframes slideInRight-enter{0%{opacity:0;transform-origin:0 0;transform:translate(100%)}to{opacity:1;transform-origin:0 0;transform:translate(0)}}@-webkit-keyframes slideInRight-leave{0%{transform-origin:0 0;transform:translate(0);opacity:1}to{transform-origin:0 0;transform:translate(100%);opacity:0}}@keyframes slideInRight-leave{0%{transform-origin:0 0;transform:translate(0);opacity:1}to{transform-origin:0 0;transform:translate(100%);opacity:0}}@-webkit-keyframes slideInLeft-enter{0%{opacity:0;transform-origin:0 0;transform:translate(-100%)}to{opacity:1;transform-origin:0 0;transform:translate(0)}}@keyframes slideInLeft-enter{0%{opacity:0;transform-origin:0 0;transform:translate(-100%)}to{opacity:1;transform-origin:0 0;transform:translate(0)}}@-webkit-keyframes slideInLeft-leave{0%{transform-origin:0 0;transform:translate(0);opacity:1}to{transform-origin:0 0;transform:translate(-100%);opacity:0}}@keyframes slideInLeft-leave{0%{transform-origin:0 0;transform:translate(0);opacity:1}to{transform-origin:0 0;transform:translate(-100%);opacity:0}}.el-tag{--el-tag-font-size:12px;--el-tag-border-radius:4px;--el-tag-border-radius-rounded:9999px}.el-tag{--el-tag-bg-color:var(--el-color-primary-light-9);--el-tag-border-color:var(--el-color-primary-light-8);--el-tag-hover-color:var(--el-color-primary);--el-tag-text-color:var(--el-color-primary);background-color:var(--el-tag-bg-color);border-color:var(--el-tag-border-color);color:var(--el-tag-text-color);display:inline-flex;justify-content:center;align-items:center;vertical-align:middle;height:24px;padding:0 9px;font-size:var(--el-tag-font-size);line-height:1;border-width:1px;border-style:solid;border-radius:var(--el-tag-border-radius);box-sizing:border-box;white-space:nowrap;--el-icon-size:14px}.el-tag.el-tag--primary{--el-tag-bg-color:var(--el-color-primary-light-9);--el-tag-border-color:var(--el-color-primary-light-8);--el-tag-hover-color:var(--el-color-primary)}.el-tag.el-tag--success{--el-tag-bg-color:var(--el-color-success-light-9);--el-tag-border-color:var(--el-color-success-light-8);--el-tag-hover-color:var(--el-color-success)}.el-tag.el-tag--warning{--el-tag-bg-color:var(--el-color-warning-light-9);--el-tag-border-color:var(--el-color-warning-light-8);--el-tag-hover-color:var(--el-color-warning)}.el-tag.el-tag--danger{--el-tag-bg-color:var(--el-color-danger-light-9);--el-tag-border-color:var(--el-color-danger-light-8);--el-tag-hover-color:var(--el-color-danger)}.el-tag.el-tag--error{--el-tag-bg-color:var(--el-color-error-light-9);--el-tag-border-color:var(--el-color-error-light-8);--el-tag-hover-color:var(--el-color-error)}.el-tag.el-tag--info{--el-tag-bg-color:var(--el-color-info-light-9);--el-tag-border-color:var(--el-color-info-light-8);--el-tag-hover-color:var(--el-color-info)}.el-tag.el-tag--primary{--el-tag-text-color:var(--el-color-primary)}.el-tag.el-tag--success{--el-tag-text-color:var(--el-color-success)}.el-tag.el-tag--warning{--el-tag-text-color:var(--el-color-warning)}.el-tag.el-tag--danger{--el-tag-text-color:var(--el-color-danger)}.el-tag.el-tag--error{--el-tag-text-color:var(--el-color-error)}.el-tag.el-tag--info{--el-tag-text-color:var(--el-color-info)}.el-tag.is-hit{border-color:var(--el-color-primary)}.el-tag.is-round{border-radius:var(--el-tag-border-radius-rounded)}.el-tag .el-tag__close{color:var(--el-tag-text-color)}.el-tag .el-tag__close:hover{color:var(--el-color-white);background-color:var(--el-tag-hover-color)}.el-tag .el-icon{border-radius:50%;cursor:pointer;font-size:calc(var(--el-icon-size) - 2px);height:var(--el-icon-size);width:var(--el-icon-size)}.el-tag .el-tag__close{margin-left:6px}.el-tag--dark{--el-tag-bg-color:var(--el-color-primary);--el-tag-border-color:var(--el-color-primary);--el-tag-hover-color:var(--el-color-primary-light-3);--el-tag-text-color:var(--el-color-white)}.el-tag--dark.el-tag--primary{--el-tag-bg-color:var(--el-color-primary);--el-tag-border-color:var(--el-color-primary);--el-tag-hover-color:var(--el-color-primary-light-3)}.el-tag--dark.el-tag--success{--el-tag-bg-color:var(--el-color-success);--el-tag-border-color:var(--el-color-success);--el-tag-hover-color:var(--el-color-success-light-3)}.el-tag--dark.el-tag--warning{--el-tag-bg-color:var(--el-color-warning);--el-tag-border-color:var(--el-color-warning);--el-tag-hover-color:var(--el-color-warning-light-3)}.el-tag--dark.el-tag--danger{--el-tag-bg-color:var(--el-color-danger);--el-tag-border-color:var(--el-color-danger);--el-tag-hover-color:var(--el-color-danger-light-3)}.el-tag--dark.el-tag--error{--el-tag-bg-color:var(--el-color-error);--el-tag-border-color:var(--el-color-error);--el-tag-hover-color:var(--el-color-error-light-3)}.el-tag--dark.el-tag--info{--el-tag-bg-color:var(--el-color-info);--el-tag-border-color:var(--el-color-info);--el-tag-hover-color:var(--el-color-info-light-3)}.el-tag--dark.el-tag--primary,.el-tag--dark.el-tag--success,.el-tag--dark.el-tag--warning,.el-tag--dark.el-tag--danger,.el-tag--dark.el-tag--error,.el-tag--dark.el-tag--info{--el-tag-text-color:var(--el-color-white)}.el-tag--plain{--el-tag-border-color:var(--el-color-primary-light-5);--el-tag-hover-color:var(--el-color-primary);--el-tag-bg-color:var(--el-fill-color-blank)}.el-tag--plain.el-tag--primary{--el-tag-bg-color:var(--el-fill-color-blank);--el-tag-border-color:var(--el-color-primary-light-5);--el-tag-hover-color:var(--el-color-primary)}.el-tag--plain.el-tag--success{--el-tag-bg-color:var(--el-fill-color-blank);--el-tag-border-color:var(--el-color-success-light-5);--el-tag-hover-color:var(--el-color-success)}.el-tag--plain.el-tag--warning{--el-tag-bg-color:var(--el-fill-color-blank);--el-tag-border-color:var(--el-color-warning-light-5);--el-tag-hover-color:var(--el-color-warning)}.el-tag--plain.el-tag--danger{--el-tag-bg-color:var(--el-fill-color-blank);--el-tag-border-color:var(--el-color-danger-light-5);--el-tag-hover-color:var(--el-color-danger)}.el-tag--plain.el-tag--error{--el-tag-bg-color:var(--el-fill-color-blank);--el-tag-border-color:var(--el-color-error-light-5);--el-tag-hover-color:var(--el-color-error)}.el-tag--plain.el-tag--info{--el-tag-bg-color:var(--el-fill-color-blank);--el-tag-border-color:var(--el-color-info-light-5);--el-tag-hover-color:var(--el-color-info)}.el-tag.is-closable{padding-right:5px}.el-tag--large{padding:0 11px;height:32px;--el-icon-size:16px}.el-tag--large .el-tag__close{margin-left:8px}.el-tag--large.is-closable{padding-right:7px}.el-tag--small{padding:0 7px;height:20px;--el-icon-size:12px}.el-tag--small .el-tag__close{margin-left:4px}.el-tag--small.is-closable{padding-right:3px}.el-tag--small .el-icon-close{transform:scale(.8)}.el-tag.el-tag--primary.is-hit{border-color:var(--el-color-primary)}.el-tag.el-tag--success.is-hit{border-color:var(--el-color-success)}.el-tag.el-tag--warning.is-hit{border-color:var(--el-color-warning)}.el-tag.el-tag--danger.is-hit{border-color:var(--el-color-danger)}.el-tag.el-tag--error.is-hit{border-color:var(--el-color-error)}.el-tag.el-tag--info.is-hit{border-color:var(--el-color-info)}.el-text{--el-text-font-size:var(--el-font-size-base);--el-text-color:var(--el-text-color-regular)}.el-text{align-self:center;margin:0;padding:0;font-size:var(--el-text-font-size);color:var(--el-text-color);word-break:break-all}.el-text.is-truncated{display:inline-block;max-width:100%;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.el-text.is-line-clamp{display:-webkit-inline-box;-webkit-box-orient:vertical;overflow:hidden}.el-text--large{--el-text-font-size:var(--el-font-size-medium)}.el-text--default{--el-text-font-size:var(--el-font-size-base)}.el-text--small{--el-text-font-size:var(--el-font-size-extra-small)}.el-text.el-text--primary{--el-text-color:var(--el-color-primary)}.el-text.el-text--success{--el-text-color:var(--el-color-success)}.el-text.el-text--warning{--el-text-color:var(--el-color-warning)}.el-text.el-text--danger{--el-text-color:var(--el-color-danger)}.el-text.el-text--error{--el-text-color:var(--el-color-error)}.el-text.el-text--info{--el-text-color:var(--el-color-info)}.el-text>.el-icon{vertical-align:-2px}.time-select{margin:5px 0;min-width:0}.time-select .el-picker-panel__content{max-height:200px;margin:0}.time-select-item{padding:8px 10px;font-size:14px;line-height:20px}.time-select-item.disabled{color:var(--el-datepicker-border-color);cursor:not-allowed}.time-select-item:hover{background-color:var(--el-fill-color-light);font-weight:700;cursor:pointer}.time-select .time-select-item.selected:not(.disabled){color:var(--el-color-primary);font-weight:700}.el-timeline-item{position:relative;padding-bottom:20px}.el-timeline-item__wrapper{position:relative;padding-left:28px;top:-3px}.el-timeline-item__tail{position:absolute;left:4px;height:100%;border-left:2px solid var(--el-timeline-node-color)}.el-timeline-item .el-timeline-item__icon{color:var(--el-color-white);font-size:var(--el-font-size-small)}.el-timeline-item__node{position:absolute;background-color:var(--el-timeline-node-color);border-color:var(--el-timeline-node-color);border-radius:50%;box-sizing:border-box;display:flex;justify-content:center;align-items:center}.el-timeline-item__node--normal{left:-1px;width:var(--el-timeline-node-size-normal);height:var(--el-timeline-node-size-normal)}.el-timeline-item__node--large{left:-2px;width:var(--el-timeline-node-size-large);height:var(--el-timeline-node-size-large)}.el-timeline-item__node.is-hollow{background:var(--el-color-white);border-style:solid;border-width:2px}.el-timeline-item__node--primary{background-color:var(--el-color-primary);border-color:var(--el-color-primary)}.el-timeline-item__node--success{background-color:var(--el-color-success);border-color:var(--el-color-success)}.el-timeline-item__node--warning{background-color:var(--el-color-warning);border-color:var(--el-color-warning)}.el-timeline-item__node--danger{background-color:var(--el-color-danger);border-color:var(--el-color-danger)}.el-timeline-item__node--info{background-color:var(--el-color-info);border-color:var(--el-color-info)}.el-timeline-item__dot{position:absolute;display:flex;justify-content:center;align-items:center}.el-timeline-item__content{color:var(--el-text-color-primary)}.el-timeline-item__timestamp{color:var(--el-text-color-secondary);line-height:1;font-size:var(--el-font-size-small)}.el-timeline-item__timestamp.is-top{margin-bottom:8px;padding-top:4px}.el-timeline-item__timestamp.is-bottom{margin-top:8px}.el-timeline{--el-timeline-node-size-normal:12px;--el-timeline-node-size-large:14px;--el-timeline-node-color:var(--el-border-color-light)}.el-timeline{margin:0;font-size:var(--el-font-size-base);list-style:none}.el-timeline .el-timeline-item:last-child .el-timeline-item__tail{display:none}.el-timeline .el-timeline-item__center{display:flex;align-items:center}.el-timeline .el-timeline-item__center .el-timeline-item__wrapper{width:100%}.el-timeline .el-timeline-item__center .el-timeline-item__tail{top:0}.el-timeline .el-timeline-item__center:first-child .el-timeline-item__tail{height:calc(50% + 10px);top:calc(50% - 10px)}.el-timeline .el-timeline-item__center:last-child .el-timeline-item__tail{display:block;height:calc(50% - 10px)}.el-tooltip-v2__content{--el-tooltip-v2-padding:5px 10px;--el-tooltip-v2-border-radius:4px;--el-tooltip-v2-border-color:var(--el-border-color);border-radius:var(--el-tooltip-v2-border-radius);color:var(--el-color-black);background-color:var(--el-color-white);padding:var(--el-tooltip-v2-padding);border:1px solid var(--el-border-color)}.el-tooltip-v2__arrow{position:absolute;color:var(--el-color-white);width:var(--el-tooltip-v2-arrow-width);height:var(--el-tooltip-v2-arrow-height);pointer-events:none;left:var(--el-tooltip-v2-arrow-x);top:var(--el-tooltip-v2-arrow-y)}.el-tooltip-v2__arrow:before{content:"";width:0;height:0;border:var(--el-tooltip-v2-arrow-border-width) solid transparent;position:absolute}.el-tooltip-v2__arrow:after{content:"";width:0;height:0;border:var(--el-tooltip-v2-arrow-border-width) solid transparent;position:absolute}.el-tooltip-v2__content[data-side^=top] .el-tooltip-v2__arrow{bottom:0}.el-tooltip-v2__content[data-side^=top] .el-tooltip-v2__arrow:before{border-top-color:var(--el-color-white);border-top-width:var(--el-tooltip-v2-arrow-border-width);border-bottom:0;top:calc(100% - 1px)}.el-tooltip-v2__content[data-side^=top] .el-tooltip-v2__arrow:after{border-top-color:var(--el-border-color);border-top-width:var(--el-tooltip-v2-arrow-border-width);border-bottom:0;top:100%;z-index:-1}.el-tooltip-v2__content[data-side^=bottom] .el-tooltip-v2__arrow{top:0}.el-tooltip-v2__content[data-side^=bottom] .el-tooltip-v2__arrow:before{border-bottom-color:var(--el-color-white);border-bottom-width:var(--el-tooltip-v2-arrow-border-width);border-top:0;bottom:calc(100% - 1px)}.el-tooltip-v2__content[data-side^=bottom] .el-tooltip-v2__arrow:after{border-bottom-color:var(--el-border-color);border-bottom-width:var(--el-tooltip-v2-arrow-border-width);border-top:0;bottom:100%;z-index:-1}.el-tooltip-v2__content[data-side^=left] .el-tooltip-v2__arrow{right:0}.el-tooltip-v2__content[data-side^=left] .el-tooltip-v2__arrow:before{border-left-color:var(--el-color-white);border-left-width:var(--el-tooltip-v2-arrow-border-width);border-right:0;left:calc(100% - 1px)}.el-tooltip-v2__content[data-side^=left] .el-tooltip-v2__arrow:after{border-left-color:var(--el-border-color);border-left-width:var(--el-tooltip-v2-arrow-border-width);border-right:0;left:100%;z-index:-1}.el-tooltip-v2__content[data-side^=right] .el-tooltip-v2__arrow{left:0}.el-tooltip-v2__content[data-side^=right] .el-tooltip-v2__arrow:before{border-right-color:var(--el-color-white);border-right-width:var(--el-tooltip-v2-arrow-border-width);border-left:0;right:calc(100% - 1px)}.el-tooltip-v2__content[data-side^=right] .el-tooltip-v2__arrow:after{border-right-color:var(--el-border-color);border-right-width:var(--el-tooltip-v2-arrow-border-width);border-left:0;right:100%;z-index:-1}.el-tooltip-v2__content.is-dark{--el-tooltip-v2-border-color:transparent;background-color:var(--el-color-black);color:var(--el-color-white);border-color:transparent}.el-tooltip-v2__content.is-dark .el-tooltip-v2__arrow{background-color:var(--el-color-black);border-color:transparent}.el-transfer{--el-transfer-border-color:var(--el-border-color-lighter);--el-transfer-border-radius:var(--el-border-radius-base);--el-transfer-panel-width:200px;--el-transfer-panel-header-height:40px;--el-transfer-panel-header-bg-color:var(--el-fill-color-light);--el-transfer-panel-footer-height:40px;--el-transfer-panel-body-height:278px;--el-transfer-item-height:30px;--el-transfer-filter-height:32px}.el-transfer{font-size:var(--el-font-size-base)}.el-transfer__buttons{display:inline-block;vertical-align:middle;padding:0 30px}.el-transfer__button{vertical-align:top}.el-transfer__button:nth-child(2){margin:0 0 0 10px}.el-transfer__button i,.el-transfer__button span{font-size:14px}.el-transfer__button .el-icon+span{margin-left:0}.el-transfer-panel{overflow:hidden;background:var(--el-bg-color-overlay);display:inline-block;text-align:left;vertical-align:middle;width:var(--el-transfer-panel-width);max-height:100%;box-sizing:border-box;position:relative}.el-transfer-panel__body{height:var(--el-transfer-panel-body-height);border-left:1px solid var(--el-transfer-border-color);border-right:1px solid var(--el-transfer-border-color);border-bottom:1px solid var(--el-transfer-border-color);border-bottom-left-radius:var(--el-transfer-border-radius);border-bottom-right-radius:var(--el-transfer-border-radius);overflow:hidden}.el-transfer-panel__body.is-with-footer{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.el-transfer-panel__list{margin:0;padding:6px 0;list-style:none;height:var(--el-transfer-panel-body-height);overflow:auto;box-sizing:border-box}.el-transfer-panel__list.is-filterable{height:calc(100% - var(--el-transfer-filter-height) - 30px);padding-top:0}.el-transfer-panel__item{height:var(--el-transfer-item-height);line-height:var(--el-transfer-item-height);padding-left:15px;display:block!important}.el-transfer-panel__item+.el-transfer-panel__item{margin-left:0}.el-transfer-panel__item.el-checkbox{color:var(--el-text-color-regular)}.el-transfer-panel__item:hover{color:var(--el-color-primary)}.el-transfer-panel__item.el-checkbox .el-checkbox__label{width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:block;box-sizing:border-box;padding-left:22px;line-height:var(--el-transfer-item-height)}.el-transfer-panel__item .el-checkbox__input{position:absolute;top:8px}.el-transfer-panel__filter{text-align:center;padding:15px;box-sizing:border-box}.el-transfer-panel__filter .el-input__inner{height:var(--el-transfer-filter-height);width:100%;font-size:12px;display:inline-block;box-sizing:border-box;border-radius:calc(var(--el-transfer-filter-height)/ 2)}.el-transfer-panel__filter .el-icon-circle-close{cursor:pointer}.el-transfer-panel .el-transfer-panel__header{display:flex;align-items:center;height:var(--el-transfer-panel-header-height);background:var(--el-transfer-panel-header-bg-color);margin:0;padding-left:15px;border:1px solid var(--el-transfer-border-color);border-top-left-radius:var(--el-transfer-border-radius);border-top-right-radius:var(--el-transfer-border-radius);box-sizing:border-box;color:var(--el-color-black)}.el-transfer-panel .el-transfer-panel__header .el-checkbox{position:relative;display:flex;width:100%;align-items:center}.el-transfer-panel .el-transfer-panel__header .el-checkbox .el-checkbox__label{font-size:16px;color:var(--el-text-color-primary);font-weight:400}.el-transfer-panel .el-transfer-panel__header .el-checkbox .el-checkbox__label span{position:absolute;right:15px;top:50%;transform:translate3d(0,-50%,0);color:var(--el-text-color-secondary);font-size:12px;font-weight:400}.el-transfer-panel .el-transfer-panel__footer{height:var(--el-transfer-panel-footer-height);background:var(--el-bg-color-overlay);margin:0;padding:0;border:1px solid var(--el-transfer-border-color);border-bottom-left-radius:var(--el-transfer-border-radius);border-bottom-right-radius:var(--el-transfer-border-radius)}.el-transfer-panel .el-transfer-panel__footer:after{display:inline-block;content:"";height:100%;vertical-align:middle}.el-transfer-panel .el-transfer-panel__footer .el-checkbox{padding-left:20px;color:var(--el-text-color-regular)}.el-transfer-panel .el-transfer-panel__empty{margin:0;height:var(--el-transfer-item-height);line-height:var(--el-transfer-item-height);padding:6px 15px 0;color:var(--el-text-color-secondary);text-align:center}.el-transfer-panel .el-checkbox__label{padding-left:8px}.el-transfer-panel .el-checkbox__inner{height:14px;width:14px;border-radius:3px}.el-transfer-panel .el-checkbox__inner:after{height:6px;width:3px;left:4px}.el-tree{--el-tree-node-content-height:26px;--el-tree-node-hover-bg-color:var(--el-fill-color-light);--el-tree-text-color:var(--el-text-color-regular);--el-tree-expand-icon-color:var(--el-text-color-placeholder)}.el-tree{position:relative;cursor:default;background:var(--el-fill-color-blank);color:var(--el-tree-text-color);font-size:var(--el-font-size-base)}.el-tree__empty-block{position:relative;min-height:60px;text-align:center;width:100%;height:100%}.el-tree__empty-text{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);color:var(--el-text-color-secondary);font-size:var(--el-font-size-base)}.el-tree__drop-indicator{position:absolute;left:0;right:0;height:1px;background-color:var(--el-color-primary)}.el-tree-node{white-space:nowrap;outline:0}.el-tree-node:focus>.el-tree-node__content{background-color:var(--el-tree-node-hover-bg-color)}.el-tree-node.is-drop-inner>.el-tree-node__content .el-tree-node__label{background-color:var(--el-color-primary);color:#fff}.el-tree-node__content{--el-checkbox-height:var(--el-tree-node-content-height);display:flex;align-items:center;height:var(--el-tree-node-content-height);cursor:pointer}.el-tree-node__content>.el-tree-node__expand-icon{padding:6px;box-sizing:content-box}.el-tree-node__content>label.el-checkbox{margin-right:8px}.el-tree-node__content:hover{background-color:var(--el-tree-node-hover-bg-color)}.el-tree.is-dragging .el-tree-node__content{cursor:move}.el-tree.is-dragging .el-tree-node__content *{pointer-events:none}.el-tree.is-dragging.is-drop-not-allow .el-tree-node__content{cursor:not-allowed}.el-tree-node__expand-icon{cursor:pointer;color:var(--el-tree-expand-icon-color);font-size:12px;transform:rotate(0);transition:transform var(--el-transition-duration) ease-in-out}.el-tree-node__expand-icon.expanded{transform:rotate(90deg)}.el-tree-node__expand-icon.is-leaf{color:transparent;cursor:default;visibility:hidden}.el-tree-node__expand-icon.is-hidden{visibility:hidden}.el-tree-node__loading-icon{margin-right:8px;font-size:var(--el-font-size-base);color:var(--el-tree-expand-icon-color)}.el-tree-node>.el-tree-node__children{overflow:hidden;background-color:transparent}.el-tree-node.is-expanded>.el-tree-node__children{display:block}.el-tree--highlight-current .el-tree-node.is-current>.el-tree-node__content{background-color:var(--el-color-primary-light-9)}.el-tree-select{--el-tree-node-content-height:26px;--el-tree-node-hover-bg-color:var(--el-fill-color-light);--el-tree-text-color:var(--el-text-color-regular);--el-tree-expand-icon-color:var(--el-text-color-placeholder)}.el-tree-select__popper .el-tree-node__expand-icon{margin-left:8px}.el-tree-select__popper .el-tree-node.is-checked>.el-tree-node__content .el-select-dropdown__item.selected:after{content:none}.el-tree-select__popper .el-select-dropdown__item{flex:1;background:0 0!important;padding-left:0;height:20px;line-height:20px}.el-upload{--el-upload-dragger-padding-horizontal:40px;--el-upload-dragger-padding-vertical:10px}.el-upload{display:inline-flex;justify-content:center;align-items:center;cursor:pointer;outline:0}.el-upload__tip{font-size:12px;color:var(--el-text-color-regular);margin-top:7px}.el-upload iframe{position:absolute;z-index:-1;top:0;left:0;opacity:0}.el-upload--picture-card{--el-upload-picture-card-size:148px;background-color:var(--el-fill-color-lighter);border:1px dashed var(--el-border-color-darker);border-radius:6px;box-sizing:border-box;width:var(--el-upload-picture-card-size);height:var(--el-upload-picture-card-size);cursor:pointer;vertical-align:top;display:inline-flex;justify-content:center;align-items:center}.el-upload--picture-card>i{font-size:28px;color:var(--el-text-color-secondary)}.el-upload--picture-card:hover{border-color:var(--el-color-primary);color:var(--el-color-primary)}.el-upload.is-drag{display:block}.el-upload:focus{border-color:var(--el-color-primary);color:var(--el-color-primary)}.el-upload:focus .el-upload-dragger{border-color:var(--el-color-primary)}.el-upload-dragger{padding:var(--el-upload-dragger-padding-horizontal) var(--el-upload-dragger-padding-vertical);background-color:var(--el-fill-color-blank);border:1px dashed var(--el-border-color);border-radius:6px;box-sizing:border-box;text-align:center;cursor:pointer;position:relative;overflow:hidden}.el-upload-dragger .el-icon--upload{font-size:67px;color:var(--el-text-color-placeholder);margin-bottom:16px;line-height:50px}.el-upload-dragger+.el-upload__tip{text-align:center}.el-upload-dragger~.el-upload__files{border-top:var(--el-border);margin-top:7px;padding-top:5px}.el-upload-dragger .el-upload__text{color:var(--el-text-color-regular);font-size:14px;text-align:center}.el-upload-dragger .el-upload__text em{color:var(--el-color-primary);font-style:normal}.el-upload-dragger:hover{border-color:var(--el-color-primary)}.el-upload-dragger.is-dragover{padding:calc(var(--el-upload-dragger-padding-horizontal) - 1px) calc(var(--el-upload-dragger-padding-vertical) - 1px);background-color:var(--el-color-primary-light-9);border:2px dashed var(--el-color-primary)}.el-upload-list{margin:10px 0 0;padding:0;list-style:none;position:relative}.el-upload-list__item{transition:all .5s cubic-bezier(.55,0,.1,1);font-size:14px;color:var(--el-text-color-regular);margin-bottom:5px;position:relative;box-sizing:border-box;border-radius:4px;width:100%}.el-upload-list__item .el-progress{position:absolute;top:20px;width:100%}.el-upload-list__item .el-progress__text{position:absolute;right:0;top:-13px}.el-upload-list__item .el-progress-bar{margin-right:0;padding-right:0}.el-upload-list__item .el-icon--upload-success{color:var(--el-color-success)}.el-upload-list__item .el-icon--close{display:none;position:absolute;right:5px;top:50%;cursor:pointer;opacity:.75;color:var(--el-text-color-regular);transition:opacity var(--el-transition-duration);transform:translateY(-50%)}.el-upload-list__item .el-icon--close:hover{opacity:1;color:var(--el-color-primary)}.el-upload-list__item .el-icon--close-tip{display:none;position:absolute;top:1px;right:5px;font-size:12px;cursor:pointer;opacity:1;color:var(--el-color-primary);font-style:normal}.el-upload-list__item:hover{background-color:var(--el-fill-color-light)}.el-upload-list__item:hover .el-icon--close{display:inline-flex}.el-upload-list__item:hover .el-progress__text{display:none}.el-upload-list__item .el-upload-list__item-info{display:inline-flex;justify-content:center;flex-direction:column;width:calc(100% - 30px);margin-left:4px}.el-upload-list__item.is-success .el-upload-list__item-status-label{display:inline-flex}.el-upload-list__item.is-success .el-upload-list__item-name:focus,.el-upload-list__item.is-success .el-upload-list__item-name:hover{color:var(--el-color-primary);cursor:pointer}.el-upload-list__item.is-success:focus:not(:hover) .el-icon--close-tip{display:inline-block}.el-upload-list__item.is-success:active,.el-upload-list__item.is-success:not(.focusing):focus{outline-width:0}.el-upload-list__item.is-success:active .el-icon--close-tip,.el-upload-list__item.is-success:not(.focusing):focus .el-icon--close-tip{display:none}.el-upload-list__item.is-success:focus .el-upload-list__item-status-label,.el-upload-list__item.is-success:hover .el-upload-list__item-status-label{display:none;opacity:0}.el-upload-list__item-name{color:var(--el-text-color-regular);display:inline-flex;text-align:center;align-items:center;padding:0 4px;transition:color var(--el-transition-duration);font-size:var(--el-font-size-base)}.el-upload-list__item-name .el-icon{margin-right:6px;color:var(--el-text-color-secondary)}.el-upload-list__item-file-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.el-upload-list__item-status-label{position:absolute;right:5px;top:0;line-height:inherit;display:none;height:100%;justify-content:center;align-items:center;transition:opacity var(--el-transition-duration)}.el-upload-list__item-delete{position:absolute;right:10px;top:0;font-size:12px;color:var(--el-text-color-regular);display:none}.el-upload-list__item-delete:hover{color:var(--el-color-primary)}.el-upload-list--picture-card{--el-upload-list-picture-card-size:148px;display:inline-flex;flex-wrap:wrap;margin:0}.el-upload-list--picture-card .el-upload-list__item{overflow:hidden;background-color:var(--el-fill-color-blank);border:1px solid var(--el-border-color);border-radius:6px;box-sizing:border-box;width:var(--el-upload-list-picture-card-size);height:var(--el-upload-list-picture-card-size);margin:0 8px 8px 0;padding:0;display:inline-flex}.el-upload-list--picture-card .el-upload-list__item .el-icon--check,.el-upload-list--picture-card .el-upload-list__item .el-icon--circle-check{color:#fff}.el-upload-list--picture-card .el-upload-list__item .el-icon--close{display:none}.el-upload-list--picture-card .el-upload-list__item:hover .el-upload-list__item-status-label{opacity:0;display:block}.el-upload-list--picture-card .el-upload-list__item:hover .el-progress__text{display:block}.el-upload-list--picture-card .el-upload-list__item .el-upload-list__item-name{display:none}.el-upload-list--picture-card .el-upload-list__item-thumbnail{width:100%;height:100%;-o-object-fit:contain;object-fit:contain}.el-upload-list--picture-card .el-upload-list__item-status-label{right:-15px;top:-6px;width:40px;height:24px;background:var(--el-color-success);text-align:center;transform:rotate(45deg)}.el-upload-list--picture-card .el-upload-list__item-status-label i{font-size:12px;margin-top:11px;transform:rotate(-45deg)}.el-upload-list--picture-card .el-upload-list__item-actions{position:absolute;width:100%;height:100%;left:0;top:0;cursor:default;display:inline-flex;justify-content:center;align-items:center;color:#fff;opacity:0;font-size:20px;background-color:var(--el-overlay-color-lighter);transition:opacity var(--el-transition-duration)}.el-upload-list--picture-card .el-upload-list__item-actions span{display:none;cursor:pointer}.el-upload-list--picture-card .el-upload-list__item-actions span+span{margin-left:1rem}.el-upload-list--picture-card .el-upload-list__item-actions .el-upload-list__item-delete{position:static;font-size:inherit;color:inherit}.el-upload-list--picture-card .el-upload-list__item-actions:hover{opacity:1}.el-upload-list--picture-card .el-upload-list__item-actions:hover span{display:inline-flex}.el-upload-list--picture-card .el-progress{top:50%;left:50%;transform:translate(-50%,-50%);bottom:auto;width:126px}.el-upload-list--picture-card .el-progress .el-progress__text{top:50%}.el-upload-list--picture .el-upload-list__item{overflow:hidden;z-index:0;background-color:var(--el-fill-color-blank);border:1px solid var(--el-border-color);border-radius:6px;box-sizing:border-box;margin-top:10px;padding:10px;display:flex;align-items:center}.el-upload-list--picture .el-upload-list__item .el-icon--check,.el-upload-list--picture .el-upload-list__item .el-icon--circle-check{color:#fff}.el-upload-list--picture .el-upload-list__item:hover .el-upload-list__item-status-label{opacity:0;display:inline-flex}.el-upload-list--picture .el-upload-list__item:hover .el-progress__text{display:block}.el-upload-list--picture .el-upload-list__item.is-success .el-upload-list__item-name i{display:none}.el-upload-list--picture .el-upload-list__item .el-icon--close{top:5px;transform:translateY(0)}.el-upload-list--picture .el-upload-list__item-thumbnail{display:inline-flex;justify-content:center;align-items:center;width:70px;height:70px;-o-object-fit:contain;object-fit:contain;position:relative;z-index:1;background-color:var(--el-color-white)}.el-upload-list--picture .el-upload-list__item-status-label{position:absolute;right:-17px;top:-7px;width:46px;height:26px;background:var(--el-color-success);text-align:center;transform:rotate(45deg)}.el-upload-list--picture .el-upload-list__item-status-label i{font-size:12px;margin-top:12px;transform:rotate(-45deg)}.el-upload-list--picture .el-progress{position:relative;top:-7px}.el-upload-cover{position:absolute;left:0;top:0;width:100%;height:100%;overflow:hidden;z-index:10;cursor:default}.el-upload-cover:after{display:inline-block;content:"";height:100%;vertical-align:middle}.el-upload-cover img{display:block;width:100%;height:100%}.el-upload-cover__label{right:-15px;top:-6px;width:40px;height:24px;background:var(--el-color-success);text-align:center;transform:rotate(45deg)}.el-upload-cover__label i{font-size:12px;margin-top:11px;transform:rotate(-45deg);color:#fff}.el-upload-cover__progress{display:inline-block;vertical-align:middle;position:static;width:243px}.el-upload-cover__progress+.el-upload__inner{opacity:0}.el-upload-cover__content{position:absolute;top:0;left:0;width:100%;height:100%}.el-upload-cover__interact{position:absolute;bottom:0;left:0;width:100%;height:100%;background-color:var(--el-overlay-color-light);text-align:center}.el-upload-cover__interact .btn{display:inline-block;color:#fff;font-size:14px;cursor:pointer;vertical-align:middle;transition:var(--el-transition-md-fade);margin-top:60px}.el-upload-cover__interact .btn i{margin-top:0}.el-upload-cover__interact .btn span{opacity:0;transition:opacity .15s linear}.el-upload-cover__interact .btn:not(:first-child){margin-left:35px}.el-upload-cover__interact .btn:hover{transform:translateY(-13px)}.el-upload-cover__interact .btn:hover span{opacity:1}.el-upload-cover__interact .btn i{color:#fff;display:block;font-size:24px;line-height:inherit;margin:0 auto 5px}.el-upload-cover__title{position:absolute;bottom:0;left:0;background-color:#fff;height:36px;width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:400;text-align:left;padding:0 10px;margin:0;line-height:36px;font-size:14px;color:var(--el-text-color-primary)}.el-upload-cover+.el-upload__inner{opacity:0;position:relative;z-index:1}.el-vl__wrapper{position:relative}.el-vl__wrapper:hover .el-virtual-scrollbar,.el-vl__wrapper.always-on .el-virtual-scrollbar{opacity:1}.el-vl__window{scrollbar-width:none}.el-vl__window::-webkit-scrollbar{display:none}.el-virtual-scrollbar{opacity:0;transition:opacity .34s ease-out}.el-virtual-scrollbar.always-on{opacity:1}.el-vg__wrapper{position:relative}.el-popper{--el-popper-border-radius:var(--el-popover-border-radius, 4px)}.el-popper{position:absolute;border-radius:var(--el-popper-border-radius);padding:5px 11px;z-index:2000;font-size:12px;line-height:20px;min-width:10px;word-wrap:break-word;visibility:visible}.el-popper.is-dark{color:var(--el-bg-color);background:var(--el-text-color-primary);border:1px solid var(--el-text-color-primary)}.el-popper.is-dark .el-popper__arrow:before{border:1px solid var(--el-text-color-primary);background:var(--el-text-color-primary);right:0}.el-popper.is-light{background:var(--el-bg-color-overlay);border:1px solid var(--el-border-color-light)}.el-popper.is-light .el-popper__arrow:before{border:1px solid var(--el-border-color-light);background:var(--el-bg-color-overlay);right:0}.el-popper.is-pure{padding:0}.el-popper__arrow{position:absolute;width:10px;height:10px;z-index:-1}.el-popper__arrow:before{position:absolute;width:10px;height:10px;z-index:-1;content:" ";transform:rotate(45deg);background:var(--el-text-color-primary);box-sizing:border-box}.el-popper[data-popper-placement^=top]>.el-popper__arrow{bottom:-5px}.el-popper[data-popper-placement^=top]>.el-popper__arrow:before{border-bottom-right-radius:2px}.el-popper[data-popper-placement^=bottom]>.el-popper__arrow{top:-5px}.el-popper[data-popper-placement^=bottom]>.el-popper__arrow:before{border-top-left-radius:2px}.el-popper[data-popper-placement^=left]>.el-popper__arrow{right:-5px}.el-popper[data-popper-placement^=left]>.el-popper__arrow:before{border-top-right-radius:2px}.el-popper[data-popper-placement^=right]>.el-popper__arrow{left:-5px}.el-popper[data-popper-placement^=right]>.el-popper__arrow:before{border-bottom-left-radius:2px}.el-popper[data-popper-placement^=top] .el-popper__arrow:before{border-top-color:transparent!important;border-left-color:transparent!important}.el-popper[data-popper-placement^=bottom] .el-popper__arrow:before{border-bottom-color:transparent!important;border-right-color:transparent!important}.el-popper[data-popper-placement^=left] .el-popper__arrow:before{border-left-color:transparent!important;border-bottom-color:transparent!important}.el-popper[data-popper-placement^=right] .el-popper__arrow:before{border-right-color:transparent!important;border-top-color:transparent!important}.el-select-dropdown__item{font-size:var(--el-font-size-base);padding:0 32px 0 20px;position:relative;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--el-text-color-regular);height:34px;line-height:34px;box-sizing:border-box;cursor:pointer}.el-select-dropdown__item.is-disabled{color:var(--el-text-color-placeholder);cursor:not-allowed}.el-select-dropdown__item.hover,.el-select-dropdown__item:hover{background-color:var(--el-fill-color-light)}.el-select-dropdown__item.selected{color:var(--el-color-primary);font-weight:700}.el-statistic{--el-statistic-title-font-weight:400;--el-statistic-title-font-size:var(--el-font-size-extra-small);--el-statistic-title-color:var(--el-text-color-regular);--el-statistic-content-font-weight:400;--el-statistic-content-font-size:var(--el-font-size-extra-large);--el-statistic-content-color:var(--el-text-color-primary)}.el-statistic__head{font-weight:var(--el-statistic-title-font-weight);font-size:var(--el-statistic-title-font-size);color:var(--el-statistic-title-color);line-height:20px;margin-bottom:4px}.el-statistic__content{font-weight:var(--el-statistic-content-font-weight);font-size:var(--el-statistic-content-font-size);color:var(--el-statistic-content-color)}.el-statistic__value{display:inline-block}.el-statistic__prefix{margin-right:4px;display:inline-block}.el-statistic__suffix{margin-left:4px;display:inline-block}.fade-enter-active,.fade-leave-active{transition:opacity .28s}.fade-enter,.fade-leave-active{opacity:0}.fade-transform--move,.fade-transform-leave-active,.fade-transform-enter-active{transition:all .5s}.fade-transform-enter{opacity:0;transform:translate(-30px)}.fade-transform-leave-to{opacity:0;transform:translate(30px)}.breadcrumb-enter-active,.breadcrumb-leave-active{transition:all .5s}.breadcrumb-enter,.breadcrumb-leave-active{opacity:0;transform:translate(20px)}.breadcrumb-move{transition:all .5s}.breadcrumb-leave-active{position:absolute}.el-breadcrumb__inner,.el-breadcrumb__inner a{font-weight:400!important}.el-upload input[type=file]{display:none!important}.el-upload__input{display:none}.cell .el-tag{margin-right:0}.small-padding .cell{padding-left:5px;padding-right:5px}.fixed-width .el-button--mini{padding:7px 10px;width:60px}.status-col .cell{padding:0 10px;text-align:center}.status-col .cell .el-tag{margin-right:0}.el-dialog{transform:none;left:0;position:relative;margin:0 auto}.upload-container .el-upload{width:100%}.upload-container .el-upload .el-upload-dragger{width:100%;height:200px}.el-dropdown-menu a{display:block}.el-range-editor.el-input__inner{display:inline-flex!important}.el-range-separator{box-sizing:content-box}.el-menu--collapse>div>.el-submenu>.el-submenu__title .el-submenu__icon-arrow{display:none}.el-dropdown .el-dropdown-link{color:var(--el-color-primary)!important}#app .main-container{height:100%;transition:margin-left .28s;margin-left:200px;position:relative}#app .sidebarHide{margin-left:0!important}#app .sidebar-container{-webkit-transition:width .28s;transition:width .28s;width:200px!important;background-color:#304156;height:100%;position:fixed;font-size:0px;top:0;bottom:0;left:0;z-index:1001;overflow:hidden;-webkit-box-shadow:2px 0 6px rgba(0,21,41,.35);box-shadow:2px 0 6px #00152959}#app .sidebar-container .horizontal-collapse-transition{transition:0s width ease-in-out,0s padding-left ease-in-out,0s padding-right ease-in-out}#app .sidebar-container .scrollbar-wrapper{overflow-x:hidden!important}#app .sidebar-container .el-scrollbar__bar.is-vertical{right:0}#app .sidebar-container .el-scrollbar{height:100%}#app .sidebar-container.has-logo .el-scrollbar{height:calc(100% - 50px)}#app .sidebar-container .is-horizontal{display:none}#app .sidebar-container a{display:inline-block;width:100%;overflow:hidden}#app .sidebar-container .svg-icon{margin-right:16px}#app .sidebar-container .el-menu{border:none;height:100%;width:100%!important}#app .sidebar-container .el-menu-item,#app .sidebar-container .menu-title{overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important}#app .sidebar-container .el-menu-item .el-menu-tooltip__trigger{display:inline-block!important}#app .sidebar-container .sub-menu-title-noDropdown:hover,#app .sidebar-container .el-sub-menu__title:hover{background-color:#0000000f!important}#app .sidebar-container .theme-dark .is-active>.el-sub-menu__title{color:#f4f4f5!important}#app .sidebar-container .nest-menu .el-sub-menu>.el-sub-menu__title,#app .sidebar-container .el-sub-menu .el-menu-item{min-width:200px!important}#app .sidebar-container .nest-menu .el-sub-menu>.el-sub-menu__title:hover,#app .sidebar-container .el-sub-menu .el-menu-item:hover{background-color:#0000000f!important}#app .sidebar-container .theme-dark .nest-menu .el-sub-menu>.el-sub-menu__title,#app .sidebar-container .theme-dark .el-sub-menu .el-menu-item{background-color:#1f2d3d!important}#app .sidebar-container .theme-dark .nest-menu .el-sub-menu>.el-sub-menu__title:hover,#app .sidebar-container .theme-dark .el-sub-menu .el-menu-item:hover{background-color:#001528!important}#app .hideSidebar .sidebar-container{width:54px!important}#app .hideSidebar .main-container{margin-left:54px}#app .hideSidebar .sub-menu-title-noDropdown{padding:0!important;position:relative}#app .hideSidebar .sub-menu-title-noDropdown .el-tooltip{padding:0!important}#app .hideSidebar .sub-menu-title-noDropdown .el-tooltip .svg-icon{margin-left:20px}#app .hideSidebar .el-sub-menu{overflow:hidden}#app .hideSidebar .el-sub-menu>.el-sub-menu__title{padding:0!important}#app .hideSidebar .el-sub-menu>.el-sub-menu__title .svg-icon{margin-left:20px}#app .hideSidebar .el-menu--collapse .el-sub-menu>.el-sub-menu__title>span{height:0;width:0;overflow:hidden;visibility:hidden;display:inline-block}#app .hideSidebar .el-menu--collapse .el-sub-menu>.el-sub-menu__title>i{height:0;width:0;overflow:hidden;visibility:hidden;display:inline-block}#app .el-menu--collapse .el-menu .el-sub-menu{min-width:200px!important}#app .mobile .main-container{margin-left:0}#app .mobile .sidebar-container{transition:transform .28s;width:200px!important}#app .mobile.hideSidebar .sidebar-container{pointer-events:none;transition-duration:.3s;transform:translate3d(-200px,0,0)}#app .withoutAnimation .main-container,#app .withoutAnimation .sidebar-container{transition:none}.el-menu--vertical>.el-menu .svg-icon{margin-right:16px}.el-menu--vertical .nest-menu .el-sub-menu>.el-sub-menu__title:hover,.el-menu--vertical .el-menu-item:hover{background-color:#0000000f!important}.el-menu--vertical>.el-menu--popup{max-height:100vh;overflow-y:auto}.el-menu--vertical>.el-menu--popup::-webkit-scrollbar-track-piece{background:#d3dce6}.el-menu--vertical>.el-menu--popup::-webkit-scrollbar{width:6px}.el-menu--vertical>.el-menu--popup::-webkit-scrollbar-thumb{background:#99a9bf;border-radius:20px}:export{menuColor:#bfcbd9;menuLightColor:rgba(0,0,0,.7);menuColorActive:#f4f4f5;menuBackground:#304156;menuLightBackground:#ffffff;subMenuBackground:#1f2d3d;subMenuHover:#001528;sideBarWidth:200px;logoTitleColor:#ffffff;logoLightTitleColor:#001529;primaryColor:#409EFF;successColor:#67C23A;dangerColor:#F56C6C;infoColor:#909399;warningColor:#E6A23C}.blue-btn{background:#324157}.blue-btn:hover{color:#324157}.blue-btn:hover:before,.blue-btn:hover:after{background:#324157}.light-blue-btn{background:#3a71a8}.light-blue-btn:hover{color:#3a71a8}.light-blue-btn:hover:before,.light-blue-btn:hover:after{background:#3a71a8}.red-btn{background:#c03639}.red-btn:hover{color:#c03639}.red-btn:hover:before,.red-btn:hover:after{background:#c03639}.pink-btn{background:#e65d6e}.pink-btn:hover{color:#e65d6e}.pink-btn:hover:before,.pink-btn:hover:after{background:#e65d6e}.green-btn{background:#30b08f}.green-btn:hover{color:#30b08f}.green-btn:hover:before,.green-btn:hover:after{background:#30b08f}.tiffany-btn{background:#4ab7bd}.tiffany-btn:hover{color:#4ab7bd}.tiffany-btn:hover:before,.tiffany-btn:hover:after{background:#4ab7bd}.yellow-btn{background:#fec171}.yellow-btn:hover{color:#fec171}.yellow-btn:hover:before,.yellow-btn:hover:after{background:#fec171}.pan-btn{font-size:14px;color:#fff;padding:14px 36px;border-radius:8px;border:none;outline:none;transition:.6s ease all;position:relative;display:inline-block}.pan-btn:hover{background:#fff}.pan-btn:hover:before,.pan-btn:hover:after{width:100%;transition:.6s ease all}.pan-btn:before,.pan-btn:after{content:"";position:absolute;top:0;right:0;height:2px;width:0;transition:.4s ease all}.pan-btn:after{right:inherit;top:inherit;left:0;bottom:0}.custom-button{display:inline-block;line-height:1;white-space:nowrap;cursor:pointer;background:#fff;color:#fff;-webkit-appearance:none;text-align:center;box-sizing:border-box;outline:0;margin:0;padding:10px 15px;font-size:14px;border-radius:4px}.pt5{padding-top:5px}.pr5{padding-right:5px}.pb5{padding-bottom:5px}.mt5{margin-top:5px}.mr5{margin-right:5px}.mb5{margin-bottom:5px}.mb8{margin-bottom:8px}.ml5{margin-left:5px}.mt10{margin-top:10px}.mr10{margin-right:10px}.mb10{margin-bottom:10px}.ml10{margin-left:10px}.mt20{margin-top:20px}.mr20{margin-right:20px}.mb20{margin-bottom:20px}.ml20{margin-left:20px}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.flex-hc{display:flex;align-items:center}.el-form .el-form-item__label{font-weight:700}.el-dialog:not(.is-fullscreen){margin-top:6vh!important}.el-dialog.scrollbar .el-dialog__body{overflow:auto;overflow-x:hidden;max-height:70vh;padding:10px 20px 0}.el-table .el-table__header-wrapper th,.el-table .el-table__fixed-header-wrapper th{word-break:break-word;background-color:#f8f8f9!important;color:#515a6e;height:40px!important;font-size:13px}.el-table .el-table__body-wrapper .el-button [class*=el-icon-]+span{margin-left:1px}.form-header{font-size:15px;color:#6379bb;border-bottom:1px solid #ddd;margin:8px 10px 25px;padding-bottom:5px}.pagination-container{position:relative;height:25px;margin-bottom:10px;margin-top:15px;padding:10px 20px!important}.el-dialog .pagination-container{position:static!important}.tree-border{margin-top:5px;border:1px solid #e5e6e7;background:#fff none;border-radius:4px;width:100%}.pagination-container .el-pagination{right:0;position:absolute}@media (max-width: 768px){.pagination-container .el-pagination>.el-pagination__jump{display:none!important}.pagination-container .el-pagination>.el-pagination__sizes{display:none!important}}.el-table .fixed-width .el-button--small{padding-left:0;padding-right:0;width:inherit}.el-table .el-dropdown-link{cursor:pointer;color:#409eff;margin-left:10px}.el-table .el-dropdown,.el-icon-arrow-down{font-size:12px}.el-tree-node__content>.el-checkbox{margin-right:8px}.list-group-striped>.list-group-item{border-left:0;border-right:0;border-radius:0;padding-left:0;padding-right:0}.list-group{padding-left:0;list-style:none}.list-group-item{border-bottom:1px solid #e7eaec;border-top:1px solid #e7eaec;margin-bottom:-1px;padding:11px 0;font-size:13px}.pull-right{float:right!important}.el-card__header{padding:14px 15px 7px!important;min-height:40px}.el-card__body{padding:15px 20px 20px!important}.card-box{padding-right:15px;padding-left:15px;margin-bottom:10px}.el-button--cyan.is-active,.el-button--cyan:active{background:#20b2aa;border-color:#20b2aa;color:#fff}.el-button--cyan:focus,.el-button--cyan:hover{background:#48d1cc;border-color:#48d1cc;color:#fff}.el-button--cyan{background-color:#20b2aa;border-color:#20b2aa;color:#fff}.text-navy{color:#1ab394}.text-primary{color:inherit}.text-success{color:#1c84c6}.text-info{color:#23c6c8}.text-warning{color:#f8ac59}.text-danger{color:#ed5565}.text-muted{color:#888}.img-circle{border-radius:50%}.img-lg{width:120px;height:120px}.avatar-upload-preview{position:absolute;top:50%;transform:translate(50%,-50%);width:200px;height:200px;border-radius:50%;box-shadow:0 0 4px #ccc;overflow:hidden}.sortable-ghost{opacity:.8;color:#fff!important;background:#42b983!important}.top-right-btn{margin-left:auto}body{height:100%;margin:0;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Arial,sans-serif}label{font-weight:700}html{height:100%;box-sizing:border-box}#app{height:100%}*,*:before,*:after{box-sizing:inherit}.no-padding{padding:0!important}.padding-content{padding:4px 0}a:focus,a:active{outline:none}a,a:focus,a:hover{cursor:pointer;color:inherit;text-decoration:none}div:focus{outline:none}.fr{float:right}.fl{float:left}.pr-5{padding-right:5px}.pl-5{padding-left:5px}.block{display:block}.pointer{cursor:pointer}.inlineBlock{display:block}.clearfix:after{visibility:hidden;display:block;font-size:0;content:" ";clear:both;height:0}aside{background:#eef1f6;padding:8px 24px;margin-bottom:20px;border-radius:2px;display:block;line-height:32px;font-size:16px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;color:#2c3e50;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}aside a{color:#337ab7;cursor:pointer}aside a:hover{color:#20a0ff}.app-container{padding:20px}.components-container{margin:30px 50px;position:relative}.pagination-container{margin-top:30px}.text-center{text-align:center}.sub-navbar{height:50px;line-height:50px;position:relative;width:100%;text-align:right;padding-right:20px;transition:.6s ease position;background:linear-gradient(90deg,#20b6f9,#20b6f9 0%,#2178f1 100%,#2178f1)}.sub-navbar .subtitle{font-size:20px;color:#fff}.sub-navbar.draft,.sub-navbar.deleted{background:#d0d0d0}.link-type,.link-type:focus{color:#337ab7;cursor:pointer}.link-type:hover,.link-type:focus:hover{color:#20a0ff}.filter-container{padding-bottom:10px}.filter-container .filter-item{display:inline-block;vertical-align:middle;margin-bottom:10px}.lf-graph{position:relative;z-index:0;width:100%;height:100%;background:#fff;-webkit-user-select:none;user-select:none}.lf-element-text{cursor:text}.lf-text-disabled{pointer-events:none}.lf-text-draggable{cursor:move}*:focus{outline:none}.lf-node-anchor{cursor:crosshair}.lf-node-anchor-hover{visibility:hidden}.lf-anchor:hover .lf-node-anchor-hover{visibility:visible}.lf-edge.pointer-none{pointer-events:none}.lf-edge-append{cursor:pointer}.lf-edge-animation{stroke-dashoffset:100%;animation:lf_animate_dash 5s linear infinite}@keyframes lf_animate_dash{to{stroke-dashoffset:0}}.lf-node-not-allow{cursor:not-allowed}.lf-polyline-append-ns-resize{cursor:ns-resize}.lf-polyline-append-ew-resize{cursor:ew-resize}.lf-dragging,.lf-dragging .lf-element-text{cursor:move}.lf-draggable{cursor:default}.lf-bezier-adjust-anchor{cursor:pointer}.lf-background,.lf-grid{position:absolute;top:0;right:0;bottom:0;left:0;z-index:-1}.lf-background-area{width:100%;height:100%}.lf-html-overlay{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;overflow:hidden;-webkit-user-select:none;user-select:none;pointer-events:none}.lf-html-overlay__transform>*{pointer-events:all}.lf-text-editable{pointer-events:all}.lf-text-input{position:absolute;box-sizing:border-box;min-width:100px;min-height:20px;padding:5px;line-height:1.2;white-space:pre;text-align:center;background:#fff;border:1px solid #edefed;border-radius:3px;outline:none;transform:translate(-50%,-50%);resize:none}.lf-get-text-height{display:inline-block;box-sizing:border-box;word-break:break-all;border:1px solid transparent}.lf-node-text-auto-wrap{display:flex;align-items:center;justify-content:center;box-sizing:border-box}.lf-node-text-auto-wrap-content{width:100%;line-height:1.2;text-align:center;word-break:break-all;background:transparent}.lf-node-text-ellipsis-content{width:100%;line-height:1.2;white-space:nowrap;text-align:center;background:transparent}.lf-node-text-ellipsis-content>div{overflow:hidden;text-overflow:ellipsis}.lf-tool-overlay{position:absolute;top:0;right:0;bottom:0;left:0;z-index:2;overflow:hidden;pointer-events:none}.lf-tool-overlay>*{pointer-events:all}.modification-overlay{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;overflow:hidden;pointer-events:none}.modification-overlay>*{pointer-events:all}.lf-outline,.lf-snapline{pointer-events:none}.lf-keyboard-tips{float:right}.lf-node-select-decorate{position:absolute;border:1px dashed #343435;transform:translate(-50%,-50%);pointer-events:none}.lf-multiple-select{position:absolute;border:2px dashed #187dffcc;box-shadow:0 0 3px #187dff80;cursor:move}.lf-edge-adjust-point{cursor:move}.lf-rotate-control{cursor:grabbing}.lf-resize-control-nw{cursor:nw-resize}.lf-resize-control-n{cursor:n-resize}.lf-resize-control-ne{cursor:ne-resize}.lf-resize-control-e{cursor:e-resize}.lf-resize-control-se{cursor:se-resize}.lf-resize-control-s{cursor:s-resize}.lf-resize-control-sw{cursor:sw-resize}.lf-resize-control-w{cursor:w-resize}.medium-editor-anchor-preview,.medium-editor-toolbar{font-family:Helvetica Neue,Helvetica,Arial,sans-serif;font-size:16px;z-index:2000}@-webkit-keyframes medium-editor-image-loading{0%{-webkit-transform:scale(0);transform:scale(0)}to{-webkit-transform:scale(1);transform:scale(1)}}@keyframes medium-editor-image-loading{0%{-webkit-transform:scale(0);transform:scale(0)}to{-webkit-transform:scale(1);transform:scale(1)}}@-webkit-keyframes medium-editor-pop-upwards{0%{opacity:0;-webkit-transform:matrix(.97,0,0,1,0,12);transform:matrix(.97,0,0,1,0,12)}20%{opacity:.7;-webkit-transform:matrix(.99,0,0,1,0,2);transform:matrix(.99,0,0,1,0,2)}40%{opacity:1;-webkit-transform:matrix(1,0,0,1,0,-1);transform:matrix(1,0,0,1,0,-1)}to{-webkit-transform:matrix(1,0,0,1,0,0);transform:scale(1)}}@keyframes medium-editor-pop-upwards{0%{opacity:0;-webkit-transform:matrix(.97,0,0,1,0,12);transform:matrix(.97,0,0,1,0,12)}20%{opacity:.7;-webkit-transform:matrix(.99,0,0,1,0,2);transform:matrix(.99,0,0,1,0,2)}40%{opacity:1;-webkit-transform:matrix(1,0,0,1,0,-1);transform:matrix(1,0,0,1,0,-1)}to{-webkit-transform:matrix(1,0,0,1,0,0);transform:scale(1)}}.medium-editor-anchor-preview{word-wrap:break-word;left:0;line-height:1.4;max-width:280px;position:absolute;text-align:center;top:0;visibility:hidden;word-break:break-all}.medium-editor-anchor-preview a{color:#fff;display:inline-block;margin:5px 5px 10px}.medium-editor-placeholder-relative:after,.medium-editor-placeholder:after{content:attr(data-placeholder)!important;font-style:italic;margin:inherit;padding:inherit;white-space:pre}.medium-editor-anchor-preview-active{visibility:visible}.medium-editor-dragover{background:#ddd}.medium-editor-image-loading{-webkit-animation:medium-editor-image-loading 1s ease-in-out infinite;animation:medium-editor-image-loading 1s ease-in-out infinite;background-color:#333;border-radius:100%;display:inline-block;height:40px;width:40px}.medium-editor-placeholder{position:relative}.medium-editor-placeholder:after{left:0;position:absolute;top:0}.medium-editor-placeholder-relative,.medium-editor-placeholder-relative:after{position:relative}.medium-toolbar-arrow-over:before,.medium-toolbar-arrow-under:after{border-style:solid;content:"";display:block;height:0;left:50%;margin-left:-8px;position:absolute;width:0}.medium-toolbar-arrow-under:after{border-width:8px 8px 0}.medium-toolbar-arrow-over:before{border-width:0 8px 8px;top:-8px}.medium-editor-toolbar{left:0;position:absolute;top:0;visibility:hidden}.medium-editor-toolbar ul{margin:0;padding:0}.medium-editor-toolbar li{float:left;list-style:none;margin:0;padding:0}.medium-editor-toolbar li button{cursor:pointer;display:block;font-size:14px;line-height:1.33;margin:0;padding:15px;text-decoration:none}.medium-editor-toolbar li button:focus{outline:0}.medium-editor-toolbar li .medium-editor-action-underline{text-decoration:underline}.medium-editor-toolbar li .medium-editor-action-pre{font-family:Consolas,Liberation Mono,Menlo,Courier,monospace;font-size:12px;font-weight:100;padding:15px 0}.medium-editor-toolbar-active{visibility:visible}.medium-editor-sticky-toolbar{position:fixed;top:1px}.medium-editor-relative-toolbar{position:relative}.medium-editor-toolbar-active.medium-editor-stalker-toolbar{-webkit-animation:medium-editor-pop-upwards .16s linear forwards;animation:medium-editor-pop-upwards .16s linear forwards}.medium-editor-action-bold{font-weight:bolder}.medium-editor-action-italic{font-style:italic}.medium-editor-toolbar-form{display:none}.medium-editor-toolbar-form a,.medium-editor-toolbar-form input{font-family:Helvetica Neue,Helvetica,Arial,sans-serif}.medium-editor-toolbar-form .medium-editor-toolbar-form-row{line-height:14px;margin-left:5px;padding-bottom:5px}.medium-editor-toolbar-form .medium-editor-toolbar-input,.medium-editor-toolbar-form label{border:none;box-sizing:border-box;display:inline-block;font-size:14px;margin:0;padding:6px;width:316px}.medium-editor-toolbar-form .medium-editor-toolbar-input:focus,.medium-editor-toolbar-form label:focus{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:none;box-shadow:none;outline:0}.medium-editor-toolbar-form a{display:inline-block;font-size:24px;font-weight:bolder;margin:0 10px;text-decoration:none}.medium-editor-toolbar-form-active{display:block}.medium-editor-toolbar-actions:after{clear:both;content:"";display:table}.medium-editor-element{word-wrap:break-word;min-height:30px}.medium-editor-element img{max-width:100%}.medium-editor-element sub{vertical-align:sub}.medium-editor-element sup{vertical-align:super}.medium-editor-hidden{display:none}.medium-toolbar-arrow-under:after{border-color:#000 transparent transparent;top:40px}.medium-toolbar-arrow-over:before{border-color:transparent transparent #000}.medium-editor-toolbar{background-color:#000;border:none;border-radius:50px}.medium-editor-toolbar li button{background-color:transparent;border:none;box-sizing:border-box;color:#ccc;height:40px;min-width:40px;padding:5px 12px;-webkit-transition:background-color .2s ease-in,color .2s ease-in;transition:background-color .2s ease-in,color .2s ease-in}.medium-editor-toolbar li .medium-editor-button-active,.medium-editor-toolbar li button:hover{background-color:#000;color:#a2d7c7}.medium-editor-toolbar li .medium-editor-button-first{border-bottom-left-radius:50px;border-top-left-radius:50px;padding-left:24px}.medium-editor-toolbar li .medium-editor-button-last{border-bottom-right-radius:50px;border-right:none;border-top-right-radius:50px;padding-right:24px}.medium-editor-toolbar-form{background:#000;border-radius:50px;color:#ccc;overflow:hidden}.medium-editor-toolbar-form .medium-editor-toolbar-input{background:#000;box-sizing:border-box;color:#ccc;height:40px;padding-left:16px;width:220px}.medium-editor-toolbar-form .medium-editor-toolbar-input::-webkit-input-placeholder{color:#f8f5f3;color:#f7f4f2cc}.medium-editor-toolbar-form .medium-editor-toolbar-input:-moz-placeholder,.medium-editor-toolbar-form .medium-editor-toolbar-input::-moz-placeholder{color:#f8f5f3;color:#f7f4f2cc}.medium-editor-toolbar-form .medium-editor-toolbar-input:-ms-input-placeholder{color:#f8f5f3;color:#f7f4f2cc}.medium-editor-toolbar-form a{color:#ccc;-webkit-transform:translateY(2px);transform:translateY(2px)}.medium-editor-toolbar-form .medium-editor-toolbar-close{margin-right:16px}.medium-editor-toolbar-anchor-preview{background:#000;border-radius:50px;padding:5px 12px}.medium-editor-anchor-preview a{color:#ccc;text-decoration:none}.medium-editor-toolbar-actions button,.medium-editor-toolbar-actions li{border-radius:50px}.picker_wrapper.no_alpha .picker_alpha{display:none}.picker_wrapper.no_editor .picker_editor{opacity:0;position:absolute;z-index:-1}.picker_wrapper.no_cancel .picker_cancel{display:none}.layout_default.picker_wrapper{align-items:stretch;display:flex;flex-flow:row wrap;font-size:10px;justify-content:space-between;padding:.5em;width:25em}.layout_default.picker_wrapper button,.layout_default.picker_wrapper input{font-size:1rem}.layout_default.picker_wrapper>*{margin:.5em}.layout_default.picker_wrapper:before{content:"";display:block;height:0;order:1;width:100%}.layout_default .picker_selector,.layout_default .picker_slider{padding:1em}.layout_default .picker_hue{width:100%}.layout_default .picker_sl{flex:1 1 auto}.layout_default .picker_sl:before{content:"";display:block;padding-bottom:100%}.layout_default .picker_editor{order:1;width:6.5rem}.layout_default .picker_editor input{height:100%;width:100%}.layout_default .picker_sample{flex:1 1 auto;order:1}.layout_default .picker_cancel,.layout_default .picker_done{order:1}.picker_wrapper{background:#f2f2f2;box-shadow:0 0 0 1px silver;box-sizing:border-box;color:#444;cursor:default;font-family:sans-serif;pointer-events:auto}.picker_wrapper:focus{outline:none}.picker_wrapper button,.picker_wrapper input{border:none;box-shadow:0 0 0 1px silver;box-sizing:border-box;outline:none}.picker_wrapper button:active,.picker_wrapper button:focus,.picker_wrapper input:active,.picker_wrapper input:focus{box-shadow:0 0 2px 1px #1e90ff}.picker_wrapper button{background-color:#f5f5f5;background-image:linear-gradient(0deg,#dcdcdc,transparent);cursor:pointer;padding:.4em .6em}.picker_wrapper button:active{background-image:linear-gradient(0deg,transparent,#dcdcdc)}.picker_wrapper button:hover{background-color:#fff}.picker_selector{background:currentColor;border:2px solid #fff;border-radius:100%;box-shadow:0 0 3px 1px #67b9ff;cursor:pointer;display:block;position:absolute;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);z-index:1}.picker_slider .picker_selector{border-radius:2px}.picker_hue{background-image:linear-gradient(90deg,red,#ff0,#0f0,#0ff,#00f,#f0f,red)}.picker_hue,.picker_sl{box-shadow:0 0 0 1px silver;position:relative}.picker_sl{background-image:linear-gradient(180deg,#fff,#fff0 50%),linear-gradient(0deg,#000,transparent 50%),linear-gradient(90deg,gray,#80808000)}.picker_alpha,.picker_sample{background:linear-gradient(45deg,#d3d3d3 25%,transparent 0,transparent 75%,#d3d3d3 0) 0 0/2em 2em,linear-gradient(45deg,#d3d3d3 25%,#fff 0,#fff 75%,#d3d3d3 0) 1em 1em/2em 2em;box-shadow:0 0 0 1px silver;position:relative}.picker_alpha .picker_selector,.picker_sample .picker_selector{background:none}.picker_editor input{font-family:monospace;padding:.2em .4em}.picker_sample:before{background:currentColor;content:"";display:block;height:100%;position:absolute;width:100%}.picker_arrow{position:absolute;z-index:-1}.picker_wrapper.popup{margin:1.5em;position:absolute;z-index:2}.picker_wrapper.popup,.picker_wrapper.popup .picker_arrow:after,.picker_wrapper.popup .picker_arrow:before{background:#f2f2f2;box-shadow:0 0 10px 1px #0006}.picker_wrapper.popup .picker_arrow{height:3em;margin:0;width:3em}.picker_wrapper.popup .picker_arrow:after,.picker_wrapper.popup .picker_arrow:before{content:"";display:block;left:0;position:absolute;top:0;z-index:-99}.picker_wrapper.popup .picker_arrow:before{height:100%;-webkit-transform:skew(45deg);transform:skew(45deg);-webkit-transform-origin:0 100%;transform-origin:0 100%;width:100%}.picker_wrapper.popup .picker_arrow:after{box-shadow:none;height:150%;width:150%}.popup.popup_top{bottom:100%;left:0}.popup.popup_top .picker_arrow{bottom:0;left:0;-webkit-transform:rotate(-90deg);transform:rotate(-90deg)}.popup.popup_bottom{left:0;top:100%}.popup.popup_bottom .picker_arrow{left:0;top:0;-webkit-transform:rotate(90deg) scaleY(-1);transform:rotate(90deg) scaleY(-1)}.popup.popup_left{right:100%;top:0}.popup.popup_left .picker_arrow{right:0;top:0;-webkit-transform:scaleX(-1);transform:scaleX(-1)}.popup.popup_right{left:100%;top:0}.popup.popup_right .picker_arrow{left:0;top:0}.lf-label-overlay{height:0;overflow:visible;width:0}.lf-label-overlay .lf-label-editor{background:#fff;border-radius:5px;padding:4px}.lf-label-overlay .lf-label-editor-container{align-items:center;display:flex;justify-content:center;overflow:visible;position:absolute;text-align:center}.lf-label-overlay .lf-label-editor-container p{margin:0}.lf-label-overlay .lf-label-editor-dragging{cursor:move}.lf-label-overlay .lf-label-editor-editing{cursor:text;outline:2px solid #275dc5}.lf-label-overlay .lf-label-editor-hover{outline:2px dashed #acacac}.lf-label-overlay .lf-label-editor-clip{overflow:hidden;text-overflow:clip;white-space:nowrap;width:100px}.lf-label-overlay .lf-label-editor-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:100px}.lf-label-overlay .lf-label-editor-wrap{overflow-wrap:break-word;white-space:normal}.lf-label-overlay .lf-label-editor-nowrap{overflow:visible;white-space:nowrap}.lf-control{background:#fffc;border-radius:5px;box-shadow:0 1px 4px #0000004d;display:flex;margin:5px;padding:0 15px;position:absolute;right:10px;top:0}.lf-control-item{align-items:center;cursor:pointer;display:flex;flex-direction:column;justify-content:center;padding:5px 10px}.lf-control-item:hover{background:#efefef}.lf-control-item.disabled{filter:opacity(.5);pointer-events:none}.lf-control-item.disabled:hover{background:#fff}.lf-control-item.disabled i{pointer-events:none}.lf-control-item i{background-size:cover;display:inline-block;height:20px;vertical-align:middle;width:20px}.lf-control-text{font-size:12px;margin-top:5px}.lf-control-zoomOut{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAFMElEQVR4Xu3dsQ3CQBBFQei/KEoDXQG2xAv9h3yDnb0nh7xffgQIXAq82RAgcC0gEK+DwI2AQDwPAgLxBgg0AV+Q5mZqREAgI4e2ZhMQSHMzNSIgkJFDW7MJCKS5mRoREMjIoa3ZBATS3EyNCAhk5NDWbAICaW6mRgQEMnJoazYBgTQ3UyMCAhk5tDWbgECam6kRAYGMHNqaTUAgzc3UiIBARg5tzSYgkOZmakRAICOHtmYTEEhzMzUiIJCRQ1uzCQikuZkaERDIyKGt2QQE0txMjQgIZOTQ1mwCAmlupkYEBDJyaGs2AYE0N1MjAgIZObQ1m4BAmpupEQGBjBzamk1AIM3N1IiAQEYObc0mIJDmZmpEQCAjh7ZmExBIczM1IiCQkUNbswkIpLmZGhEQyMihrdkEBNLcTI0ICGTk0NZsAgJpbqZGBAQycmhrNgGBNDdTIwICGTm0NZuAQJqbqREBgYwc2ppNQCDNzdSIgEBGDm3NJiCQ5mZqREAgI4e2ZhMQSHMzNSIgkJFDW7MJCKS5mRoREMjIoa3ZBATS3EyNCAhk5NDWbAICaW6mRgQEMnJoazYBgTQ3UyMCAhk5tDWbgECam6kRAYGMHNqaTUAgzc3UiIBARg5tzSYgkOZmakRAICOHtmYTEEhzMzUiIJCRQ1uzCQikuZkaERDIyKGt2QQE0txMjQgIZOTQ1mwCAmlupkYEBDJyaGs2AYE0N1MjAgIZObQ1m4BAmpupEQGBjBzamk3gBPJpo6YIPF/gBPJ9/po2JNAEBNLcTI0ICGTk0NZsAgJpbqZGBAQycmhrNgGBNDdTIwICGTm0NZuAQJqbqREBgYwc2ppNQCDNzdSIgEBGDm3NJiCQ5mZqREAgI4e2ZhMQSHMzNSIgkJFDW7MJCKS5mRoREMjIoa3ZBATS3EyNCAhk5NDWbAICaW6mRgQEMnJoazYBgTQ3UyMCAhk5tDWbgECam6kRAYGMHNqaTUAgzc3UiIBARg5tzSYgkOZmakRAICOHtmYTEEhzMzUiIJCRQ1uzCQikuZkaETiB+AOdkWNb83+BE4gfAQIXAgLxNAjcCAjE8yAgEG+AQBPwBWlupkYEBDJyaGs2AYE0N1MjAgIZObQ1m4BAmpupEQGBjBzamk1AIM3N1IiAQEYObc0mIJDmZmpEQCAjh7ZmExBIczM1IiCQkUNbswkIpLmZGhEQyMihrdkEBNLcTI0ICGTk0NZsAgJpbqZGBAQycmhrNgGBNDdTIwICGTm0NZuAQJqbqREBgYwc2ppNQCDNzdSIgEBGDm3NJiCQ5mZqREAgI4e2ZhMQSHMzNSIgkJFDW7MJCKS5mRoREMjIoa3ZBATS3EyNCAhk5NDWbAICaW6mRgQEMnJoazYBgTQ3UyMCAhk5tDWbgECam6kRAYGMHNqaTUAgzc3UiIBARg5tzSYgkOZmakRAICOHtmYTEEhzMzUiIJCRQ1uzCQikuZkaERDIyKGt2QQE0txMjQgIZOTQ1mwCAmlupkYEBDJyaGs2AYE0N1MjAgIZObQ1m4BAmpupEQGBjBzamk1AIM3N1IiAQEYObc0mIJDmZmpEQCAjh7ZmExBIczM1IiCQkUNbswkIpLmZGhEQyMihrdkEBNLcTI0ICGTk0NZsAgJpbqZGBAQycmhrNgGBNDdTIwICGTm0NZuAQJqbqREBgYwc2ppNQCDNzdSIgEBGDm3NJiCQ5mZqREAgI4e2ZhMQSHMzNSIgkJFDW7MJCKS5mRoREMjIoa3ZBATS3EyNCPwAiAUiJ9hUCCUAAAAASUVORK5CYII=)}.lf-control-zoomIn{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAGr0lEQVR4Xu3dsW3cABQEUaoIN+F6DDfpJlSFm1ARMi53JAywwse7nHP8s5iYb49faeDP8zy/SuAXWK93+P2F5zzyHwNvrKQGBJLq3MME0m4gkNbnnCaQdgKBtD7nNIG0Ewik9TmnCaSdQCCtzzlNIO0EAml9zmkCaScQSOtzThNIO4FAWp9zmkDaCQTS+pzTBNJOIJDW55wmkHYCgbQ+5zSBtBMIpPU5pwmknUAgrc85TSDtBAJpfc5pAmknEEjrc04TSDuBQFqfc5pA2gkE0vqc0wTSTiCQ1uecJpB2AoG0Puc0gbQTCKT1OacJpJ1AIK3POU0g7QQCaX3OaQJpJxBI63NOE0g7gUBan3OaQNoJBNL6nNME0k4gkNbnnCaQdgKBtD7nNIG0Ewik9TmnCaSdQCCtzzlNIO0EAml9zmkCaScQSOtzThNIO4FAWp9zmkDaCQTS+pzTBNJOIJDW55wmkHYCgbQ+5zSBtBMIpPU5pwmknUAgrc85TSDtBAJpfc5pAmknEEjrc04TSDuBQFqfc5pA2gkE0vqc0wTSTiCQ1uecJpB2AoG0Puc0gbQTCKT1OacJpJ1AIK3POU0g7QQCaX3OaQJpJxBI63NOE0g7gUBan3OaQNoJBNL6nNME0k4gkNbnnCaQdgKBtD7nNIG0Ewik9TmnCaSdQCCtzzlNIO0EAml9zmkCaScQSOtzThNIO4FAWp9zmkDaCQTS+pzTBNJOIJDW55wmkHYCgbQ+5zSBtBMIpPU5pwmknUAgrc85TSDtBAJpfc5pAmknEEjrc04TSDuBQFqfc5pA2gkE0vqc0wTSTiCQ1uecJpB2AoG0Puc0gbQTCKT1OacJpJ1AIK3POU0g7QQCaX3OaQJpJxBI63NOE0g7gUBan3OaQNoJBNL6nNME0k4gkNbnnCaQdgKBtD7nNIG0Ewik9TmnCaSdQCCtzzlNIO0EAml9zmkCaScQSOtzThNIO4FAWp9zmkDaCQTS+pzTBNJOIJDW55z2CuR9/hZ3XuDn8zw/xud8PM/zd/wOZ/7+FcjnmWscwkBsQCCxULhbBgRya0/XxAYEEguFu2VAILf2dE1sQCCxULhbBgRya0/XxAYEEguFu2VAILf2dE1sQCCxULhbBgRya0/XxAYEEguFu2VAILf2dE1sQCCxULhbBgRya0/XxAYEEguFu2VAILf2dE1sQCCxULhbBgRya0/XxAYEEguFu2VAILf2dE1sQCCxULhbBgRya0/XxAYEEguFu2VAILf2dE1sQCCxULhbBgRya0/XxAYEEguFu2VAILf2dE1sQCCxULhbBgRya0/XxAYEEguFu2XgFYgP6HSb+oBO5/JbkF6B+HUGfIKtc/ktSAJpZxBI63NOE0g7gUBan3OaQNoJBNL6nNME0k4gkNbnnCaQdgKBtD7nNIG0Ewik9TmnCaSdQCCtzzlNIO0EAml9zmkCaScQSOtzThNIO4FAWp9zmkDaCQTS+pzTBNJOIJDW55wmkHYCgbQ+5zSBtBMIpPU5pwmknUAgrc85TSDtBAJpfc5pAmknEEjrc04TSDuBQFqfc5pA2gkE0vqc0wTSTiCQ1uecJpB2AoG0Puc0gbQTCKT1OacJpJ1AIK3POU0g7QQCaX3OaQJpJxBI63NOE0g7gUBan3OaQNoJBNL6nNME0k4gkNbnnCaQdgKBtD7nNIG0Ewik9TmnCaSdQCCtzzlNIO0EAml9zmkCaScQSOtzThNIO4FAWp9zmkDaCQTS+pzTBNJOIJDW55wmkHYCgbQ+5zSBtBMIpPU5pwmknUAgrc85TSDtBAJpfc5pAmknEEjrc04TSDuBQFqfc5pA2gkE0vqc0wTSTiCQ1uecJpB2AoG0Puc0gbQTCKT1OacJpJ1AIK3POU0g7QQCaX3OaQJpJxBI63NOE0g7gUBan3OaQNoJBNL6nNME0k4gkNbnnCaQdgKBtD7nNIG0Ewik9TmnCaSdQCCtzzlNIO0EAml9zmkCaScQSOtzThNIO4FAWp9zmkDaCQTS+pzTBNJOIJDW55wmkHYCgbQ+5zSBtBMIpPU5pwmknUAgrc85TSDtBAJpfc5pAmknEEjrc04TSDuBQFqfc5pA2gkE0vqc0wTSTiCQ1uecJpB2AoG0Puc0gbQTCKT1OacJpJ1AIK3POU0g7QQCaX3OaQJpJxBI63NOE0g7gUBan3OaQNoJBNL6nNME0k4gkNbnnCaQdgKBtD7nNIG0Ewik9TmnCaSdQCCtzzlNIO0EAml9zmkCaScQSOtzThNIO4FAWp9z2j95TXI4rwKcnQAAAABJRU5ErkJggg==)}.lf-control-fit{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAKq0lEQVR4Xu2d8bEURRCHmwg0AyECIAIlAs1AjQCJQI1AiUCJQIhAiQCMAIhAjUDr9+rOOinf4+bedO/8er+t4h9qrnfm6/7ezO3tzt4JDghA4FoCd2ADAQhcTwBBqA4I3EAAQSgPCCAINQCBywgwg1zGjU/thMBKgtyNiM8j4osT9s8j4kVEvN1JPvY2zOVzvoogjyPixxuq45uIeLq36mk+XoucryCIiv+HM4rhyQckOiMETRYhYJPzrQXRFPtmIGn3WG4N0FqzqVXOtxbk3L8kx1Qzi6xZ9CO9ssr51oL8FhGfDtDVF/bTL/EDH6XpIgSscu4myMuI+GyRRNONywiMCrJpzhHksiTzqcsJIMgAOytYA+Oi6fUErHLODEIpVxNAkAHiVrAGxkVTZpApNYAgUzBaBbHKOUssq9pq0VkEGUijFayBcdGUJdaUGkCQKRitgljlnCWWVW216CyCDKTRCtbAuGjKEmtKDSDIFIxWQaxyzhLLqrZadBZBBtJoBWtgXDRliTWlBhBkCkarIFY5Z4llVVstOosgA2m0gjUwLpqyxJpSAwgyBaNVEKucs8Syqq0WnUWQgTRawRoYF01ZYk2pAQSZgtEqiFXOWWJZ1VaLziLIQBqtYA2Mi6YssabUAIJMwWgVxCrnLLGsaqtFZ+0F0SsIHhSl4quI0F6t5x6bbiJ2bidpdyOBUUH06oufi5i+Prxu49/Tnc4gKlbtsv5xUWcuOQ2CXEJtrc+MClLd+z8jQntAX0l5FOS7iPi2uicXnA9BLoC22EdWF+SI6/uI+E6CaK/bXxeDeF13EMQkUTd000UQDeGRBNFU8qUJdwQxSVQTQZ5JkD8W/95xyhpBEKSSwFsJ8nflGW95LgS5JcAFPu60xLr6ko4gC1TNjrpgJ4iu/d43SdDVlQWTvtLN/yegtxnrDbcOx0vNIKPvjNtyYA8jQkJz+BLQj9CvTLr/RILoh0FNe6vPIs8iQj9mcvgTcLhy+rt+Ajn+UCir1elVJdHLOyWHfuXk8CegP8qqN93WtOIhOVRvr09vNVGn9Z/6t4Iofx1mtueF9+KsmKzOfVKt6a3Fq4iiq6THerv6Y7z13bydk8/YGhBAkAZJZAh5BBAkjy2RGxBAkAZJZAh5BBAkjy2RGxBAkAZJZAh5BBAkjy2RGxBAkAZJZAh5BBAkjy2RGxBAkAZJZAh5BBAkjy2RGxBAkAZJZAh5BPYmiO5a/uiwk0se1b6R9ViEbiLdzTM5exBEOzdqzy/dNbrypnhOWulOV931qic8tfNh26OzIJJBO0XykFVu+eq5Du1E2PJZna6CaCn1U+Eew7kluH50Lbm+7rj06iiIZo43LKfKrdIMcq/bTNJREG0IULU7fXkVLn5CzSTaWKPN0U0Qfd/Q0opjOwJaalW9riB9lN0E0dJq5H0j6YB3eAJd1dJSq8XRSRBdxv2lRVb8B/HosOGG/Ug6CeK0Y5994XxgAE8PGxLaj7OTIFZ7vtpXzs0DaLPJeCdB+P6xjnVtvod0EsRpl/p1SjmvJy1qq8UgDjkeFYSd4s+X45J3WLaorRaDQJDzK/3ClghyIbiVPsYMkpcNBMljWxYZQfJQI0ge27LICJKHGkHy2JZFRpA81AiSx7YsMoLkoUaQPLZlkREkDzWC5LEti4wgeagRJI9tWWQEyUONIHlsyyIjSB5qBMljWxYZQfJQI0ge27LICJKHGkHy2JZFRpA81AiSx7YsMoLkoUaQPLZlkREkDzWC5LEti4wgeagRJI9tWWQEyUONIHlsyyIjSB5qBMljWxYZQfJQI0ge27LICJKHGkHy2JZFRpA81AiSx7YsMoLkoUaQPLZlkREkDzWC5LEti4wgeagRJI9tWWQEyUONIHlsyyIjSB5qBMljWxYZQfJQI0ge27LICJKHGkHy2JZFRpA81AiSx7YsMoLkoUaQPLZlkREkDzWC5LEti4wgeagRJI9tWWQEyUONIHlsyyIjSB5qBMljWxYZQfJQI0ge27LICJKHGkHy2JZFRpA81AiSx7YsMoLkoUaQPLZlkREkDzWC5LEti4wgeagRJI9tWWQEyUONIHlsyyIjSB5qBMljWxYZQfJQI0ge27LICJKHGkHy2JZFRpA81AiSx7YsMoLkoUaQPLZlkREkDzWC5LEti4wgeagRJI9tWWQEyUONIHlsyyKPClLWsZ2e6E6HcbcYxCERbyPikw5JaTCGdxFxt8E4opMgv0XEpx2S0mAMLyPiswbjaCXIjxHxuENSGozhaUR802AcrQT5IiJ+6ZCUBmN4FBGa0e2PTkssJYPvIduXZJvvH0LZTZCvIuKn7Wtk1z34OiJ+7kKgmyDKy+uIuN8lQWbj+D0iHpj1+cbudhTk48NS66NOiTIYy1+HS7t/GvT17C52FESD118xTfPMJGeXwq0aaubQ8lazd6ujqyBKkmYSXfr9slXG1hvMs8Ml3VYzxxFzZ0GOY9QvurqXSJeBWXbNEUxXqnQZV1x15bDtsQdBTpOnpZdmlha/8m5QlZJCM0W7pdR1LPcmyAY1xSmdCSCIc/boezoBBElHzAmcCSCIc/boezoBBElHzAmcCSCIc/boezoBBElHzAmcCSCIc/boezoBBElHzAmcCSCIc/boezoBBElHzAmcCSCIc/boezqBU0F0E59uDdd9/as8FfY8IvRPt1Rz9COgetNd1vq3wqGbMPUckert6vb9oyASQs9yryLG+7AkiZ51bvnMwQqVUdwH/TFWva0ixvvDlyiqt9cSRJ39dWE5jp2X2eo0hz8ByaGVysqHJHkkQbTB1w8r9/Skbw/39CyCSU5Gu6lVyqvRD23U/okEUWdXXVq9z+X7w1NsG/HitBMIOO2A+VKCOO2K3mbP1wmF5hrCag9lBHEtM99+2wmiK0Mumxkwg/iKcey5kyDvNIPo6pDL1jgIgiCVBJ5JEO3wocu8DgeCOGTp5j46zSBXl3l1XPIOui1ShSBbUJ97ThdBrq6Ynt5qoh9udAlu5e8jCDK3WLeItrog2mNYvw1e7VD/fzcr6uf/qt9FJOXIewURZIuSnnvOUUG0i2PV6xTUt/+8+Gfru3lHYSHI3GLdIppVzhFkixLZ9zkRZCD/VrAGxkXT6wlY5ZwZhFKuJoAgA8StYA2Mi6bMIFNqAEGmYLQKYpVzllhWtdWiswgykEYrWAPjoilLrCk1gCBTMFoFsco5Syyr2mrRWQQZSKMVrIFx0ZQl1pQaQJApGK2CWOWcJZZVbbXoLIIMpNEK1sC4aMoSa0oNIMgUjFZBrHLOEsuqtlp0FkEG0mgFa2BcNGWJNaUGEGQKRqsgVjlniWVVWy06iyADabSCNTAumrLEmlIDCDIFo1UQq5yzxLKqrRadRZCBNFrBGhgXTVliTakBBJmC0SqIVc5ZYlnVVovOIshAGkdhvVj4xY8Dw951U6ucbz2DjL4f8clh/+BdV5j54K1yvrUgdyPizUDC70XE24H2NF2PgFXOtxZE6Tv3Lwqzx3rFfmmPbHK+giDnSIIcl5biup/7kCRL5HwVQZRGTb169YL+6dC7E/WF7jnLqnWr/JY9Wz7nKwlyS9Z8HALzCSDIfKZEbEQAQRolk6HMJ4Ag85kSsREBBGmUTIYynwCCzGdKxEYE/gHBVkPiBWPScAAAAABJRU5ErkJggg==)}.lf-control-undo{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAARvklEQVR4Xu2dB6wvRRXGf4iKgCJg7xJBUFDBLlZiBWyxUBXFACoWxEIRxAJYg4iFiGDEiLyHGkURxYiNKEZBRYiIYq+oWJ+o0Yjmu+yfd999/7Jlzu7s7neSm/uSN3Pm7Hf2u7Mzc+acDbAYASMwE4ENjI0RMAKzETBB/HYYgTkImCB+PYyACeJ3wAjUQ8AzSD3c3GskCJggI3G0H7MeAiZIPdzcayQImCDxjr498Bbg2fFDeYTUCJggqRFdV99zgJOANcCdYoey9ggETJAIVOFWwAeBXQv1vzJBYoCO1mqCpEf4ycAHgC2XqTZB0uPcikYTJB3MmwPvAfaZotIESYdzq5pMkDRwPwY4A7jNDHUmSBqcW9digjSDfFPgRODABWpMkGY4d9bbBKkP/UOBVSUX3yZIfZw77WmCVIf/JsCbgEOAsviZINVxzqJHWQdnYWwGRtwPWA1sXdEWE6QiYLk0N0HKeeJGwGuBI4ANy3VZp5UJUgO0HLqYIIu9sD1wFqDfdcUEqYtcx/1MkNkOuAFwGPB64MYN/WSCNASwq+4myHTktcbQWkNrjhRigqRAsQMdJsi6oAuPFwNvBjZJ6A8TJCGYbaoyQdairWhbnWvofCO1mCCpEW1JnwlyHdDPK07ENwvC3QQJAjZa7dgJsjIsPQpvEyQK2WC9YybItLD0KLhNkChkg/WOkSAKS38vsGcwtsvVmyAtgp1yqLERZFFYekpsTZAoNFvUOxaCKCz9ncVivEV4rx/KM0gXqCcYcwwEqRKWngDSqSpMkChkg/UOmSAKS1e6nZdUCEuPgtsEiUI2WO9QCVI3LD0KbhMkCtlgvUMjiMLSFVyoIMM6YelRcJsgUcgG6x0SQVKEpUfBbYJEIRusdwgEUVj6kcWFJs0gOYoJkqNXStjUd4KkDksvAVmtJiZILdi679RXgsjulxbJEzbuHsaFFpggCyHKs0EfCRIZlh7lJRMkCtlgvX0jyAHA24GbBeOSWr0JkhrRlvT1hSBK6anUnoql6qOYIH30WgYnzGVg2xt494ps6WX65dTGBMnJGxVsyXkGUfkAlRHQvY2+iwnSUw/mShAVnlEBGt34G4KYID31Ym4E0eJbYenP7Smes8w2QXrq0JwI8ijgw4CKXg5NTJCeejQHguig723AwT3ZNKjjahOkDmoZ9OmaIA8qMhjeNQMsIk0wQSLRDdTdFUGU6/YNwKsABRsOXUyQnnq4C4Lcp8iWvm1PMatjtglSB7UM+rRJkBsWYemvAXINS49yiQkShWyw3rYIorD0jwGaPcYoJkhPvR5NEOk/FDgeUBKFsYoJ0lPPRxKkj2HpUW40QaKQDdYbRZCDgBOAmwbb3xf1JkhfPLXCztQE6XtYepQbTZAoZIP1piTIvsC7gC2Cbe6jehOkj15LFNqhsPRTgaf1FIO2zP5KWwM1GOdfwD+n/FwD/AK4svi5qsEYveradAZ5CnAacMtePbWNbYrA35eR5fvABcAXmyrNsX9dgigsXbf89svxoWxTJwho5vkq8AXgfOA7wLWdWJJw0DoEeSxw+kDD0hNCO3pVfwXOLYJRz+krGlUIorB0bd2+sK8Pa7s7Q+CPwEeBM4tZ5n+dWVJx4LIEUY0NXWa6S0X9bm4EViKgHT1lqFH6pj/kDs8igigsXWEiLx9JWHru/hqSfVqzqFakarj8LtcHm0eQMYal5+qnIdulreX3AW/MkSizCKKQdF1oshiBNhHQQbPeu6vbHHTeWCsJoktMZ404LD0Xv4zZDu1+HVtkt/lP10BMCKJrr1pnHAds1LVRHt8IAD8GXgmc3SUaIshWxQ7VQ7o0xGMbgRkIfLkodXFZFwiJIM8udhM26cIAj2kESiLw6qIeTMnmaZpNPrF8uSkNntYSi4BivvYBfh07zFrtyxfp+rdqir8Z6EPVprYw8jh5IfA3YH/g422YNW2bVwkWdNKppG4WI5ArAkpuHp7DedY5iHa1DgdeB+g03WIEckTga4AqAayJMm5RqEnOtcejMLHefiGg3S1VHvt9hNmLCKIxleTttcARwIYRRlinEWiIwM+AXQD9TiplCDIZ8H5FbL/WKBYjkBsCmkE0kyQ9L6lCEAGi5G9vAg5JdJ89N5BtT78R+AvwMOB7qR6jKkEm4+p+yCpA5ycWI5ATAppJFBXykxRG1SWIxt4UOBE4MIUh1mEEEiLwS+DBwG+a6mxCkMnY+u7TuYmSxlmMQC4I/BDYGdB139qSgiAafHPgFGCP2pYMv2PuebG0Q6kcZ5sBylpz8wG45LvAw5uck6QiyARLJY9TnixnV1z37dKUf+cevnC3LdaZdwT0c0/gicW/+/I4Ckl5el1jUxNEdqi2+QeA3esaNcB+yko4pIQXOwBPKH4e3QN/KcZQedwqSwRBJkYoqZxqng9hqq4M7IoOQ87Nq0+yxwNPAnYDbtEUrID+upmondeLquqOJIhsUc1zpQtSDfQxy5AJstyvirpQAOFRGc6YCpHXzKezktISTZCJIUo2p1ro2hoeo4yFIBPfiijPK4iS01nZeUVwY+l3sC2CyCDVQl890jD6sRFk8gIqEvxgQLcBtTbNQVTcSdUISkmbBJFBY00OMVaCTF5CbRsrAYMSg3RddUyfWIonLHU+0jZBJoCNLb3Q2Aky8bs+t5SfV/FSXYpSW+1VxoCuCCLbVDddi7mji3+XsbevbUyQtZ6bXMZTgji9A12Jtqk/t2jwLgkyse2+ReDj3RcZ2+P/7+tBYSTkDyhyXmmnswvR2dR2RTWtmePnQJCJcQqj16WsIYoJMt2rit/7DKA/kl2IEpQcOW/gnAgiO5UoQjtd2vEakvgTa7Y3lY9NawKFsLQtyjCvGWzm2UhuBBFASjk0uZTVNmBR4w0t1CQCJ229HhCheIFOpdtVsvapkiNBJobq9F2n8F19o6b0lWeQxWjqXVQqH2X6bFNUkPR2gH6vJzkTRMZq/1wp8Z/TJmIBY5kg5UDVDpe2gfcs1zxZK80gmkl6R5CJwfo+VYRwX8tN+xOr2rusKrltRgnr0FBnNFqTrCO5zyDLjdVlHlUiqh3bX81HSVt7F6sanPpDqOwkuo/SlhwKvKPPBJnYvjfwnp5dyvInVvXXXDPI51vMnnMFcI8hEETPoEWVarU/rjrunfTwJ1Y92F8PHFOva61eujNy4fKeffrEmvbE2hZUOWEt5nMWzyD1vKNQlEsApcBtQ7TOVZj+9dJ3guhB+lDbxASp/3rvVNwEbCPtrSruav1zzcTcIRBEz6LneGlxwJhjbRMTpD5B1POtwKuaqSjde537IkMhyOTptylydD2wNBztNPQapBnOSnmrop5tHBp/tUgVtGTx0AiiZ9Jhk4IelZE+l9omnkGaEUS9X1Q3M0nFoa8tEo0snawPkSATPHKqbWKCVHxLpzTXHzuVN9AOZrQ8Ffjk0Ami51PyAFXJUrWsNhZ5sxxngqR5pZ9fVGROo222FoU3aU076Blk+eN3XdvEBEnzSusPnhJSR4ccXT7ZWh7yJ9ZKl2ihpwsy+svQ9nObIGkI0uaOlj7lrmr7RUkHU31NXdQ2MUHq+2tlT6Vw/WkLf+SepesWYySIANfJu9KihpcRLrzrbd50BJEmxWip7EakKHDx0LESZAJsW7VNPIOkfZVVAnC9yNu0QyxlPHnC2AkiTFXb5GRAUcJRYoKkRfZewKVpVa6n7efKjWCCrMXlycWlLN07SS0mSGpEr8uMGOGr5ZbexARZ13HKH6t70bsm9qcJkhjQIpdaqeyIDYbe0QSZjp4W7/rGTVXbxARp8JbO6KobgLrqECl7mSCz4b0D8CFglwQe8C5WAhBXqNBluYWpQxsOe4wJshhBBckp3FoJzuqKZ5C6yM3up5qPWkhHyskmSDl4m9Y2MUHK4Vy1lS44bVS1U4X2q0yQCmgBhwFvqdZlqbU/sWqAVqLLd4AdS7Sr2+Q8E6Q6dHVqm3gGqY5zmR7RJ+rfNEHKuGH9NkomoGx8Ki1WpsaFCVIP50W9lJp2n0WNGvz/lSZIA/SA+xSZyTWrzBMTpBnOs3qfCLwsRvWS1qtNkDTonlDU35ulzZkV0+C8UotmcVWqChMTJB2082qbeAZJh/NyTbrfo9uiUbLGBEkLrVIO6XT3BSvUmiBpcZ5oU5DpC2NUL2n9jQkSg+7K2iYmSAzOZwD7xqhe0voDEyQOXV3KUpJtFYQxQWJw/hTwpBjVS1ovNkEC0S1UKzL4+A4LVcY/YXcjRB8UfskEace5N51V4qud4Qc7igreKBlHlJxtgkRBa73RCNwN+FHwIKebIMEIW30YAroBupT9MFCOMkEC0bXqUASUe1lZMyPlqSZIJLzWHYnA14EHRw4AbGuCBCNs9SEI6Cr0n4OTx/0b2NgECfGflQYjoDrqq4PHUJXde5sgwShbfQgCZwbnMZPRZwFO2hDiPiuNRECfV78Lvmor+1+huDrPIJGutO4IBA4uQngidC/Xqbs+l5og0TBbf2oELgZU7yVS/gJsoQFMkEiYrTs1Ag8Avpla6RR9qyZXeU2QFtD2EMkQUKI4JYyLlgOA93sGiYbZ+lMioNLe30ipcI4uFelRqiZ/YrUEuIdpjoDql6s6WLT8ANhuMog/saLhtv4UCDwT+EgKRSV0KBHEcSZICaTcJAsEdO7xkxZqgehh/wfcsaiku/TwnkGyeAdsxBwElGFfBTXbkC+srH1ogrQBu8eoi4Dum+veeVui/AFKBHG9mCBtQe9xqiKgbJU6FNR15TbkGuCWgDLGmyBtIO4xaiOwWUGObWprqN7xVOCgld08g1QH0j3iETgX2C1+mHVGEBnXu+NugrTsBQ83F4EbFGHmz2gZp9OB/aeNaYK07AkPNxMBvYvasYrMlDht8P8CWwFKML6emCB+Y3NBQGsAxUC1LadMyaXsRXrbXvB4MxFQcdSPdrDmkEG6d771rNlDDTyD+M3tEoFbA+cBO3VkxEmLCvCYIB15xsOieiqfAG7XERa/L2aPNfPGN0E68s6Ih90QOKqo8VimvmMUVPsVmwJz9ZsgUfBb7zQEdM9C6w3dDOxSLiwbOm+CdOmm8YytmUJZQo5uMXRkFrra1t1exXHKwG+ClEHJbZog8EhAW7htho3Ms1cFV19Z9oFMkLJIuV1VBHYpdoiUhT0XuQK4P6DAxFJigpSCyY1KInDjIhvIIcCOJfu01ezvRV17Xb4qLSZIaajccA4CuiuuEJE9gFtkiJRuCu4OfLaqbSZIVcTcXggoHH1n4BEFMe6cOSzHAMfWsdEEqYPauPpsDtyr2Pm5N/BwYIceQfDpJpVwx0wQpdBPUYT+WuCCHr0ws0zVAd6WgJIkaIbQb6W/UThIX+XbgGrWzz0tn/dwYyaItvre1lfP2+6FCFxZVKD608KWcxqYIE3Qc99cEfhpcVL+26YGmiBNEXT/3BAQKRQIOfUCVFVjTZCqiLl9zgjoc0qFPfV5lURMkCQwWkkGCFxVLMhLxViVtdcEKYuU2+WMwI8LcvwqtZEmSGpEra9tBC4BHg002q2aZbQJ0rY7PV5KBL5S3GX/R0qly3WZIFHIWm80AqcBB0YPYoJEI2z9qRFQgU1dlz0nteJp+kyQNlD2GKkQUHkClULQjlUrYoK0ArMHaYiAMq4fDryzoZ7K3U2QypC5Q8sIKBBUWdeTnm+UfQYTpCxSbtc2AspbpYBS5evtTEyQzqD3wDMQ0PUB5cs9Avhb1yiZIF17wOMvR+Ci4o7Ot3KBxQTJxRPjtUP3xXXrT+l4dPCXlZggWbljVMZoZ0rri7dOq+yUCxImSC6eGI8dPwNU0endwB9zf2wTJHcPDcM+HeytBs4EtM7ojZggvXFV7wy9GvhM8Rl1fu+sLww2QfrqufzsVuYQHeopHESEuCw/E6tbZIJUx8w9QAGDPyyutl5e7D59bYjAmCBD9Gr9Z/prsXDW5SP9aBGt338AlClEd731o8+nUciYCTIKB/shmyFggjTDz70HjoAJMnAH+/GaIWCCNMPPvQeOgAkycAf78ZohYII0w8+9B46ACTJwB/vxmiFggjTDz70HjsD/AT5bM+fiPLaOAAAAAElFTkSuQmCC)}.lf-control-redo{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAASJElEQVR4Xu2dCdB2YxnHf0hZvkTC2CpZxpKZZB0iDcZUQ5LJEkNkouxEWUJSyJatIssIqca0aLSQyiStTCotQzXaJs1EQiqq+X/ffXzv937v85ztXs+5rpl3vm/mPee6r/t/n9/7POec676uJTAzBUyBiQosYdqYAqbAZAUMELs6TIEpChggdnmYAgaIXQOmQDcF7BOkm2521kgUMEBGstA2zW4KGCDddLOzRqKAAVLuQt8AnAj8qdwp5B+5AZL/Gk2K8A/APOAo4Ppyp5F35AZI3uszLToBsqY74CvAgcBfy51OnpEbIHmuS5OoZgKi4/8GvB34UpOT7ZhmChggzXTK8ajZgFQx3gS8G3gsx6BLi8kAKW3FFsY7CRAd8Rdgf+COcqeXR+QGSB7r0CWKaYBU/q4EjgOe7DKAnQMGSLlXQRNANLvfA/sCd5c71XSRGyDptO87clNANM7/gIuBk4Gn+w48pvMNkHJXuw0g1SwfBPYBflzutONGboDE1dvnaF0A0fjPAOcAHwD+4zOgIfoyQMpd1a6AVDP+ObA3oH/NJihggJR7afQFRDP/N3A6cB7w33KlCBe5ARJO29CefQBSxah7kr2A34UOujT/BkhpK7YwXp+AyOtTwEnAZeVK4j9yA8S/prE8+gakivtbwNssjX6BHAZIrMvZ/zihAFGkjwPHANf6D7ssjwZIWes1M9qQgFTjjD6N3gAxQOoUGHUavQFSd3nk+/sYnyAzZ38zcPjY0ugNkHwBqIssNiCKZ3Rp9AZI3WWY7+9TAFKpcTVw9BjS6A2QfAGoiywlIIptFGn0BkjdZZjv71MDImWURn+pe8E4yDR6AyRfAOoiywGQKsbBptEbIHWXYb6/zwkQqfQscC5wxpDS6A2QfAGoiyw3QKp4B5VGb4DUXYb5/j5XQKSY0ujPdBuzik6jN0DyBaAuspwBqWJXGr22+OoepUgzQIpctvlBlwCI4vwn8D7gEvfUqyjFDZCilmuRYEsBpApaZYdUfkjvT4oxA6SYpVos0NIA0QT+ARwL6E18EWaAFLFMcwZZIiDVRFQSVaVRlduVtRkgWS/P1OBKBkQTUxq9imwrSzhbM0CyXZrawEoHpJqg2jWobYOAyc4MkOyWpHFAQwFEE1bjHzUA0g7GrMwAyWo5WgUzJECqiWsPvNLodTOfhRkgWSxDpyCGCIiEUFNSVVVRdZXkZoAkX4LOAQwVEAmiNPrLXRdfvWhMZgZIMul7DzxkQCpxVOlRqSrf761WRwcGSEfhMjhtDIBIZqXRnw+cliKN3gDJ4ErvGMJYAKnkSZJGb4B0vDozOG1sgEhy9TNRX5MPxapGb4BkcKV3DGGMgFRSRUujN0A6Xp0ZnDZmQCS/ikSo56J6L+qpVxAzQILIGsXp2AGpRA6aRm+ARLmWgwxigCyU9QngeEB94b2aAeJVzqjODJDF5faeRm+ARL2mvQ5mgMwt56PAEcBNPtQeGyCrA+sDGwBrA8u5n2WBmT8v8CFuYB+axxqBxyjZvZc0+qECsgywPbAdsLGDQhfU8iWv+KzY/wwIeLPJCvROox8KIEsBWwA7uR+BUcKnQJ+L2wBprt717mtX6zT60gHZE9gbeAMwr7legzjSAGm3jJ3S6EsDZElgR2A/4C3Aiu00GtTRBkj75dQLxY8BJ7h6XbUeSgFE37U1KW2kWa12VuM4wADpvs6N0+hzB2Qt4BTgYOD53fUY5JkGSL9lVc3gC4BTXS3hOb3lCshLXf6/NvIv3U+HwZ5tgPhZ2l+5+9ifzOUuN0D0VUpVwQ/1M/dBezFA/C3vM8AHgbMB/f85ywUQvbA7EXiPe3Hnb+rD9WSA+F9bfYroqag+VeZbakA0/gFuA8ya/uc7aI8GSJjl/Ze7771Im7JSArINcBmweZh5Dt6rARJ2iVUoYp8UgOim+yz3dUrvNcy6KWCAdNOt6VlPAYfFBkT5ULcAmzaN0o6bqIABEu7ieG4TVkxAjnRdUJU1a9ZfAQOkv4azPSzWDSsGIC8EPgfs6n8+o/ZogPhd/jkLQYQGZBXgm8Amfudi3gADxM9lMLUjb0hAXgZ8G9C/Zv4VMED6a1pbjC4UIK8Cvg7oE8QsjAIGSHddVc70XOCMunKmIQB5LXCbvRHvvnoNzzRAGgo16zD1bFdBbN1z1JpvQHYAbrfM21rdfRxggLRTUXtBPup6tqvoXCPzCYjeiN9lnxyNdPdxkAHSXEX1ZlePdr3faGW+ANkIuAd4UavR7eA+ChggzdS7yvVmf7LZ4Yse5QOQdVyDE7sh77IC3c8xQKZrpx7s6sWuYnKdrS8gysD9HqCdf6Xb34HHXQNJtSTWk46czepiTV4dFY1TD/bH+i5gX0B+AGzZN4iI5z8MfBn4BaDKhPrR91P9tSnNrLLi4iumP2zqua6icV6sDyB6InCUlyjCOtFTta+6HtwCYyhmgCy6kuqxri3aKhbnzboCsptPSr3NZoEjCaT3MLc6MDrdnHmOKYQ7A2SBqvpqrD/UKg7n3boAsh5wX4aF2h5y+4o/VcD9g4+FNEAW9FJXKSgVhQtibQFROU/Boce6uZjA0GZ7gbHIhvtcAgwUx5gB0bcC1S9QEbig1haQC90z5aBBNXSuvxpq6Hj1yMCo5BkrIPO3wgIq/hbc2gCyGfBDQIWiU5oe3SnRTA8JtMFlrDY2QFRMQUXe9EdaRd+iWFNABIVKoqTe1/ENV5f3kSjq5D3ImABZrBxPrKVpCojq4n4kVlBzjKPkMtXNujRhDLkNPQZA1Bdd95eLFXSLtRhNANGGp18CakqTwn4N7OFe7qUYP9cxhw7I1JKgsRalCSB68/zGWAHNGkc7End3KSCJQsh22KECovuL811tZm2HTWp1gOjG/N5EEd4IHDTSJ1RNJB8iII3bEjQRyMcxdYB8AXiTj4Fa+vgwcHLLc8Z2+JAA0WamK9y7jayeTE4DRM0vtak9tl2c0buW2HNvM95QAOnUGq2NUH2OnQaIalnt1cd5h3P1ZvRdHc4b4ylDAOQ6l0fVurlmrAWfBIhSSR6IFYQbR/cc2uBi1kyBkgHp3Z65mUT9j5oEiMhW6nAs0yO9VwMqGGzWTIFSAdFeDe3Z0N6N7G0uQLSvXBuIYvUZ10tAFXyI/YmV/eLUBFgaIAJCu/xuLkn4uQDRJNS3I5YdAlwTa7ABjVMSINoXrq/Pxe3cnAuQ+yO2J1Bu1c4DumhjTqUEQHTzfazLuI6pjbexZgOyhcvY9TbAFEdPABsCf4wx2ADHyB2Q53pslKz9bEA+Drwz0oQOAz4RaawhDpMrIHrR916XWKoXgEXbbEC0v3eFCDPSvpKtIowz5CFyBGTOHhslL8JMQHZ0vTxizEfNdFT93ay7AjkBoqRCVUrXRrZom5m6S9f8zJmAqJH6Kc1P7Xykamlt3flsO7FSIBdAantslLxkMwFRhcQYF67aI6jItVk/BVIDosqTSipVXQBtbBqkVYAs70pu1mX39hXBPj36Krjw/JSAtOqx4W/K8T1VQCilXantoe1g4NrQg4zEfwpA9FRK2dbaitC4x0bJ61EBor3eRwSeiApDrwqoOoVZfwViA9K5x0b/qabzUAES4/5DZXqOSTfVwY0cE5ArgeOAoZZxnXhxVIDorbbuQ0KaUkqUWmLmR4EYgHjpseFnumm8CJDVQ9Y2ddPS1yplCdvXK3/rHBoQ7c/R1+7ePTb8TTm+JwHyOuDOwEMrm3OXwGOMzX0oQLz32Ch5YQSItrheHngSys3RW1YzfwqEACRIjw1/U47vSYBcAhwZeGh9SqlUvZk/BXwCoq9R6rGhCvlmMxQQIOq+pNyokKbHu147/4QMthDfvgAJ3mOjED3nDFOAKLNW+0BCmb7TrhzK+Yj99gVETy7VY0NbHMwmKCBAfha4arvyrpR/ZeZXgT6ARO2x4Xfacb0JEOXVrBtwWFVIURULM78KdAFE6SHK2Fa6yKDS0v1Ku9CbAFFlO70LCWVWDC6Msm0BSdZjI8z043gVII8CKwYc7gJA/UXM/CrQFBClop/lUtPH1MPRi9oCRB+7IWtgaSPWaV6iNSczFWgCSBY9NkpeNgESemP9+91fsJJ1yjH2aYDo/kIdwaR98h4bOYrXNCYBotpF85qe0OG484CTOpxnp0xXYBIg2fXYKHkh7Sa93NWbDYi+CShlSL0cs+qxUa7EIEDUA3D9gJO4ATggoP+xup4JSNY9NkpeIAGiWkaqrB7KVM07RZeqUPPJxW8FiLYwH+2+KucS22DiECBqlLlDwBkJwJCpLAFDz9r1fW5vuDJwzQIpIEBCd7HVJqlULaQDyZaFW+0AHd0W2NjKCxClOIfu7KRe6w/HnpyNZwr0VUCA6Fn5mX0d1Zz/epdWH3gYc28K+FVAgKhRpxp2hjTVUVIVPjNToCgFBMgrgZ8GjloPAlQc28wUKEoBAbKUqzaif0OZkuSUEGk3laEUNr9BFKjqYimpbYMgIyx0qncheidiZgoUo0AFiOryhn6ZdzXwjmKUsUBNARakmsh0E312YEX09eolYyl6HFhLcx9JgQoQ9QVRfd7QdiBwfehBzL8p4EuBCpAlAfUnDJn2rphVYkY1ssxMgSIUqABRsDHuQzTOJsADRahjQY5egZmAqLKeWhSENr2UfGvoQcy/KeBDgZmA6C+7amSFNm3s0ctJ+xQJrbT5763ATEDkLHQJoCrgzwN79o7eHJgCgRWYDcg5EfePW7fbwItr7vsrMBuQjQH1vY5hKi6g8Wz/dAy1bYxOCswGRE5Ut3WrTt7an3SR633X/kw7wxSIoMBcgMRoqDNzatsC90SYqw1hCrRWYC5AVgLUvHHp1t66nfAIsFmEPondorOzRq3AXIBIkJuBvSMqox4lsb7WRZyWDVW6ApMAURkgVSOJadcAh8Qc0MYyBeoUmASIzvsisHudA8+/P991PfLs1tyZAt0UmAaI7gv0KTLtmG6jTj9LafenhnBsPk2BtgrUXfy3JHrjrSorZ7SdjB1vCvhWoA4QvchTflbdcb7jkr+rgMOBZ0M4N5+mQBMFmlz4sZ9ozYz7DuDNgDqympkC0RVoAshargL8stGjWzCgUl+0X/6hROPbsCNWoAkgkkcNcJTImMq0n/0Y4JOpArBxx6lAU0D0Vv1+YMPEMmnX48Gu8WjiUGz4MSjQFBBpsR3wnQxEURqMuihZ8YcMFmPoIbQBRFp8GtgnE1G+CxxqOxMzWY2BhtEWkNVcHd9VMtJDKSrqxW5beDNalKGE0hYQzXsb4K6I2b5Ntb4d0P4S67jUVDE7rlaBLoDIqV7gXVHrPc0B2ql4I3Ad8GCaEGzUoSjQFRDNX80jD8pciHuBz7qHC3dnHquFl6ECfQDRdH4EbJ7hvCaFpJ2LehKnfih6Aan7lqcLit9CjaxAX0DWAPSX+eWR4/Y53G+A37o2yo+7fx8dSA7YuoDKyvY1fWW9ra+TEs/vC4jm/Aq3p3zVEgWwmBspcDxwYaMjB3aQD0Akiaoy6quLukiZDU8BA8TDmm7pqrcv58GXuchLAQPE03qoUefXgOd78mdu8lDAAPG4Dur/cSuwvEef5iqtAgaIZ/21n11vtlf27NfcpVHAAAmg+3rAncDaAXyby7gKGCCB9F7dQZJ6H0mg6Y3GrQEScKn16FfpHrsEHMNch1XAAAmr73zvRwLnActEGMuG8KuAAeJXz4neNgI+A2waaTwbxo8CBogfHRt50f52VU88IVG9rUZB2kGLKGCAJLggtndVSjZIMLYN2U4BA6SdXt6Ofp77JDkNsBQVb7J6d2SAeJe0nUO9K1Gp0V3bnWZHR1LAAIkkdN0we7i06nXqDrTfR1XAAIkq9/TBtMFHPdR1E791RnGNORQDJNPVf41rqLObPfFKukIGSFL56wdXXtfRwL6WAFkvVoAjDJAAooZwuZRLWdnPtUWYF2IQ87mYAgZIgReF2jHoq9f+wE72mDjoChogQeUN71zvU3RDv7P70f9j9XkPP7v0Ixgg6dfAawTazajtv9u6lg16W68SOKmaAHmdXAJnBkgC0VMMqReS6wMCRv/XjscXu5+VZvx/hRTBBRhTrSJ039bXTs+41GzfuU0931fZn6BBmnNTIJUCBkgq5W3cIhQwQIpYJgsylQIGSCrlbdwiFDBAilgmCzKVAgZIKuVt3CIUMECKWCYLMpUCBkgq5W3cIhT4P9N9vGSnCJYeAAAAAElFTkSuQmCC)}.lf-menu{background:#fff;border:1px solid #efefee;border-radius:3px;display:none;margin:0 0 0 10px;padding:10px 0;position:absolute;width:150px;z-index:999}.lf-menu>li{cursor:pointer;font-size:12px;line-height:18px;list-style:none;padding:3px 12px;position:relative;transition:all .12s ease-in-out}.lf-menu-item{align-items:center;display:flex;gap:4px;justify-content:flex-start;list-style:none}.lf-menu-item:hover{background:#f3f3f3}.lf-menu-item__disabled{color:#aaa;cursor:not-allowed!important;opacity:.88;pointer-events:inherit!important}.lf-menu-item__disabled:hover{background:#fff}.lf-dndpanel{background:#fffc;border-radius:5px;box-shadow:0 1px 4px #0000004d;margin:5px;padding:15px 5px;position:absolute;z-index:999}.lf-dnd-item{font-size:14px;margin-top:5px;text-align:center}.lf-dnd-item.disabled>div{color:#a8abb2;cursor:not-allowed}.lf-dnd-shape{background-position:50%;background-repeat:no-repeat;cursor:grab;height:36px;margin:0 auto;opacity:.99;width:36px}.lf-selection-select{border:2px dashed #ff8c00;position:absolute}.lf-mini-map{background:#eaedf2;border:1px solid #93a3b4;overflow:hidden;padding:28px 8px 8px;position:absolute}.lf-mini-map .lf-graph{height:100%!important;width:100%!important}.lf-mini-map-graph{overflow:hidden;position:relative}.lf-minimap-viewport{background-color:#30303033;cursor:grab;left:0;position:absolute;top:0}.lf-mini-map-header{background-color:#a6a6a6;background-image:linear-gradient(180deg,#b2b2b2,#9b9b9b);border-bottom:1px solid #999;border-top:1px solid #999;border-color:#bfbfbf #999 #666;border-style:solid;border-width:1px;height:18px;left:0;line-height:18px;padding:0 10px;position:absolute;right:0;text-shadow:0 1px 0 hsla(0,0%,100%,.33);top:0}.lf-mini-map-close{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACL0lEQVRoQ+1YT2vUQBT/vS+Smc3u0ZtfwA+hUKEttIIVbMEWrGAL3UItqKCCCipooS3Ynu29ePfqLcnO5Nov0NsrWbc0myxtJpNmGZw9LZk37/3+vCTzQnD8R47jhycwbQe9A94BSwV8C1kKaL3dO2AtoWWC/8+BMJB9ItwHcM6MX0mqtm1EzOU7Y8bvJFV9k3zGDnSFPAVw76oIb8ZavzIpehnbE+Ilg/J7/8Za3THJVYfAHwB3x4owXsSpemNSuCs6zwEu7rl9AmEgt4hQspkIa5FS76uQCKV8RoxSLDP6pi1p7EAGsCvEBkA7ZbC0EuvBp+tI9IR4yqBSDIE3Iq13qwiQj6lFYEgikOsgvC4VJDyJlfo6CUg3kI9BmLBG67EevDUFn8XXJpBt7km5yox3xcIMfpRo/SN/PZRygRhj17J1JqwmSn2oA96awL926iwD/LEIgJjmo3RwMCQadGaZePh/3H5ejrT+XBd8IwSGJKRcAuPLhHZ6mIkMxlFpjbEUp+qbDfjGCGSJQiEWCfS9CiAmLCZK7VWJvSnG6h4oJu8FnTkm3r+uKDHNReng8CZgVdcbJTBqpxkwfk4EQJiJlTquCq5KnCeQV8npFnL6Jnb6Mer0i8zpo4TThzmnj9OjGXardDBrZqDZbmMmdnukdH6oL3xWOTG1vNh6oxn7AYB2PqtUOWC1GdP4Ya5N8I0ONG0Dv6znHZiW8t6BaSvvHfAONKSAf4w2JGTtNBey0xBAkjSEzwAAAABJRU5ErkJggg==);background-size:contain;cursor:pointer;height:18px;position:absolute;right:2px;top:0;width:18px}.lf-mini-map-no-header{padding-top:8px}.lf-mini-map-no-close-icon .lf-mini-map-close,.lf-mini-map-no-header .lf-mini-map-header{display:none}.lf-resize-control-0{cursor:nw-resize}.lf-resize-control-1{cursor:ne-resize}.lf-resize-control-2{cursor:se-resize}.lf-resize-control-3{cursor:sw-resize}.lf-inner-context{display:none;flex-flow:column wrap;height:90px;position:absolute}.lf-context-item{box-sizing:content-box;height:20px;margin:0 2px 2px 0;padding:2px 3px;width:20px}.lf-context-item:hover{background:#c9d9d880}.lf-context-img{cursor:pointer;height:20px;width:20px}.lf-mindmap_addIcon{margin-top:10px}[data-v-a733d95f] .Tabs{border:0;margin-top:-20px}[data-v-a733d95f] .Tabs .el-tabs__content{display:none}[data-v-a733d95f] .Tabs .el-tabs__item.is-active{margin-left:0;border-top:1px solid var(--el-border-color);margin-top:0}.startForm[data-v-a733d95f]{border:1px solid #e4e7ed;border-top:0;padding:15px}.app-container[data-v-7b451969]{padding:0}.app-container[data-v-7b451969] .Tabs{border:0}.app-container[data-v-7b451969] .Tabs .el-tabs__content{display:none}.app-container[data-v-7b451969] .Tabs .el-tabs__item.is-active{margin-left:0;border-top:1px solid var(--el-border-color);margin-top:0}.app-container .content[data-v-7b451969]{border:1px solid #e4e7ed;border-top:0;padding:15px;margin:0!important}.app-container .el-pagination[data-v-7b451969]{margin-top:10px;justify-content:flex-end}.app-container .el-pagination .el-pagination__rightwrapper[data-v-7b451969]{flex:none}.app-container .head-container .el-tree[data-v-7b451969]{max-height:679px;overflow:auto;margin:-15px;padding-right:10px}.app-container .head-container .el-tree[data-v-7b451969] .el-tree-node{display:table;min-width:100%}.dialogSelect[data-v-7da9ec1e]{display:none}.placeholder[data-v-7da9ec1e]{color:#828f9e;font-size:12px}[data-v-7da9ec1e] .listenerItem{display:inline-block;width:100%}[data-v-7da9ec1e] .listenerItem .el-form-item__label{float:none;display:inline-block;text-align:left}[data-v-7da9ec1e] .listenerItem .el-form-item__content{margin-left:0!important}[data-v-7da9ec1e] .Tabs{border:0;margin-top:-20px}[data-v-7da9ec1e] .Tabs .el-tabs__content{padding:0}[data-v-7da9ec1e] .Tabs .el-tabs__item.is-active{margin-left:0;border-top:1px solid var(--el-border-color);margin-top:0}.tabPane[data-v-7da9ec1e]{padding:15px;border:1px solid #e4e7ed;border-top:0}.betweenForm[data-v-7da9ec1e]{border-top:0}[data-v-7da9ec1e] .permissionItem{display:inline-block;width:100%}[data-v-7da9ec1e] .permissionItem .el-form-item__content{display:block}[data-v-7da9ec1e] .permissionItem .inputGroup{display:flex;align-items:center;margin-bottom:10px}[data-v-7da9ec1e] .permissionItem .inputGroup .Icon{width:14px;height:14px;margin-left:5px;cursor:pointer;color:#fff;background:#94979d;border-radius:50%;padding:2px}[data-v-7da9ec1e] .permissionItem .inputGroup .Icon:hover{background:#f0f2f5;color:#94979d}[data-v-7da9ec1e] .permissionItem .inputGroup .btn{height:18px;margin-left:5px;color:#fff;background:#94979d;border-radius:5px;padding:6px;font-size:10px;border:0}[data-v-7da9ec1e] .permissionItem .inputGroup .btn:hover{background:#f0f2f5;color:#94979d}.placeholder[data-v-8db59d96]{color:#828f9e;font-size:12px}[data-v-d1456424] .Tabs{margin-top:-20px;box-shadow:none;border-bottom:0}[data-v-d1456424] .Tabs .el-tabs__content{display:none}[data-v-d1456424] .Tabs .el-tabs__item.is-active{margin-left:0;border-top:1px solid var(--el-border-color);margin-top:0}.dialogForm[data-v-d1456424]{border-top:0;padding:15px;width:1000px;margin:50px auto}[data-v-d1456424] .listenerItem .el-form-item__label{float:none;display:inline-block;text-align:left}[data-v-d1456424] .listenerItem .el-form-item__content{margin-left:0!important}.el-drawer__container[data-v-2d3b44d4] ::-webkit-scrollbar{display:none}.base-node[data-v-2bf639da]{width:100%;height:80px;box-sizing:border-box;border-radius:5px}.top-section[data-v-2bf639da]{position:relative;font-size:13px;padding:10px;height:25px;display:flex;align-items:center;border-top-left-radius:5px;border-top-right-radius:5px}.delete-btn[data-v-2bf639da]{position:absolute;right:8px;top:50%;transform:translateY(-50%);display:none;color:#000}.base-node:hover .delete-btn[data-v-2bf639da]{display:block}.bottom-section[data-v-2bf639da]{padding:10px;height:100%;font-size:14px}.edit-icon[data-v-2bf639da]{vertical-align:middle;margin:3px}.tooltip-container[data-v-ca4bb98e]{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;width:100%}.tooltip-item[data-v-ca4bb98e]{display:flex;align-items:center;padding:4px 5px;cursor:pointer;border-radius:4px;transition:background-color .2s}.tooltip-item[data-v-ca4bb98e]:hover{background-color:#f0f0f0}.tooltip-icon[data-v-ca4bb98e]{display:flex;align-items:center;justify-content:center;width:32px;height:32px;border:1px solid #ddd;border-radius:50%;margin-right:10px;color:#666}.container{flex:1;width:100%;height:800px}.logo-text{position:absolute;font-weight:700;right:50px;bottom:10px;font-size:15px;color:#333;z-index:1}.flow-name{font-weight:700;padding-left:40px;padding-right:200px;width:500px;max-width:500px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:#333}.steps-container{display:flex;align-items:center;height:35px;justify-content:space-between}.steps{display:flex}.step-item{display:flex;align-items:center;margin-right:100px;cursor:pointer}.step-item i{margin-right:5px}.step-item.active{color:#409eff}.containerView[data-v-644143b5]{width:100%;height:100%}.top-text[data-v-644143b5]{position:absolute;font-weight:700;left:500px;top:2px;border:1px solid #d1e9ff;background-color:#e8f4ff;padding:4px 8px;border-radius:4px;max-width:300px;font-size:15px;color:#333;z-index:1}.log-text[data-v-644143b5]{position:absolute;font-weight:700;right:10px;bottom:10px;font-size:15px;color:#333;z-index:1}[data-v-fd5df4ef] .container{width:100%}[data-v-fd5df4ef] .container .fcDesigner{height:100vh}[data-v-fd5df4ef] .container .fcDesigner .el-aside{padding:0;background:#fff;height:100%}.sub-el-icon,.nav-icon{display:inline-block;font-size:15px;margin-right:12px;position:relative}.svg-icon{width:1em;height:1em;position:relative;fill:currentColor;vertical-align:-2px} diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-vue3-ui/src/main/resources/warm-flow-ui/ico/favicon-FqvijpIH.ico b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-vue3-ui/src/main/resources/warm-flow-ui/ico/favicon-FqvijpIH.ico new file mode 100644 index 0000000..d9c9543 Binary files /dev/null and b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-vue3-ui/src/main/resources/warm-flow-ui/ico/favicon-FqvijpIH.ico differ diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-vue3-ui/src/main/resources/warm-flow-ui/index.html b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-vue3-ui/src/main/resources/warm-flow-ui/index.html new file mode 100644 index 0000000..a70bb59 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-vue3-ui/src/main/resources/warm-flow-ui/index.html @@ -0,0 +1,229 @@ + + + + + + + + + + 流程图 + + + + + + + +
+
+
+
+
+
正在加载设计器中
+
+
+ + + + diff --git a/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-vue3-ui/src/main/resources/warm-flow-ui/js/index-9QJKzKRw.js b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-vue3-ui/src/main/resources/warm-flow-ui/js/index-9QJKzKRw.js new file mode 100644 index 0000000..fb7096b --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-plugin/warm-flow-plugin-ui/warm-flow-plugin-vue3-ui/src/main/resources/warm-flow-ui/js/index-9QJKzKRw.js @@ -0,0 +1,1218 @@ +var dX=Object.defineProperty;var fX=(e,t,n)=>t in e?dX(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var pX=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var Dd=(e,t,n)=>(fX(e,typeof t!="symbol"?t+"":t,n),n);var tct=pX((ja,Va)=>{(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))r(o);new MutationObserver(o=>{for(const i of o)if(i.type==="childList")for(const a of i.addedNodes)a.tagName==="LINK"&&a.rel==="modulepreload"&&r(a)}).observe(document,{childList:!0,subtree:!0});function n(o){const i={};return o.integrity&&(i.integrity=o.integrity),o.referrerPolicy&&(i.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?i.credentials="include":o.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function r(o){if(o.ep)return;o.ep=!0;const i=n(o);fetch(o.href,i)}})();function UC(e,t){const n=Object.create(null),r=e.split(",");for(let o=0;o!!n[o.toLowerCase()]:o=>!!n[o]}const Kr={},lh=[],lr=()=>{},hX=()=>!1,vX=/^on[^a-z]/,Mb=e=>vX.test(e),KC=e=>e.startsWith("onUpdate:"),Ho=Object.assign,YC=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},mX=Object.prototype.hasOwnProperty,kn=(e,t)=>mX.call(e,t),Mt=Array.isArray,sh=e=>Cg(e)==="[object Map]",Tb=e=>Cg(e)==="[object Set]",Af=e=>Cg(e)==="[object Date]",jt=e=>typeof e=="function",Yt=e=>typeof e=="string",xh=e=>typeof e=="symbol",hn=e=>e!==null&&typeof e=="object",Em=e=>(hn(e)||jt(e))&&jt(e.then)&&jt(e.catch),rI=Object.prototype.toString,Cg=e=>rI.call(e),U2=e=>Cg(e).slice(8,-1),Gy=e=>Cg(e)==="[object Object]",GC=e=>Yt(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,K2=UC(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),Ob=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},gX=/-(\w)/g,ml=Ob(e=>e.replace(gX,(t,n)=>n?n.toUpperCase():"")),yX=/\B([A-Z])/g,ad=Ob(e=>e.replace(yX,"-$1").toLowerCase()),Eg=Ob(e=>e.charAt(0).toUpperCase()+e.slice(1)),Y2=Ob(e=>e?`on${Eg(e)}`:""),Sf=(e,t)=>!Object.is(e,t),G2=(e,t)=>{for(let n=0;n{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,value:n})},K8=e=>{const t=parseFloat(e);return isNaN(t)?e:t},_X=e=>{const t=Yt(e)?Number(e):NaN;return isNaN(t)?e:t};let kk;const Y8=()=>kk||(kk=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});function bt(e){if(Mt(e)){const t={};for(let n=0;n{if(n){const r=n.split(wX);r.length>1&&(t[r[0].trim()]=r[1].trim())}}),t}function re(e){let t="";if(Yt(e))t=e;else if(Mt(e))for(let n=0;nCh(n,t))}const Pe=e=>Yt(e)?e:e==null?"":Mt(e)||hn(e)&&(e.toString===rI||!jt(e.toString))?JSON.stringify(e,aI,2):String(e),aI=(e,t)=>t&&t.__v_isRef?aI(e,t.value):sh(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((n,[r,o])=>(n[`${r} =>`]=o,n),{})}:Tb(t)?{[`Set(${t.size})`]:[...t.values()]}:hn(t)&&!Mt(t)&&!Gy(t)?String(t):t;let Ia;class lI{constructor(t=!1){this.detached=t,this._active=!0,this.effects=[],this.cleanups=[],this.parent=Ia,!t&&Ia&&(this.index=(Ia.scopes||(Ia.scopes=[])).push(this)-1)}get active(){return this._active}run(t){if(this._active){const n=Ia;try{return Ia=this,t()}finally{Ia=n}}}on(){Ia=this}off(){Ia=this.parent}stop(t){if(this._active){let n,r;for(n=0,r=this.effects.length;n{const t=new Set(e);return t.w=0,t.n=0,t},sI=e=>(e.w&Uc)>0,uI=e=>(e.n&Uc)>0,MX=({deps:e})=>{if(e.length)for(let t=0;t{const{deps:t}=e;if(t.length){let n=0;for(let r=0;r{(c==="length"||!xh(c)&&c>=s)&&l.push(u)})}else switch(n!==void 0&&l.push(a.get(n)),t){case"add":Mt(e)?GC(n)&&l.push(a.get("length")):(l.push(a.get(hf)),sh(e)&&l.push(a.get(q8)));break;case"delete":Mt(e)||(l.push(a.get(hf)),sh(e)&&l.push(a.get(q8)));break;case"set":sh(e)&&l.push(a.get(hf));break}if(l.length===1)l[0]&&X8(l[0]);else{const s=[];for(const u of l)u&&s.push(...u);X8(JC(s))}}function X8(e,t){const n=Mt(e)?e:[...e];for(const r of n)r.computed&&Tk(r);for(const r of n)r.computed||Tk(r)}function Tk(e,t){(e!==Ul||e.allowRecurse)&&(e.scheduler?e.scheduler():e.run())}function OX(e,t){var n;return(n=Xy.get(e))==null?void 0:n.get(t)}const DX=UC("__proto__,__v_isRef,__isVue"),fI=new Set(Object.getOwnPropertyNames(Symbol).filter(e=>e!=="arguments"&&e!=="caller").map(e=>Symbol[e]).filter(xh)),Ok=NX();function NX(){const e={};return["includes","indexOf","lastIndexOf"].forEach(t=>{e[t]=function(...n){const r=Xn(this);for(let i=0,a=this.length;i{e[t]=function(...n){sv();const r=Xn(this)[t].apply(this,n);return uv(),r}}),e}function FX(e){const t=Xn(this);return wa(t,"has",e),t.hasOwnProperty(e)}class pI{constructor(t=!1,n=!1){this._isReadonly=t,this._shallow=n}get(t,n,r){const o=this._isReadonly,i=this._shallow;if(n==="__v_isReactive")return!o;if(n==="__v_isReadonly")return o;if(n==="__v_isShallow")return i;if(n==="__v_raw"&&r===(o?i?KX:gI:i?mI:vI).get(t))return t;const a=Mt(t);if(!o){if(a&&kn(Ok,n))return Reflect.get(Ok,n,r);if(n==="hasOwnProperty")return FX}const l=Reflect.get(t,n,r);return(xh(n)?fI.has(n):DX(n))||(o||wa(t,"get",n),i)?l:Mn(l)?a&&GC(n)?l:l.value:hn(l)?o?cv(l):mn(l):l}}class hI extends pI{constructor(t=!1){super(!1,t)}set(t,n,r,o){let i=t[n];if(Eh(i)&&Mn(i)&&!Mn(r))return!1;if(!this._shallow&&(!Qy(r)&&!Eh(r)&&(i=Xn(i),r=Xn(r)),!Mt(t)&&Mn(i)&&!Mn(r)))return i.value=r,!0;const a=Mt(t)&&GC(n)?Number(n)e,Db=e=>Reflect.getPrototypeOf(e);function R1(e,t,n=!1,r=!1){e=e.__v_raw;const o=Xn(e),i=Xn(t);n||(Sf(t,i)&&wa(o,"get",t),wa(o,"get",i));const{has:a}=Db(o),l=r?eE:n?rE:Am;if(a.call(o,t))return l(e.get(t));if(a.call(o,i))return l(e.get(i));e!==o&&e.get(t)}function P1(e,t=!1){const n=this.__v_raw,r=Xn(n),o=Xn(e);return t||(Sf(e,o)&&wa(r,"has",e),wa(r,"has",o)),e===o?n.has(e):n.has(e)||n.has(o)}function I1(e,t=!1){return e=e.__v_raw,!t&&wa(Xn(e),"iterate",hf),Reflect.get(e,"size",e)}function Dk(e){e=Xn(e);const t=Xn(this);return Db(t).has.call(t,e)||(t.add(e),Su(t,"add",e,e)),this}function Nk(e,t){t=Xn(t);const n=Xn(this),{has:r,get:o}=Db(n);let i=r.call(n,e);i||(e=Xn(e),i=r.call(n,e));const a=o.call(n,e);return n.set(e,t),i?Sf(t,a)&&Su(n,"set",e,t):Su(n,"add",e,t),this}function Fk(e){const t=Xn(this),{has:n,get:r}=Db(t);let o=n.call(t,e);o||(e=Xn(e),o=n.call(t,e)),r&&r.call(t,e);const i=t.delete(e);return o&&Su(t,"delete",e,void 0),i}function Rk(){const e=Xn(this),t=e.size!==0,n=e.clear();return t&&Su(e,"clear",void 0,void 0),n}function L1(e,t){return function(r,o){const i=this,a=i.__v_raw,l=Xn(a),s=t?eE:e?rE:Am;return!e&&wa(l,"iterate",hf),a.forEach((u,c)=>r.call(o,s(u),s(c),i))}}function B1(e,t,n){return function(...r){const o=this.__v_raw,i=Xn(o),a=sh(i),l=e==="entries"||e===Symbol.iterator&&a,s=e==="keys"&&a,u=o[e](...r),c=n?eE:t?rE:Am;return!t&&wa(i,"iterate",s?q8:hf),{next(){const{value:d,done:f}=u.next();return f?{value:d,done:f}:{value:l?[c(d[0]),c(d[1])]:c(d),done:f}},[Symbol.iterator](){return this}}}}function dc(e){return function(...t){return e==="delete"?!1:e==="clear"?void 0:this}}function BX(){const e={get(i){return R1(this,i)},get size(){return I1(this)},has:P1,add:Dk,set:Nk,delete:Fk,clear:Rk,forEach:L1(!1,!1)},t={get(i){return R1(this,i,!1,!0)},get size(){return I1(this)},has:P1,add:Dk,set:Nk,delete:Fk,clear:Rk,forEach:L1(!1,!0)},n={get(i){return R1(this,i,!0)},get size(){return I1(this,!0)},has(i){return P1.call(this,i,!0)},add:dc("add"),set:dc("set"),delete:dc("delete"),clear:dc("clear"),forEach:L1(!0,!1)},r={get(i){return R1(this,i,!0,!0)},get size(){return I1(this,!0)},has(i){return P1.call(this,i,!0)},add:dc("add"),set:dc("set"),delete:dc("delete"),clear:dc("clear"),forEach:L1(!0,!0)};return["keys","values","entries",Symbol.iterator].forEach(i=>{e[i]=B1(i,!1,!1),n[i]=B1(i,!0,!1),t[i]=B1(i,!1,!0),r[i]=B1(i,!0,!0)}),[e,n,t,r]}const[$X,zX,jX,VX]=BX();function tE(e,t){const n=t?e?VX:jX:e?zX:$X;return(r,o,i)=>o==="__v_isReactive"?!e:o==="__v_isReadonly"?e:o==="__v_raw"?r:Reflect.get(kn(n,o)&&o in r?n:r,o,i)}const HX={get:tE(!1,!1)},WX={get:tE(!1,!0)},UX={get:tE(!0,!1)},vI=new WeakMap,mI=new WeakMap,gI=new WeakMap,KX=new WeakMap;function YX(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function GX(e){return e.__v_skip||!Object.isExtensible(e)?0:YX(U2(e))}function mn(e){return Eh(e)?e:nE(e,!1,PX,HX,vI)}function Nb(e){return nE(e,!1,LX,WX,mI)}function cv(e){return nE(e,!0,IX,UX,gI)}function nE(e,t,n,r,o){if(!hn(e)||e.__v_raw&&!(t&&e.__v_isReactive))return e;const i=o.get(e);if(i)return i;const a=GX(e);if(a===0)return e;const l=new Proxy(e,a===2?r:n);return o.set(e,l),l}function $c(e){return Eh(e)?$c(e.__v_raw):!!(e&&e.__v_isReactive)}function Eh(e){return!!(e&&e.__v_isReadonly)}function Qy(e){return!!(e&&e.__v_isShallow)}function yI(e){return $c(e)||Eh(e)}function Xn(e){const t=e&&e.__v_raw;return t?Xn(t):e}function Yr(e){return qy(e,"__v_skip",!0),e}const Am=e=>hn(e)?mn(e):e,rE=e=>hn(e)?cv(e):e;function _I(e){Bc&&Ul&&(e=Xn(e),dI(e.dep||(e.dep=JC())))}function oE(e,t){e=Xn(e);const n=e.dep;n&&X8(n)}function Mn(e){return!!(e&&e.__v_isRef===!0)}function se(e){return bI(e,!1)}function pr(e){return bI(e,!0)}function bI(e,t){return Mn(e)?e:new qX(e,t)}class qX{constructor(t,n){this.__v_isShallow=n,this.dep=void 0,this.__v_isRef=!0,this._rawValue=n?t:Xn(t),this._value=n?t:Am(t)}get value(){return _I(this),this._value}set value(t){const n=this.__v_isShallow||Qy(t)||Eh(t);t=n?t:Xn(t),Sf(t,this._rawValue)&&(this._rawValue=t,this._value=n?t:Am(t),oE(this))}}function d0(e){oE(e)}function w(e){return Mn(e)?e.value:e}const XX={get:(e,t,n)=>w(Reflect.get(e,t,n)),set:(e,t,n,r)=>{const o=e[t];return Mn(o)&&!Mn(n)?(o.value=n,!0):Reflect.set(e,t,n,r)}};function wI(e){return $c(e)?e:new Proxy(e,XX)}function $r(e){const t=Mt(e)?new Array(e.length):{};for(const n in e)t[n]=xI(e,n);return t}class QX{constructor(t,n,r){this._object=t,this._key=n,this._defaultValue=r,this.__v_isRef=!0}get value(){const t=this._object[this._key];return t===void 0?this._defaultValue:t}set value(t){this._object[this._key]=t}get dep(){return OX(Xn(this._object),this._key)}}class JX{constructor(t){this._getter=t,this.__v_isRef=!0,this.__v_isReadonly=!0}get value(){return this._getter()}}function In(e,t,n){return Mn(e)?e:jt(e)?new JX(e):hn(e)&&arguments.length>1?xI(e,t,n):se(e)}function xI(e,t,n){const r=e[t];return Mn(r)?r:new QX(e,t,n)}class ZX{constructor(t,n,r,o){this._setter=n,this.dep=void 0,this.__v_isRef=!0,this.__v_isReadonly=!1,this._dirty=!0,this.effect=new ZC(t,()=>{this._dirty||(this._dirty=!0,oE(this))}),this.effect.computed=this,this.effect.active=this._cacheable=!o,this.__v_isReadonly=r}get value(){const t=Xn(this);return _I(t),(t._dirty||!t._cacheable)&&(t._dirty=!1,t._value=t.effect.run()),t._value}set value(t){this._setter(t)}}function CI(e,t,n=!1){let r,o;const i=jt(e);return i?(r=e,o=lr):(r=e.get,o=e.set),new ZX(r,o,i||!o,n)}function EI(e,...t){}function zc(e,t,n,r){let o;try{o=r?e(...r):e()}catch(i){Fb(i,t,n)}return o}function ul(e,t,n,r){if(jt(e)){const i=zc(e,t,n,r);return i&&Em(i)&&i.catch(a=>{Fb(a,t,n)}),i}const o=[];for(let i=0;i>>1,o=xi[r],i=km(o);iOs&&xi.splice(t,1)}function rQ(e){Mt(e)?uh.push(...e):(!gu||!gu.includes(e,e.allowRecurse?Yd+1:Yd))&&uh.push(e),SI()}function Pk(e,t=Sm?Os+1:0){for(;tkm(n)-km(r)),Yd=0;Yde.id==null?1/0:e.id,oQ=(e,t)=>{const n=km(e)-km(t);if(n===0){if(e.pre&&!t.pre)return-1;if(t.pre&&!e.pre)return 1}return n};function MI(e){Q8=!1,Sm=!0,xi.sort(oQ);try{for(Os=0;OsYt(h)?h.trim():h)),d&&(o=n.map(K8))}let l,s=r[l=Y2(t)]||r[l=Y2(ml(t))];!s&&i&&(s=r[l=Y2(ad(t))]),s&&ul(s,e,6,o);const u=r[l+"Once"];if(u){if(!e.emitted)e.emitted={};else if(e.emitted[l])return;e.emitted[l]=!0,ul(u,e,6,o)}}function TI(e,t,n=!1){const r=t.emitsCache,o=r.get(e);if(o!==void 0)return o;const i=e.emits;let a={},l=!1;if(!jt(e)){const s=u=>{const c=TI(u,t,!0);c&&(l=!0,Ho(a,c))};!n&&t.mixins.length&&t.mixins.forEach(s),e.extends&&s(e.extends),e.mixins&&e.mixins.forEach(s)}return!i&&!l?(hn(e)&&r.set(e,null),null):(Mt(i)?i.forEach(s=>a[s]=null):Ho(a,i),hn(e)&&r.set(e,a),a)}function Rb(e,t){return!e||!Mb(t)?!1:(t=t.slice(2).replace(/Once$/,""),kn(e,t[0].toLowerCase()+t.slice(1))||kn(e,ad(t))||kn(e,t))}let Vo=null,Pb=null;function Jy(e){const t=Vo;return Vo=e,Pb=e&&e.type.__scopeId||null,t}function Ib(e){Pb=e}function Lb(){Pb=null}function X(e,t=Vo,n){if(!t||e._n)return e;const r=(...o)=>{r._d&&qk(-1);const i=Jy(t);let a;try{a=e(...o)}finally{Jy(i),r._d&&qk(1)}return a};return r._n=!0,r._c=!0,r._d=!0,r}function Ik(e){const{type:t,vnode:n,proxy:r,withProxy:o,props:i,propsOptions:[a],slots:l,attrs:s,emit:u,render:c,renderCache:d,data:f,setupState:h,ctx:v,inheritAttrs:m}=e;let y,g;const b=Jy(e);try{if(n.shapeFlag&4){const C=o||r,E=C;y=ks(c.call(E,C,d,i,h,f,v)),g=s}else{const C=t;y=ks(C.length>1?C(i,{attrs:s,slots:l,emit:u}):C(i,null)),g=t.props?s:aQ(s)}}catch(C){K0.length=0,Fb(C,e,1),y=Y(Ui)}let x=y;if(g&&m!==!1){const C=Object.keys(g),{shapeFlag:E}=x;C.length&&E&7&&(a&&C.some(KC)&&(g=lQ(g,a)),x=Ru(x,g))}return n.dirs&&(x=Ru(x),x.dirs=x.dirs?x.dirs.concat(n.dirs):n.dirs),n.transition&&(x.transition=n.transition),y=x,Jy(b),y}const aQ=e=>{let t;for(const n in e)(n==="class"||n==="style"||Mb(n))&&((t||(t={}))[n]=e[n]);return t},lQ=(e,t)=>{const n={};for(const r in e)(!KC(r)||!(r.slice(9)in t))&&(n[r]=e[r]);return n};function sQ(e,t,n){const{props:r,children:o,component:i}=e,{props:a,children:l,patchFlag:s}=t,u=i.emitsOptions;if(t.dirs||t.transition)return!0;if(n&&s>=0){if(s&1024)return!0;if(s&16)return r?Lk(r,a,u):!!a;if(s&8){const c=t.dynamicProps;for(let d=0;de.__isSuspense;function fQ(e,t){t&&t.pendingBranch?Mt(e)?t.effects.push(...e):t.effects.push(e):rQ(e)}function ma(e,t){return uE(e,null,t)}const $1={};function Ye(e,t,n){return uE(e,t,n)}function uE(e,t,{immediate:n,deep:r,flush:o,onTrack:i,onTrigger:a}=Kr){var l;const s=XC()===((l=Io)==null?void 0:l.scope)?Io:null;let u,c=!1,d=!1;if(Mn(e)?(u=()=>e.value,c=Qy(e)):$c(e)?(u=()=>e,r=!0):Mt(e)?(d=!0,c=e.some(C=>$c(C)||Qy(C)),u=()=>e.map(C=>{if(Mn(C))return C.value;if($c(C))return af(C);if(jt(C))return zc(C,s,2)})):jt(e)?t?u=()=>zc(e,s,2):u=()=>{if(!(s&&s.isUnmounted))return f&&f(),ul(e,s,3,[h])}:u=lr,t&&r){const C=u;u=()=>af(C())}let f,h=C=>{f=b.onStop=()=>{zc(C,s,4),f=b.onStop=void 0}},v;if(Nm)if(h=lr,t?n&&ul(t,s,3,[u(),d?[]:void 0,h]):u(),o==="sync"){const C=tJ();v=C.__watcherHandles||(C.__watcherHandles=[])}else return lr;let m=d?new Array(e.length).fill($1):$1;const y=()=>{if(b.active)if(t){const C=b.run();(r||c||(d?C.some((E,A)=>Sf(E,m[A])):Sf(C,m)))&&(f&&f(),ul(t,s,3,[C,m===$1?void 0:d&&m[0]===$1?[]:m,h]),m=C)}else b.run()};y.allowRecurse=!!t;let g;o==="sync"?g=y:o==="post"?g=()=>pa(y,s&&s.suspense):(y.pre=!0,s&&(y.id=s.uid),g=()=>aE(y));const b=new ZC(u,g);t?n?y():m=b.run():o==="post"?pa(b.run.bind(b),s&&s.suspense):b.run();const x=()=>{b.stop(),s&&s.scope&&YC(s.scope.effects,b)};return v&&v.push(x),x}function pQ(e,t,n){const r=this.proxy,o=Yt(e)?e.includes(".")?DI(r,e):()=>r[e]:e.bind(r,r);let i;jt(t)?i=t:(i=t.handler,n=t);const a=Io;Ah(this);const l=uE(o,i.bind(r),n);return a?Ah(a):vf(),l}function DI(e,t){const n=t.split(".");return()=>{let r=e;for(let o=0;o{af(n,t)});else if(Gy(e))for(const n in e)af(e[n],t);return e}function It(e,t){const n=Vo;if(n===null)return e;const r=jb(n)||n.proxy,o=e.dirs||(e.dirs=[]);for(let i=0;i{e.isMounted=!0}),Ar(()=>{e.isUnmounting=!0}),e}const Xa=[Function,Array],FI={mode:String,appear:Boolean,persisted:Boolean,onBeforeEnter:Xa,onEnter:Xa,onAfterEnter:Xa,onEnterCancelled:Xa,onBeforeLeave:Xa,onLeave:Xa,onAfterLeave:Xa,onLeaveCancelled:Xa,onBeforeAppear:Xa,onAppear:Xa,onAfterAppear:Xa,onAppearCancelled:Xa},hQ={name:"BaseTransition",props:FI,setup(e,{slots:t}){const n=Kt(),r=NI();let o;return()=>{const i=t.default&&cE(t.default(),!0);if(!i||!i.length)return;let a=i[0];if(i.length>1){for(const m of i)if(m.type!==Ui){a=m;break}}const l=Xn(e),{mode:s}=l;if(r.isLeaving)return n3(a);const u=$k(a);if(!u)return n3(a);const c=Mm(u,l,r,n);Tm(u,c);const d=n.subTree,f=d&&$k(d);let h=!1;const{getTransitionKey:v}=u.type;if(v){const m=v();o===void 0?o=m:m!==o&&(o=m,h=!0)}if(f&&f.type!==Ui&&(!Gd(u,f)||h)){const m=Mm(f,l,r,n);if(Tm(f,m),s==="out-in")return r.isLeaving=!0,m.afterLeave=()=>{r.isLeaving=!1,n.update.active!==!1&&n.update()},n3(a);s==="in-out"&&u.type!==Ui&&(m.delayLeave=(y,g,b)=>{const x=RI(r,f);x[String(f.key)]=f,y[bc]=()=>{g(),y[bc]=void 0,delete c.delayedLeave},c.delayedLeave=b})}return a}}},vQ=hQ;function RI(e,t){const{leavingVNodes:n}=e;let r=n.get(t.type);return r||(r=Object.create(null),n.set(t.type,r)),r}function Mm(e,t,n,r){const{appear:o,mode:i,persisted:a=!1,onBeforeEnter:l,onEnter:s,onAfterEnter:u,onEnterCancelled:c,onBeforeLeave:d,onLeave:f,onAfterLeave:h,onLeaveCancelled:v,onBeforeAppear:m,onAppear:y,onAfterAppear:g,onAppearCancelled:b}=t,x=String(e.key),C=RI(n,e),E=(D,I)=>{D&&ul(D,r,9,I)},A=(D,I)=>{const T=I[1];E(D,I),Mt(D)?D.every(N=>N.length<=1)&&T():D.length<=1&&T()},M={mode:i,persisted:a,beforeEnter(D){let I=l;if(!n.isMounted)if(o)I=m||l;else return;D[bc]&&D[bc](!0);const T=C[x];T&&Gd(e,T)&&T.el[bc]&&T.el[bc](),E(I,[D])},enter(D){let I=s,T=u,N=c;if(!n.isMounted)if(o)I=y||s,T=g||u,N=b||c;else return;let L=!1;const P=D[z1]=j=>{L||(L=!0,j?E(N,[D]):E(T,[D]),M.delayedLeave&&M.delayedLeave(),D[z1]=void 0)};I?A(I,[D,P]):P()},leave(D,I){const T=String(e.key);if(D[z1]&&D[z1](!0),n.isUnmounting)return I();E(d,[D]);let N=!1;const L=D[bc]=P=>{N||(N=!0,I(),P?E(v,[D]):E(h,[D]),D[bc]=void 0,C[T]===e&&delete C[T])};C[T]=e,f?A(f,[D,L]):L()},clone(D){return Mm(D,t,n,r)}};return M}function n3(e){if(Bb(e))return e=Ru(e),e.children=null,e}function $k(e){return Bb(e)?e.children?e.children[0]:void 0:e}function Tm(e,t){e.shapeFlag&6&&e.component?Tm(e.component.subTree,t):e.shapeFlag&128?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function cE(e,t=!1,n){let r=[],o=0;for(let i=0;i1)for(let i=0;i!!e.type.__asyncLoader,Bb=e=>e.type.__isKeepAlive;function mQ(e,t){II(e,"a",t)}function PI(e,t){II(e,"da",t)}function II(e,t,n=Io){const r=e.__wdc||(e.__wdc=()=>{let o=n;for(;o;){if(o.isDeactivated)return;o=o.parent}return e()});if($b(t,r,n),n){let o=n.parent;for(;o&&o.parent;)Bb(o.parent.vnode)&&gQ(r,t,n,o),o=o.parent}}function gQ(e,t,n,r){const o=$b(t,e,r,!0);qs(()=>{YC(r[t],o)},n)}function $b(e,t,n=Io,r=!1){if(n){const o=n[e]||(n[e]=[]),i=t.__weh||(t.__weh=(...a)=>{if(n.isUnmounted)return;sv(),Ah(n);const l=ul(t,n,e,a);return vf(),uv(),l});return r?o.unshift(i):o.push(i),i}}const Vu=e=>(t,n=Io)=>(!Nm||e==="sp")&&$b(e,(...r)=>t(...r),n),Ag=Vu("bm"),en=Vu("m"),LI=Vu("bu"),Gs=Vu("u"),Ar=Vu("bum"),qs=Vu("um"),yQ=Vu("sp"),_Q=Vu("rtg"),bQ=Vu("rtc");function wQ(e,t=Io){$b("ec",e,t)}function Nt(e,t,n,r){let o;const i=n;if(Mt(e)||Yt(e)){o=new Array(e.length);for(let a=0,l=e.length;at(a,l,void 0,i));else{const a=Object.keys(e);o=new Array(a.length);for(let l=0,s=a.length;l{const i=r.fn(...o);return i&&(i.key=r.key),i}:r.fn)}return e}function Qe(e,t,n={},r,o){if(Vo.isCE||Vo.parent&&H0(Vo.parent)&&Vo.parent.isCE)return t!=="default"&&(n.name=t),Y("slot",n,r&&r());let i=e[t];i&&i._c&&(i._d=!1),R();const a=i&&BI(i(n)),l=be(at,{key:n.key||a&&a.key||`_${t}`},a||(r?r():[]),a&&e._===1?64:-2);return!o&&l.scopeId&&(l.slotScopeIds=[l.scopeId+"-s"]),i&&i._c&&(i._d=!0),l}function BI(e){return e.some(t=>Er(t)?!(t.type===Ui||t.type===at&&!BI(t.children)):!0)?e:null}function xQ(e,t){const n={};for(const r in e)n[Y2(r)]=e[r];return n}const J8=e=>e?QI(e)?jb(e)||e.proxy:J8(e.parent):null,W0=Ho(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>J8(e.parent),$root:e=>J8(e.root),$emit:e=>e.emit,$options:e=>jI(e),$forceUpdate:e=>e.f||(e.f=()=>aE(e.update)),$nextTick:e=>e.n||(e.n=vt.bind(e.proxy)),$watch:e=>pQ.bind(e)}),r3=(e,t)=>e!==Kr&&!e.__isScriptSetup&&kn(e,t),CQ={get({_:e},t){const{ctx:n,setupState:r,data:o,props:i,accessCache:a,type:l,appContext:s}=e;let u;if(t[0]!=="$"){const h=a[t];if(h!==void 0)switch(h){case 1:return r[t];case 2:return o[t];case 4:return n[t];case 3:return i[t]}else{if(r3(r,t))return a[t]=1,r[t];if(o!==Kr&&kn(o,t))return a[t]=2,o[t];if((u=e.propsOptions[0])&&kn(u,t))return a[t]=3,i[t];if(n!==Kr&&kn(n,t))return a[t]=4,n[t];Z8&&(a[t]=0)}}const c=W0[t];let d,f;if(c)return t==="$attrs"&&wa(e,"get",t),c(e);if((d=l.__cssModules)&&(d=d[t]))return d;if(n!==Kr&&kn(n,t))return a[t]=4,n[t];if(f=s.config.globalProperties,kn(f,t))return f[t]},set({_:e},t,n){const{data:r,setupState:o,ctx:i}=e;return r3(o,t)?(o[t]=n,!0):r!==Kr&&kn(r,t)?(r[t]=n,!0):kn(e.props,t)||t[0]==="$"&&t.slice(1)in e?!1:(i[t]=n,!0)},has({_:{data:e,setupState:t,accessCache:n,ctx:r,appContext:o,propsOptions:i}},a){let l;return!!n[a]||e!==Kr&&kn(e,a)||r3(t,a)||(l=i[0])&&kn(l,a)||kn(r,a)||kn(W0,a)||kn(o.config.globalProperties,a)},defineProperty(e,t,n){return n.get!=null?e._.accessCache[t]=0:kn(n,"value")&&this.set(e,t,n.value,null),Reflect.defineProperty(e,t,n)}};function Ko(){return $I().slots}function Vf(){return $I().attrs}function $I(){const e=Kt();return e.setupContext||(e.setupContext=ZI(e))}function zk(e){return Mt(e)?e.reduce((t,n)=>(t[n]=null,t),{}):e}let Z8=!0;function EQ(e){const t=jI(e),n=e.proxy,r=e.ctx;Z8=!1,t.beforeCreate&&jk(t.beforeCreate,e,"bc");const{data:o,computed:i,methods:a,watch:l,provide:s,inject:u,created:c,beforeMount:d,mounted:f,beforeUpdate:h,updated:v,activated:m,deactivated:y,beforeDestroy:g,beforeUnmount:b,destroyed:x,unmounted:C,render:E,renderTracked:A,renderTriggered:M,errorCaptured:D,serverPrefetch:I,expose:T,inheritAttrs:N,components:L,directives:P,filters:j}=t;if(u&&AQ(u,r,null),a)for(const H in a){const K=a[H];jt(K)&&(r[H]=K.bind(n))}if(o){const H=o.call(n,n);hn(H)&&(e.data=mn(H))}if(Z8=!0,i)for(const H in i){const K=i[H],B=jt(K)?K.bind(n,n):jt(K.get)?K.get.bind(n,n):lr,Z=!jt(K)&&jt(K.set)?K.set.bind(n):lr,oe=G({get:B,set:Z});Object.defineProperty(r,H,{enumerable:!0,configurable:!0,get:()=>oe.value,set:V=>oe.value=V})}if(l)for(const H in l)zI(l[H],r,n,H);if(s){const H=jt(s)?s.call(n):s;Reflect.ownKeys(H).forEach(K=>{_n(K,H[K])})}c&&jk(c,e,"c");function z(H,K){Mt(K)?K.forEach(B=>H(B.bind(n))):K&&H(K.bind(n))}if(z(Ag,d),z(en,f),z(LI,h),z(Gs,v),z(mQ,m),z(PI,y),z(wQ,D),z(bQ,A),z(_Q,M),z(Ar,b),z(qs,C),z(yQ,I),Mt(T))if(T.length){const H=e.exposed||(e.exposed={});T.forEach(K=>{Object.defineProperty(H,K,{get:()=>n[K],set:B=>n[K]=B})})}else e.exposed||(e.exposed={});E&&e.render===lr&&(e.render=E),N!=null&&(e.inheritAttrs=N),L&&(e.components=L),P&&(e.directives=P)}function AQ(e,t,n=lr){Mt(e)&&(e=e5(e));for(const r in e){const o=e[r];let i;hn(o)?"default"in o?i=kt(o.from||r,o.default,!0):i=kt(o.from||r):i=kt(o),Mn(i)?Object.defineProperty(t,r,{enumerable:!0,configurable:!0,get:()=>i.value,set:a=>i.value=a}):t[r]=i}}function jk(e,t,n){ul(Mt(e)?e.map(r=>r.bind(t.proxy)):e.bind(t.proxy),t,n)}function zI(e,t,n,r){const o=r.includes(".")?DI(n,r):()=>n[r];if(Yt(e)){const i=t[e];jt(i)&&Ye(o,i)}else if(jt(e))Ye(o,e.bind(n));else if(hn(e))if(Mt(e))e.forEach(i=>zI(i,t,n,r));else{const i=jt(e.handler)?e.handler.bind(n):t[e.handler];jt(i)&&Ye(o,i,e)}}function jI(e){const t=e.type,{mixins:n,extends:r}=t,{mixins:o,optionsCache:i,config:{optionMergeStrategies:a}}=e.appContext,l=i.get(t);let s;return l?s=l:!o.length&&!n&&!r?s=t:(s={},o.length&&o.forEach(u=>Zy(s,u,a,!0)),Zy(s,t,a)),hn(t)&&i.set(t,s),s}function Zy(e,t,n,r=!1){const{mixins:o,extends:i}=t;i&&Zy(e,i,n,!0),o&&o.forEach(a=>Zy(e,a,n,!0));for(const a in t)if(!(r&&a==="expose")){const l=SQ[a]||n&&n[a];e[a]=l?l(e[a],t[a]):t[a]}return e}const SQ={data:Vk,props:Hk,emits:Hk,methods:F0,computed:F0,beforeCreate:zi,created:zi,beforeMount:zi,mounted:zi,beforeUpdate:zi,updated:zi,beforeDestroy:zi,beforeUnmount:zi,destroyed:zi,unmounted:zi,activated:zi,deactivated:zi,errorCaptured:zi,serverPrefetch:zi,components:F0,directives:F0,watch:MQ,provide:Vk,inject:kQ};function Vk(e,t){return t?e?function(){return Ho(jt(e)?e.call(this,this):e,jt(t)?t.call(this,this):t)}:t:e}function kQ(e,t){return F0(e5(e),e5(t))}function e5(e){if(Mt(e)){const t={};for(let n=0;n1)return n&&jt(t)?t.call(r&&r.proxy):t}}function DQ(){return!!(Io||Vo||Om)}function NQ(e,t,n,r=!1){const o={},i={};qy(i,zb,1),e.propsDefaults=Object.create(null),HI(e,t,o,i);for(const a in e.propsOptions[0])a in o||(o[a]=void 0);n?e.props=r?o:Nb(o):e.type.props?e.props=o:e.props=i,e.attrs=i}function FQ(e,t,n,r){const{props:o,attrs:i,vnode:{patchFlag:a}}=e,l=Xn(o),[s]=e.propsOptions;let u=!1;if((r||a>0)&&!(a&16)){if(a&8){const c=e.vnode.dynamicProps;for(let d=0;d{s=!0;const[f,h]=WI(d,t,!0);Ho(a,f),h&&l.push(...h)};!n&&t.mixins.length&&t.mixins.forEach(c),e.extends&&c(e.extends),e.mixins&&e.mixins.forEach(c)}if(!i&&!s)return hn(e)&&r.set(e,lh),lh;if(Mt(i))for(let c=0;c-1,h[1]=m<0||v-1||kn(h,"default"))&&l.push(d)}}}const u=[a,l];return hn(e)&&r.set(e,u),u}function Wk(e){return e[0]!=="$"}function Uk(e){const t=e&&e.toString().match(/^\s*(function|class) (\w+)/);return t?t[2]:e===null?"null":""}function Kk(e,t){return Uk(e)===Uk(t)}function Yk(e,t){return Mt(t)?t.findIndex(n=>Kk(n,e)):jt(t)&&Kk(t,e)?0:-1}const UI=e=>e[0]==="_"||e==="$stable",dE=e=>Mt(e)?e.map(ks):[ks(e)],RQ=(e,t,n)=>{if(t._n)return t;const r=X((...o)=>dE(t(...o)),n);return r._c=!1,r},KI=(e,t,n)=>{const r=e._ctx;for(const o in e){if(UI(o))continue;const i=e[o];if(jt(i))t[o]=RQ(o,i,r);else if(i!=null){const a=dE(i);t[o]=()=>a}}},YI=(e,t)=>{const n=dE(t);e.slots.default=()=>n},PQ=(e,t)=>{if(e.vnode.shapeFlag&32){const n=t._;n?(e.slots=Xn(t),qy(t,"_",n)):KI(t,e.slots={})}else e.slots={},t&&YI(e,t);qy(e.slots,zb,1)},IQ=(e,t,n)=>{const{vnode:r,slots:o}=e;let i=!0,a=Kr;if(r.shapeFlag&32){const l=t._;l?n&&l===1?i=!1:(Ho(o,t),!n&&l===1&&delete o._):(i=!t.$stable,KI(t,o)),a=t}else t&&(YI(e,t),a={default:1});if(i)for(const l in o)!UI(l)&&a[l]==null&&delete o[l]};function n5(e,t,n,r,o=!1){if(Mt(e)){e.forEach((f,h)=>n5(f,t&&(Mt(t)?t[h]:t),n,r,o));return}if(H0(r)&&!o)return;const i=r.shapeFlag&4?jb(r.component)||r.component.proxy:r.el,a=o?null:i,{i:l,r:s}=e,u=t&&t.r,c=l.refs===Kr?l.refs={}:l.refs,d=l.setupState;if(u!=null&&u!==s&&(Yt(u)?(c[u]=null,kn(d,u)&&(d[u]=null)):Mn(u)&&(u.value=null)),jt(s))zc(s,l,12,[a,c]);else{const f=Yt(s),h=Mn(s);if(f||h){const v=()=>{if(e.f){const m=f?kn(d,s)?d[s]:c[s]:s.value;o?Mt(m)&&YC(m,i):Mt(m)?m.includes(i)||m.push(i):f?(c[s]=[i],kn(d,s)&&(d[s]=c[s])):(s.value=[i],e.k&&(c[e.k]=s.value))}else f?(c[s]=a,kn(d,s)&&(d[s]=a)):h&&(s.value=a,e.k&&(c[e.k]=a))};a?(v.id=-1,pa(v,n)):v()}}}const pa=fQ;function LQ(e){return BQ(e)}function BQ(e,t){const n=Y8();n.__VUE__=!0;const{insert:r,remove:o,patchProp:i,createElement:a,createText:l,createComment:s,setText:u,setElementText:c,parentNode:d,nextSibling:f,setScopeId:h=lr,insertStaticContent:v}=e,m=(J,ce,ye,Ne=null,Be=null,Ge=null,qe=!1,Te=null,pe=!!ce.dynamicChildren)=>{if(J===ce)return;J&&!Gd(J,ce)&&(Ne=ie(J),V(J,Be,Ge,!0),J=null),ce.patchFlag===-2&&(pe=!1,ce.dynamicChildren=null);const{type:me,ref:Ee,shapeFlag:$e}=ce;switch(me){case dv:y(J,ce,ye,Ne);break;case Ui:g(J,ce,ye,Ne);break;case o3:J==null&&b(ce,ye,Ne,qe);break;case at:L(J,ce,ye,Ne,Be,Ge,qe,Te,pe);break;default:$e&1?E(J,ce,ye,Ne,Be,Ge,qe,Te,pe):$e&6?P(J,ce,ye,Ne,Be,Ge,qe,Te,pe):($e&64||$e&128)&&me.process(J,ce,ye,Ne,Be,Ge,qe,Te,pe,ae)}Ee!=null&&Be&&n5(Ee,J&&J.ref,Ge,ce||J,!ce)},y=(J,ce,ye,Ne)=>{if(J==null)r(ce.el=l(ce.children),ye,Ne);else{const Be=ce.el=J.el;ce.children!==J.children&&u(Be,ce.children)}},g=(J,ce,ye,Ne)=>{J==null?r(ce.el=s(ce.children||""),ye,Ne):ce.el=J.el},b=(J,ce,ye,Ne)=>{[J.el,J.anchor]=v(J.children,ce,ye,Ne,J.el,J.anchor)},x=({el:J,anchor:ce},ye,Ne)=>{let Be;for(;J&&J!==ce;)Be=f(J),r(J,ye,Ne),J=Be;r(ce,ye,Ne)},C=({el:J,anchor:ce})=>{let ye;for(;J&&J!==ce;)ye=f(J),o(J),J=ye;o(ce)},E=(J,ce,ye,Ne,Be,Ge,qe,Te,pe)=>{qe=qe||ce.type==="svg",J==null?A(ce,ye,Ne,Be,Ge,qe,Te,pe):I(J,ce,Be,Ge,qe,Te,pe)},A=(J,ce,ye,Ne,Be,Ge,qe,Te)=>{let pe,me;const{type:Ee,props:$e,shapeFlag:De,transition:Xe,dirs:Ie}=J;if(pe=J.el=a(J.type,Ge,$e&&$e.is,$e),De&8?c(pe,J.children):De&16&&D(J.children,pe,null,Ne,Be,Ge&&Ee!=="foreignObject",qe,Te),Ie&&Nd(J,null,Ne,"created"),M(pe,J,J.scopeId,qe,Ne),$e){for(const ct in $e)ct!=="value"&&!K2(ct)&&i(pe,ct,null,$e[ct],Ge,J.children,Ne,Be,Q);"value"in $e&&i(pe,"value",null,$e.value),(me=$e.onVnodeBeforeMount)&&xs(me,Ne,J)}Ie&&Nd(J,null,Ne,"beforeMount");const Je=$Q(Be,Xe);Je&&Xe.beforeEnter(pe),r(pe,ce,ye),((me=$e&&$e.onVnodeMounted)||Je||Ie)&&pa(()=>{me&&xs(me,Ne,J),Je&&Xe.enter(pe),Ie&&Nd(J,null,Ne,"mounted")},Be)},M=(J,ce,ye,Ne,Be)=>{if(ye&&h(J,ye),Ne)for(let Ge=0;Ge{for(let me=pe;me{const Te=ce.el=J.el;let{patchFlag:pe,dynamicChildren:me,dirs:Ee}=ce;pe|=J.patchFlag&16;const $e=J.props||Kr,De=ce.props||Kr;let Xe;ye&&Fd(ye,!1),(Xe=De.onVnodeBeforeUpdate)&&xs(Xe,ye,ce,J),Ee&&Nd(ce,J,ye,"beforeUpdate"),ye&&Fd(ye,!0);const Ie=Be&&ce.type!=="foreignObject";if(me?T(J.dynamicChildren,me,Te,ye,Ne,Ie,Ge):qe||K(J,ce,Te,null,ye,Ne,Ie,Ge,!1),pe>0){if(pe&16)N(Te,ce,$e,De,ye,Ne,Be);else if(pe&2&&$e.class!==De.class&&i(Te,"class",null,De.class,Be),pe&4&&i(Te,"style",$e.style,De.style,Be),pe&8){const Je=ce.dynamicProps;for(let ct=0;ct{Xe&&xs(Xe,ye,ce,J),Ee&&Nd(ce,J,ye,"updated")},Ne)},T=(J,ce,ye,Ne,Be,Ge,qe)=>{for(let Te=0;Te{if(ye!==Ne){if(ye!==Kr)for(const Te in ye)!K2(Te)&&!(Te in Ne)&&i(J,Te,ye[Te],null,qe,ce.children,Be,Ge,Q);for(const Te in Ne){if(K2(Te))continue;const pe=Ne[Te],me=ye[Te];pe!==me&&Te!=="value"&&i(J,Te,me,pe,qe,ce.children,Be,Ge,Q)}"value"in Ne&&i(J,"value",ye.value,Ne.value)}},L=(J,ce,ye,Ne,Be,Ge,qe,Te,pe)=>{const me=ce.el=J?J.el:l(""),Ee=ce.anchor=J?J.anchor:l("");let{patchFlag:$e,dynamicChildren:De,slotScopeIds:Xe}=ce;Xe&&(Te=Te?Te.concat(Xe):Xe),J==null?(r(me,ye,Ne),r(Ee,ye,Ne),D(ce.children,ye,Ee,Be,Ge,qe,Te,pe)):$e>0&&$e&64&&De&&J.dynamicChildren?(T(J.dynamicChildren,De,ye,Be,Ge,qe,Te),(ce.key!=null||Be&&ce===Be.subTree)&&fE(J,ce,!0)):K(J,ce,ye,Ee,Be,Ge,qe,Te,pe)},P=(J,ce,ye,Ne,Be,Ge,qe,Te,pe)=>{ce.slotScopeIds=Te,J==null?ce.shapeFlag&512?Be.ctx.activate(ce,ye,Ne,qe,pe):j(ce,ye,Ne,Be,Ge,qe,pe):O(J,ce,pe)},j=(J,ce,ye,Ne,Be,Ge,qe)=>{const Te=J.component=GQ(J,Ne,Be);if(Bb(J)&&(Te.ctx.renderer=ae),qQ(Te),Te.asyncDep){if(Be&&Be.registerDep(Te,z),!J.el){const pe=Te.subTree=Y(Ui);g(null,pe,ce,ye)}return}z(Te,J,ce,ye,Be,Ge,qe)},O=(J,ce,ye)=>{const Ne=ce.component=J.component;if(sQ(J,ce,ye))if(Ne.asyncDep&&!Ne.asyncResolved){H(Ne,ce,ye);return}else Ne.next=ce,nQ(Ne.update),Ne.update();else ce.el=J.el,Ne.vnode=ce},z=(J,ce,ye,Ne,Be,Ge,qe)=>{const Te=()=>{if(J.isMounted){let{next:Ee,bu:$e,u:De,parent:Xe,vnode:Ie}=J,Je=Ee,ct;Fd(J,!1),Ee?(Ee.el=Ie.el,H(J,Ee,qe)):Ee=Ie,$e&&G2($e),(ct=Ee.props&&Ee.props.onVnodeBeforeUpdate)&&xs(ct,Xe,Ee,Ie),Fd(J,!0);const wt=Ik(J),gt=J.subTree;J.subTree=wt,m(gt,wt,d(gt.el),ie(gt),J,Be,Ge),Ee.el=wt.el,Je===null&&uQ(J,wt.el),De&&pa(De,Be),(ct=Ee.props&&Ee.props.onVnodeUpdated)&&pa(()=>xs(ct,Xe,Ee,Ie),Be)}else{let Ee;const{el:$e,props:De}=ce,{bm:Xe,m:Ie,parent:Je}=J,ct=H0(ce);Fd(J,!1),Xe&&G2(Xe),!ct&&(Ee=De&&De.onVnodeBeforeMount)&&xs(Ee,Je,ce),Fd(J,!0);{const wt=J.subTree=Ik(J);m(null,wt,ye,Ne,J,Be,Ge),ce.el=wt.el}if(Ie&&pa(Ie,Be),!ct&&(Ee=De&&De.onVnodeMounted)){const wt=ce;pa(()=>xs(Ee,Je,wt),Be)}(ce.shapeFlag&256||Je&&H0(Je.vnode)&&Je.vnode.shapeFlag&256)&&J.a&&pa(J.a,Be),J.isMounted=!0,ce=ye=Ne=null}},pe=J.effect=new ZC(Te,()=>aE(me),J.scope),me=J.update=()=>pe.run();me.id=J.uid,Fd(J,!0),me()},H=(J,ce,ye)=>{ce.component=J;const Ne=J.vnode.props;J.vnode=ce,J.next=null,FQ(J,ce.props,Ne,ye),IQ(J,ce.children,ye),sv(),Pk(),uv()},K=(J,ce,ye,Ne,Be,Ge,qe,Te,pe=!1)=>{const me=J&&J.children,Ee=J?J.shapeFlag:0,$e=ce.children,{patchFlag:De,shapeFlag:Xe}=ce;if(De>0){if(De&128){Z(me,$e,ye,Ne,Be,Ge,qe,Te,pe);return}else if(De&256){B(me,$e,ye,Ne,Be,Ge,qe,Te,pe);return}}Xe&8?(Ee&16&&Q(me,Be,Ge),$e!==me&&c(ye,$e)):Ee&16?Xe&16?Z(me,$e,ye,Ne,Be,Ge,qe,Te,pe):Q(me,Be,Ge,!0):(Ee&8&&c(ye,""),Xe&16&&D($e,ye,Ne,Be,Ge,qe,Te,pe))},B=(J,ce,ye,Ne,Be,Ge,qe,Te,pe)=>{J=J||lh,ce=ce||lh;const me=J.length,Ee=ce.length,$e=Math.min(me,Ee);let De;for(De=0;De<$e;De++){const Xe=ce[De]=pe?wc(ce[De]):ks(ce[De]);m(J[De],Xe,ye,null,Be,Ge,qe,Te,pe)}me>Ee?Q(J,Be,Ge,!0,!1,$e):D(ce,ye,Ne,Be,Ge,qe,Te,pe,$e)},Z=(J,ce,ye,Ne,Be,Ge,qe,Te,pe)=>{let me=0;const Ee=ce.length;let $e=J.length-1,De=Ee-1;for(;me<=$e&&me<=De;){const Xe=J[me],Ie=ce[me]=pe?wc(ce[me]):ks(ce[me]);if(Gd(Xe,Ie))m(Xe,Ie,ye,null,Be,Ge,qe,Te,pe);else break;me++}for(;me<=$e&&me<=De;){const Xe=J[$e],Ie=ce[De]=pe?wc(ce[De]):ks(ce[De]);if(Gd(Xe,Ie))m(Xe,Ie,ye,null,Be,Ge,qe,Te,pe);else break;$e--,De--}if(me>$e){if(me<=De){const Xe=De+1,Ie=XeDe)for(;me<=$e;)V(J[me],Be,Ge,!0),me++;else{const Xe=me,Ie=me,Je=new Map;for(me=Ie;me<=De;me++){const Re=ce[me]=pe?wc(ce[me]):ks(ce[me]);Re.key!=null&&Je.set(Re.key,me)}let ct,wt=0;const gt=De-Ie+1;let ut=!1,He=0;const Ae=new Array(gt);for(me=0;me=gt){V(Re,Be,Ge,!0);continue}let _e;if(Re.key!=null)_e=Je.get(Re.key);else for(ct=Ie;ct<=De;ct++)if(Ae[ct-Ie]===0&&Gd(Re,ce[ct])){_e=ct;break}_e===void 0?V(Re,Be,Ge,!0):(Ae[_e-Ie]=me+1,_e>=He?He=_e:ut=!0,m(Re,ce[_e],ye,null,Be,Ge,qe,Te,pe),wt++)}const Me=ut?zQ(Ae):lh;for(ct=Me.length-1,me=gt-1;me>=0;me--){const Re=Ie+me,_e=ce[Re],Ue=Re+1{const{el:Ge,type:qe,transition:Te,children:pe,shapeFlag:me}=J;if(me&6){oe(J.component.subTree,ce,ye,Ne);return}if(me&128){J.suspense.move(ce,ye,Ne);return}if(me&64){qe.move(J,ce,ye,ae);return}if(qe===at){r(Ge,ce,ye);for(let $e=0;$eTe.enter(Ge),Be);else{const{leave:$e,delayLeave:De,afterLeave:Xe}=Te,Ie=()=>r(Ge,ce,ye),Je=()=>{$e(Ge,()=>{Ie(),Xe&&Xe()})};De?De(Ge,Ie,Je):Je()}else r(Ge,ce,ye)},V=(J,ce,ye,Ne=!1,Be=!1)=>{const{type:Ge,props:qe,ref:Te,children:pe,dynamicChildren:me,shapeFlag:Ee,patchFlag:$e,dirs:De}=J;if(Te!=null&&n5(Te,null,ye,J,!0),Ee&256){ce.ctx.deactivate(J);return}const Xe=Ee&1&&De,Ie=!H0(J);let Je;if(Ie&&(Je=qe&&qe.onVnodeBeforeUnmount)&&xs(Je,ce,J),Ee&6)fe(J.component,ye,Ne);else{if(Ee&128){J.suspense.unmount(ye,Ne);return}Xe&&Nd(J,null,ce,"beforeUnmount"),Ee&64?J.type.remove(J,ce,ye,Be,ae,Ne):me&&(Ge!==at||$e>0&&$e&64)?Q(me,ce,ye,!1,!0):(Ge===at&&$e&384||!Be&&Ee&16)&&Q(pe,ce,ye),Ne&&q(J)}(Ie&&(Je=qe&&qe.onVnodeUnmounted)||Xe)&&pa(()=>{Je&&xs(Je,ce,J),Xe&&Nd(J,null,ce,"unmounted")},ye)},q=J=>{const{type:ce,el:ye,anchor:Ne,transition:Be}=J;if(ce===at){ne(ye,Ne);return}if(ce===o3){C(J);return}const Ge=()=>{o(ye),Be&&!Be.persisted&&Be.afterLeave&&Be.afterLeave()};if(J.shapeFlag&1&&Be&&!Be.persisted){const{leave:qe,delayLeave:Te}=Be,pe=()=>qe(ye,Ge);Te?Te(J.el,Ge,pe):pe()}else Ge()},ne=(J,ce)=>{let ye;for(;J!==ce;)ye=f(J),o(J),J=ye;o(ce)},fe=(J,ce,ye)=>{const{bum:Ne,scope:Be,update:Ge,subTree:qe,um:Te}=J;Ne&&G2(Ne),Be.stop(),Ge&&(Ge.active=!1,V(qe,J,ce,ye)),Te&&pa(Te,ce),pa(()=>{J.isUnmounted=!0},ce),ce&&ce.pendingBranch&&!ce.isUnmounted&&J.asyncDep&&!J.asyncResolved&&J.suspenseId===ce.pendingId&&(ce.deps--,ce.deps===0&&ce.resolve())},Q=(J,ce,ye,Ne=!1,Be=!1,Ge=0)=>{for(let qe=Ge;qeJ.shapeFlag&6?ie(J.component.subTree):J.shapeFlag&128?J.suspense.next():f(J.anchor||J.el),he=(J,ce,ye)=>{J==null?ce._vnode&&V(ce._vnode,null,null,!0):m(ce._vnode||null,J,ce,null,null,null,ye),Pk(),kI(),ce._vnode=J},ae={p:m,um:V,m:oe,r:q,mt:j,mc:D,pc:K,pbc:T,n:ie,o:e};return{render:he,hydrate:void 0,createApp:OQ(he)}}function Fd({effect:e,update:t},n){e.allowRecurse=t.allowRecurse=n}function $Q(e,t){return(!e||e&&!e.pendingBranch)&&t&&!t.persisted}function fE(e,t,n=!1){const r=e.children,o=t.children;if(Mt(r)&&Mt(o))for(let i=0;i>1,e[n[l]]0&&(t[r]=n[i-1]),n[i]=r)}}for(i=n.length,a=n[i-1];i-- >0;)n[i]=a,a=t[a];return n}const jQ=e=>e.__isTeleport,U0=e=>e&&(e.disabled||e.disabled===""),Gk=e=>typeof SVGElement<"u"&&e instanceof SVGElement,r5=(e,t)=>{const n=e&&e.to;return Yt(n)?t?t(n):null:n},VQ={name:"Teleport",__isTeleport:!0,process(e,t,n,r,o,i,a,l,s,u){const{mc:c,pc:d,pbc:f,o:{insert:h,querySelector:v,createText:m,createComment:y}}=u,g=U0(t.props);let{shapeFlag:b,children:x,dynamicChildren:C}=t;if(e==null){const E=t.el=m(""),A=t.anchor=m("");h(E,n,r),h(A,n,r);const M=t.target=r5(t.props,v),D=t.targetAnchor=m("");M&&(h(D,M),a=a||Gk(M));const I=(T,N)=>{b&16&&c(x,T,N,o,i,a,l,s)};g?I(n,A):M&&I(M,D)}else{t.el=e.el;const E=t.anchor=e.anchor,A=t.target=e.target,M=t.targetAnchor=e.targetAnchor,D=U0(e.props),I=D?n:A,T=D?E:M;if(a=a||Gk(A),C?(f(e.dynamicChildren,C,I,o,i,a,l),fE(e,t,!0)):s||d(e,t,I,T,o,i,a,l,!1),g)D?t.props&&e.props&&t.props.to!==e.props.to&&(t.props.to=e.props.to):j1(t,n,E,u,1);else if((t.props&&t.props.to)!==(e.props&&e.props.to)){const N=t.target=r5(t.props,v);N&&j1(t,N,null,u,0)}else D&&j1(t,A,M,u,1)}GI(t)},remove(e,t,n,r,{um:o,o:{remove:i}},a){const{shapeFlag:l,children:s,anchor:u,targetAnchor:c,target:d,props:f}=e;if(d&&i(c),a&&i(u),l&16){const h=a||!U0(f);for(let v=0;v0?ql||lh:null,WQ(),Dm>0&&ql&&ql.push(e),e}function W(e,t,n,r,o,i){return qI($(e,t,n,r,o,i,!0))}function be(e,t,n,r,o){return qI(Y(e,t,n,r,o,!0))}function Er(e){return e?e.__v_isVNode===!0:!1}function Gd(e,t){return e.type===t.type&&e.key===t.key}const zb="__vInternal",XI=({key:e})=>e??null,q2=({ref:e,ref_key:t,ref_for:n})=>(typeof e=="number"&&(e=""+e),e!=null?Yt(e)||Mn(e)||jt(e)?{i:Vo,r:e,k:t,f:!!n}:e:null);function $(e,t=null,n=null,r=0,o=null,i=e===at?0:1,a=!1,l=!1){const s={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&XI(t),ref:t&&q2(t),scopeId:Pb,slotScopeIds:null,children:n,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetAnchor:null,staticCount:0,shapeFlag:i,patchFlag:r,dynamicProps:o,dynamicChildren:null,appContext:null,ctx:Vo};return l?(pE(s,n),i&128&&e.normalize(s)):n&&(s.shapeFlag|=Yt(n)?8:16),Dm>0&&!a&&ql&&(s.patchFlag>0||i&6)&&s.patchFlag!==32&&ql.push(s),s}const Y=UQ;function UQ(e,t=null,n=null,r=0,o=null,i=!1){if((!e||e===OI)&&(e=Ui),Er(e)){const l=Ru(e,t,!0);return n&&pE(l,n),Dm>0&&!i&&ql&&(l.shapeFlag&6?ql[ql.indexOf(e)]=l:ql.push(l)),l.patchFlag|=-2,l}if(ZQ(e)&&(e=e.__vccOpts),t){t=fv(t);let{class:l,style:s}=t;l&&!Yt(l)&&(t.class=re(l)),hn(s)&&(yI(s)&&!Mt(s)&&(s=Ho({},s)),t.style=bt(s))}const a=Yt(e)?1:dQ(e)?128:jQ(e)?64:hn(e)?4:jt(e)?2:0;return $(e,t,n,r,o,a,i,!0)}function fv(e){return e?yI(e)||zb in e?Ho({},e):e:null}function Ru(e,t,n=!1){const{props:r,ref:o,patchFlag:i,children:a}=e,l=t?Qt(r||{},t):r;return{__v_isVNode:!0,__v_skip:!0,type:e.type,props:l,key:l&&XI(l),ref:t&&t.ref?n&&o?Mt(o)?o.concat(q2(t)):[o,q2(t)]:q2(t):o,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:a,target:e.target,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==at?i===-1?16:i|16:i,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:e.transition,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&Ru(e.ssContent),ssFallback:e.ssFallback&&Ru(e.ssFallback),el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce}}function ot(e=" ",t=0){return Y(dv,null,e,t)}function xe(e="",t=!1){return t?(R(),be(Ui,null,e)):Y(Ui,null,e)}function ks(e){return e==null||typeof e=="boolean"?Y(Ui):Mt(e)?Y(at,null,e.slice()):typeof e=="object"?wc(e):Y(dv,null,String(e))}function wc(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:Ru(e)}function pE(e,t){let n=0;const{shapeFlag:r}=e;if(t==null)t=null;else if(Mt(t))n=16;else if(typeof t=="object")if(r&65){const o=t.default;o&&(o._c&&(o._d=!1),pE(e,o()),o._c&&(o._d=!0));return}else{n=32;const o=t._;!o&&!(zb in t)?t._ctx=Vo:o===3&&Vo&&(Vo.slots._===1?t._=1:(t._=2,e.patchFlag|=1024))}else jt(t)?(t={default:t,_ctx:Vo},n=32):(t=String(t),r&64?(n=16,t=[ot(t)]):n=8);e.children=t,e.shapeFlag|=n}function Qt(...e){const t={};for(let n=0;nIo||Vo;let hE,bp,Xk="__VUE_INSTANCE_SETTERS__";(bp=Y8()[Xk])||(bp=Y8()[Xk]=[]),bp.push(e=>Io=e),hE=e=>{bp.length>1?bp.forEach(t=>t(e)):bp[0](e)};const Ah=e=>{hE(e),e.scope.on()},vf=()=>{Io&&Io.scope.off(),hE(null)};function QI(e){return e.vnode.shapeFlag&4}let Nm=!1;function qQ(e,t=!1){Nm=t;const{props:n,children:r}=e.vnode,o=QI(e);NQ(e,n,o,t),PQ(e,r);const i=o?XQ(e,t):void 0;return Nm=!1,i}function XQ(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=Yr(new Proxy(e.ctx,CQ));const{setup:r}=n;if(r){const o=e.setupContext=r.length>1?ZI(e):null;Ah(e),sv();const i=zc(r,e,0,[e.props,o]);if(uv(),vf(),Em(i)){if(i.then(vf,vf),t)return i.then(a=>{Qk(e,a)}).catch(a=>{Fb(a,e,0)});e.asyncDep=i}else Qk(e,i)}else JI(e)}function Qk(e,t,n){jt(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:hn(t)&&(e.setupState=wI(t)),JI(e)}function JI(e,t,n){const r=e.type;e.render||(e.render=r.render||lr);{Ah(e),sv();try{EQ(e)}finally{uv(),vf()}}}function QQ(e){return e.attrsProxy||(e.attrsProxy=new Proxy(e.attrs,{get(t,n){return wa(e,"get","$attrs"),t[n]}}))}function ZI(e){const t=n=>{e.exposed=n||{}};return{get attrs(){return QQ(e)},slots:e.slots,emit:e.emit,expose:t}}function jb(e){if(e.exposed)return e.exposeProxy||(e.exposeProxy=new Proxy(wI(Yr(e.exposed)),{get(t,n){if(n in t)return t[n];if(n in W0)return W0[n](e)},has(t,n){return n in t||n in W0}}))}function JQ(e,t=!0){return jt(e)?e.displayName||e.name:e.name||t&&e.__name}function ZQ(e){return jt(e)&&"__vccOpts"in e}const G=(e,t)=>CI(e,t,Nm);function Tt(e,t,n){const r=arguments.length;return r===2?hn(t)&&!Mt(t)?Er(t)?Y(e,null,[t]):Y(e,t):Y(e,null,t):(r>3?n=Array.prototype.slice.call(arguments,2):r===3&&Er(n)&&(n=[n]),Y(e,t,n))}const eJ=Symbol.for("v-scx"),tJ=()=>kt(eJ),nJ="3.3.9",rJ="http://www.w3.org/2000/svg",qd=typeof document<"u"?document:null,Jk=qd&&qd.createElement("template"),oJ={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,r)=>{const o=t?qd.createElementNS(rJ,e):qd.createElement(e,n?{is:n}:void 0);return e==="select"&&r&&r.multiple!=null&&o.setAttribute("multiple",r.multiple),o},createText:e=>qd.createTextNode(e),createComment:e=>qd.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>qd.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,n,r,o,i){const a=n?n.previousSibling:t.lastChild;if(o&&(o===i||o.nextSibling))for(;t.insertBefore(o.cloneNode(!0),n),!(o===i||!(o=o.nextSibling)););else{Jk.innerHTML=r?`${e}`:e;const l=Jk.content;if(r){const s=l.firstChild;for(;s.firstChild;)l.appendChild(s.firstChild);l.removeChild(s)}t.insertBefore(l,n)}return[a?a.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}},fc="transition",f0="animation",Sh=Symbol("_vtc"),Gr=(e,{slots:t})=>Tt(vQ,tL(e),t);Gr.displayName="Transition";const eL={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String},iJ=Gr.props=Ho({},FI,eL),Rd=(e,t=[])=>{Mt(e)?e.forEach(n=>n(...t)):e&&e(...t)},Zk=e=>e?Mt(e)?e.some(t=>t.length>1):e.length>1:!1;function tL(e){const t={};for(const L in e)L in eL||(t[L]=e[L]);if(e.css===!1)return t;const{name:n="v",type:r,duration:o,enterFromClass:i=`${n}-enter-from`,enterActiveClass:a=`${n}-enter-active`,enterToClass:l=`${n}-enter-to`,appearFromClass:s=i,appearActiveClass:u=a,appearToClass:c=l,leaveFromClass:d=`${n}-leave-from`,leaveActiveClass:f=`${n}-leave-active`,leaveToClass:h=`${n}-leave-to`}=e,v=aJ(o),m=v&&v[0],y=v&&v[1],{onBeforeEnter:g,onEnter:b,onEnterCancelled:x,onLeave:C,onLeaveCancelled:E,onBeforeAppear:A=g,onAppear:M=b,onAppearCancelled:D=x}=t,I=(L,P,j)=>{hc(L,P?c:l),hc(L,P?u:a),j&&j()},T=(L,P)=>{L._isLeaving=!1,hc(L,d),hc(L,h),hc(L,f),P&&P()},N=L=>(P,j)=>{const O=L?M:b,z=()=>I(P,L,j);Rd(O,[P,z]),eM(()=>{hc(P,L?s:i),vu(P,L?c:l),Zk(O)||tM(P,r,m,z)})};return Ho(t,{onBeforeEnter(L){Rd(g,[L]),vu(L,i),vu(L,a)},onBeforeAppear(L){Rd(A,[L]),vu(L,s),vu(L,u)},onEnter:N(!1),onAppear:N(!0),onLeave(L,P){L._isLeaving=!0;const j=()=>T(L,P);vu(L,d),rL(),vu(L,f),eM(()=>{L._isLeaving&&(hc(L,d),vu(L,h),Zk(C)||tM(L,r,y,j))}),Rd(C,[L,j])},onEnterCancelled(L){I(L,!1),Rd(x,[L])},onAppearCancelled(L){I(L,!0),Rd(D,[L])},onLeaveCancelled(L){T(L),Rd(E,[L])}})}function aJ(e){if(e==null)return null;if(hn(e))return[i3(e.enter),i3(e.leave)];{const t=i3(e);return[t,t]}}function i3(e){return _X(e)}function vu(e,t){t.split(/\s+/).forEach(n=>n&&e.classList.add(n)),(e[Sh]||(e[Sh]=new Set)).add(t)}function hc(e,t){t.split(/\s+/).forEach(r=>r&&e.classList.remove(r));const n=e[Sh];n&&(n.delete(t),n.size||(e[Sh]=void 0))}function eM(e){requestAnimationFrame(()=>{requestAnimationFrame(e)})}let lJ=0;function tM(e,t,n,r){const o=e._endId=++lJ,i=()=>{o===e._endId&&r()};if(n)return setTimeout(i,n);const{type:a,timeout:l,propCount:s}=nL(e,t);if(!a)return r();const u=a+"end";let c=0;const d=()=>{e.removeEventListener(u,f),i()},f=h=>{h.target===e&&++c>=s&&d()};setTimeout(()=>{c(n[v]||"").split(", "),o=r(`${fc}Delay`),i=r(`${fc}Duration`),a=nM(o,i),l=r(`${f0}Delay`),s=r(`${f0}Duration`),u=nM(l,s);let c=null,d=0,f=0;t===fc?a>0&&(c=fc,d=a,f=i.length):t===f0?u>0&&(c=f0,d=u,f=s.length):(d=Math.max(a,u),c=d>0?a>u?fc:f0:null,f=c?c===fc?i.length:s.length:0);const h=c===fc&&/\b(transform|all)(,|$)/.test(r(`${fc}Property`).toString());return{type:c,timeout:d,propCount:f,hasTransform:h}}function nM(e,t){for(;e.lengthrM(n)+rM(e[r])))}function rM(e){return e==="auto"?0:Number(e.slice(0,-1).replace(",","."))*1e3}function rL(){return document.body.offsetHeight}function sJ(e,t,n){const r=e[Sh];r&&(t=(t?[t,...r]:[...r]).join(" ")),t==null?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}const vE=Symbol("_vod"),rn={beforeMount(e,{value:t},{transition:n}){e[vE]=e.style.display==="none"?"":e.style.display,n&&t?n.beforeEnter(e):p0(e,t)},mounted(e,{value:t},{transition:n}){n&&t&&n.enter(e)},updated(e,{value:t,oldValue:n},{transition:r}){!t!=!n&&(r?t?(r.beforeEnter(e),p0(e,!0),r.enter(e)):r.leave(e,()=>{p0(e,!1)}):p0(e,t))},beforeUnmount(e,{value:t}){p0(e,t)}};function p0(e,t){e.style.display=t?e[vE]:"none"}function uJ(e,t,n){const r=e.style,o=Yt(n);if(n&&!o){if(t&&!Yt(t))for(const i in t)n[i]==null&&o5(r,i,"");for(const i in n)o5(r,i,n[i])}else{const i=r.display;o?t!==n&&(r.cssText=n):t&&e.removeAttribute("style"),vE in e&&(r.display=i)}}const oM=/\s*!important$/;function o5(e,t,n){if(Mt(n))n.forEach(r=>o5(e,t,r));else if(n==null&&(n=""),t.startsWith("--"))e.setProperty(t,n);else{const r=cJ(e,t);oM.test(n)?e.setProperty(ad(r),n.replace(oM,""),"important"):e[r]=n}}const iM=["Webkit","Moz","ms"],a3={};function cJ(e,t){const n=a3[t];if(n)return n;let r=ml(t);if(r!=="filter"&&r in e)return a3[t]=r;r=Eg(r);for(let o=0;ol3||(mJ.then(()=>l3=0),l3=Date.now());function yJ(e,t){const n=r=>{if(!r._vts)r._vts=Date.now();else if(r._vts<=n.attached)return;ul(_J(r,n.value),t,5,[r])};return n.value=e,n.attached=gJ(),n}function _J(e,t){if(Mt(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map(r=>o=>!o._stopped&&r&&r(o))}else return t}const uM=/^on[a-z]/,bJ=(e,t,n,r,o=!1,i,a,l,s)=>{t==="class"?sJ(e,r,o):t==="style"?uJ(e,n,r):Mb(t)?KC(t)||hJ(e,t,n,r,a):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):wJ(e,t,r,o))?fJ(e,t,r,i,a,l,s):(t==="true-value"?e._trueValue=r:t==="false-value"&&(e._falseValue=r),dJ(e,t,r,o))};function wJ(e,t,n,r){return r?!!(t==="innerHTML"||t==="textContent"||t in e&&uM.test(t)&&jt(n)):t==="spellcheck"||t==="draggable"||t==="translate"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA"||uM.test(t)&&Yt(n)?!1:t in e}const oL=new WeakMap,iL=new WeakMap,e_=Symbol("_moveCb"),cM=Symbol("_enterCb"),aL={name:"TransitionGroup",props:Ho({},iJ,{tag:String,moveClass:String}),setup(e,{slots:t}){const n=Kt(),r=NI();let o,i;return Gs(()=>{if(!o.length)return;const a=e.moveClass||`${e.name||"v"}-move`;if(!SJ(o[0].el,n.vnode.el,a))return;o.forEach(CJ),o.forEach(EJ);const l=o.filter(AJ);rL(),l.forEach(s=>{const u=s.el,c=u.style;vu(u,a),c.transform=c.webkitTransform=c.transitionDuration="";const d=u[e_]=f=>{f&&f.target!==u||(!f||/transform$/.test(f.propertyName))&&(u.removeEventListener("transitionend",d),u[e_]=null,hc(u,a))};u.addEventListener("transitionend",d)})}),()=>{const a=Xn(e),l=tL(a);let s=a.tag||at;o=i,i=t.default?cE(t.default()):[];for(let u=0;udelete e.mode;aL.props;const mE=aL;function CJ(e){const t=e.el;t[e_]&&t[e_](),t[cM]&&t[cM]()}function EJ(e){iL.set(e,e.el.getBoundingClientRect())}function AJ(e){const t=oL.get(e),n=iL.get(e),r=t.left-n.left,o=t.top-n.top;if(r||o){const i=e.el.style;return i.transform=i.webkitTransform=`translate(${r}px,${o}px)`,i.transitionDuration="0s",e}}function SJ(e,t,n){const r=e.cloneNode(),o=e[Sh];o&&o.forEach(l=>{l.split(/\s+/).forEach(s=>s&&r.classList.remove(s))}),n.split(/\s+/).forEach(l=>l&&r.classList.add(l)),r.style.display="none";const i=t.nodeType===1?t:t.parentNode;i.appendChild(r);const{hasTransform:a}=nL(r);return i.removeChild(r),a}const kh=e=>{const t=e.props["onUpdate:modelValue"]||!1;return Mt(t)?n=>G2(t,n):t};function kJ(e){e.target.composing=!0}function dM(e){const t=e.target;t.composing&&(t.composing=!1,t.dispatchEvent(new Event("input")))}const ku=Symbol("_assign"),Vb={created(e,{modifiers:{lazy:t,trim:n,number:r}},o){e[ku]=kh(o);const i=r||o.props&&o.props.type==="number";kc(e,t?"change":"input",a=>{if(a.target.composing)return;let l=e.value;n&&(l=l.trim()),i&&(l=K8(l)),e[ku](l)}),n&&kc(e,"change",()=>{e.value=e.value.trim()}),t||(kc(e,"compositionstart",kJ),kc(e,"compositionend",dM),kc(e,"change",dM))},mounted(e,{value:t}){e.value=t??""},beforeUpdate(e,{value:t,modifiers:{lazy:n,trim:r,number:o}},i){if(e[ku]=kh(i),e.composing)return;const a=o||e.type==="number"?K8(e.value):e.value,l=t??"";a!==l&&(document.activeElement===e&&e.type!=="range"&&(n||r&&e.value.trim()===l)||(e.value=l))}},t_={deep:!0,created(e,t,n){e[ku]=kh(n),kc(e,"change",()=>{const r=e._modelValue,o=sL(e),i=e.checked,a=e[ku];if(Mt(r)){const l=iI(r,o),s=l!==-1;if(i&&!s)a(r.concat(o));else if(!i&&s){const u=[...r];u.splice(l,1),a(u)}}else if(Tb(r)){const l=new Set(r);i?l.add(o):l.delete(o),a(l)}else a(uL(e,i))})},mounted:fM,beforeUpdate(e,t,n){e[ku]=kh(n),fM(e,t,n)}};function fM(e,{value:t,oldValue:n},r){e._modelValue=t,Mt(t)?e.checked=iI(t,r.props.value)>-1:Tb(t)?e.checked=t.has(r.props.value):t!==n&&(e.checked=Ch(t,uL(e,!0)))}const lL={created(e,{value:t},n){e.checked=Ch(t,n.props.value),e[ku]=kh(n),kc(e,"change",()=>{e[ku](sL(e))})},beforeUpdate(e,{value:t,oldValue:n},r){e[ku]=kh(r),t!==n&&(e.checked=Ch(t,r.props.value))}};function sL(e){return"_value"in e?e._value:e.value}function uL(e,t){const n=t?"_trueValue":"_falseValue";return n in e?e[n]:t}const MJ=["ctrl","shift","alt","meta"],TJ={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&e.button!==0,middle:e=>"button"in e&&e.button!==1,right:e=>"button"in e&&e.button!==2,exact:(e,t)=>MJ.some(n=>e[`${n}Key`]&&!t.includes(n))},St=(e,t)=>(n,...r)=>{for(let o=0;on=>{if(!("key"in n))return;const r=ad(n.key);if(t.some(o=>o===r||OJ[o]===r))return e(n)},DJ=Ho({patchProp:bJ},oJ);let pM;function cL(){return pM||(pM=LQ(DJ))}const Pu=(...e)=>{cL().render(...e)},kg=(...e)=>{const t=cL().createApp(...e),{mount:n}=t;return t.mount=r=>{const o=NJ(r);if(!o)return;const i=t._component;!jt(i)&&!i.render&&!i.template&&(i.template=o.innerHTML),o.innerHTML="";const a=n(o,!1,o instanceof SVGElement);return o instanceof Element&&(o.removeAttribute("v-cloak"),o.setAttribute("data-v-app","")),a},t};function NJ(e){return Yt(e)?document.querySelector(e):e}const dL=Symbol(),X2="el",FJ="is-",Pd=(e,t,n,r,o)=>{let i=`${e}-${t}`;return n&&(i+=`-${n}`),r&&(i+=`__${r}`),o&&(i+=`--${o}`),i},fL=Symbol("namespaceContextKey"),RJ=e=>{const t=e||(Kt()?kt(fL,se(X2)):se(X2));return G(()=>w(t)||X2)},gE=(e,t)=>{const n=RJ(t);return{namespace:n,b:(m="")=>Pd(n.value,e,m,"",""),e:m=>m?Pd(n.value,e,"",m,""):"",m:m=>m?Pd(n.value,e,"","",m):"",be:(m,y)=>m&&y?Pd(n.value,e,m,y,""):"",em:(m,y)=>m&&y?Pd(n.value,e,"",m,y):"",bm:(m,y)=>m&&y?Pd(n.value,e,m,"",y):"",bem:(m,y,g)=>m&&y&&g?Pd(n.value,e,m,y,g):"",is:(m,...y)=>{const g=y.length>=1?y[0]:!0;return m&&g?`${FJ}${m}`:""},cssVar:m=>{const y={};for(const g in m)m[g]&&(y[`--${n.value}-${g}`]=m[g]);return y},cssVarName:m=>`--${n.value}-${m}`,cssVarBlock:m=>{const y={};for(const g in m)m[g]&&(y[`--${n.value}-${e}-${g}`]=m[g]);return y},cssVarBlockName:m=>`--${n.value}-${e}-${m}`}};var pL=typeof global=="object"&&global&&global.Object===Object&&global,PJ=typeof self=="object"&&self&&self.Object===Object&&self,Yo=pL||PJ||Function("return this")(),ki=Yo.Symbol,hL=Object.prototype,IJ=hL.hasOwnProperty,LJ=hL.toString,h0=ki?ki.toStringTag:void 0;function BJ(e){var t=IJ.call(e,h0),n=e[h0];try{e[h0]=void 0;var r=!0}catch{}var o=LJ.call(e);return r&&(t?e[h0]=n:delete e[h0]),o}var $J=Object.prototype,zJ=$J.toString;function jJ(e){return zJ.call(e)}var VJ="[object Null]",HJ="[object Undefined]",hM=ki?ki.toStringTag:void 0;function Ji(e){return e==null?e===void 0?HJ:VJ:hM&&hM in Object(e)?BJ(e):jJ(e)}function co(e){return e!=null&&typeof e=="object"}var WJ="[object Symbol]";function Wa(e){return typeof e=="symbol"||co(e)&&Ji(e)==WJ}var UJ=NaN;function vM(e){return typeof e=="number"?e:Wa(e)?UJ:+e}function Jr(e,t){for(var n=-1,r=e==null?0:e.length,o=Array(r);++n0){if(++t>=kZ)return arguments[0]}else t=0;return e.apply(void 0,arguments)}}var CL=xL(yL),OZ=/\{\n\/\* \[wrapped with (.+)\] \*/,DZ=/,? & /;function NZ(e){var t=e.match(OZ);return t?t[1].split(DZ):[]}var FZ=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/;function RZ(e,t){var n=t.length;if(!n)return e;var r=n-1;return t[r]=(n>1?"& ":"")+t[r],t=t.join(n>2?", ":" "),e.replace(FZ,`{ +/* [wrapped with `+t+`] */ +`)}function _E(e){return function(){return e}}var r_=function(){try{var e=Wf(Object,"defineProperty");return e({},"",{}),e}catch{}}(),PZ=r_?function(e,t){return r_(e,"toString",{configurable:!0,enumerable:!1,value:_E(t),writable:!0})}:Aa,bE=xL(PZ);function Al(e,t){for(var n=-1,r=e==null?0:e.length;++n-1}var LZ=1,BZ=2,$Z=8,zZ=16,jZ=32,VZ=64,HZ=128,WZ=256,UZ=512,KZ=[["ary",HZ],["bind",LZ],["bindKey",BZ],["curry",$Z],["curryRight",zZ],["flip",UZ],["partial",jZ],["partialRight",VZ],["rearg",WZ]];function YZ(e,t){return Al(KZ,function(n){var r="_."+n[0];t&n[1]&&!Kb(e,r)&&e.push(r)}),e.sort()}function AL(e,t,n){var r=t+"";return bE(e,RZ(r,YZ(NZ(r),n)))}var GZ=4,qZ=8,xM=32,CM=64;function SL(e,t,n,r,o,i,a,l,s,u){var c=t&qZ,d=c?a:void 0,f=c?void 0:a,h=c?i:void 0,v=c?void 0:i;t|=c?xM:CM,t&=~(c?CM:xM),t&GZ||(t&=-4);var m=[e,t,o,h,d,v,f,l,s,u],y=n.apply(void 0,m);return i5(e)&&CL(y,m),y.placeholder=r,AL(y,e,t)}function vv(e){var t=e;return t.placeholder}var XZ=9007199254740991,QZ=/^(?:0|[1-9]\d*)$/;function Wu(e,t){var n=typeof e;return t=t??XZ,!!t&&(n=="number"||n!="symbol"&&QZ.test(e))&&e>-1&&e%1==0&&e1&&b.reverse(),c&&s-1&&e%1==0&&e<=_ee}function Sa(e){return e!=null&&Gb(e.length)&&!Hu(e)}function Gi(e,t,n){if(!eo(n))return!1;var r=typeof t;return(r=="number"?Sa(n)&&Wu(t,n.length):r=="string"&&t in n)?fs(n[t],e):!1}function mv(e){return or(function(t,n){var r=-1,o=n.length,i=o>1?n[o-1]:void 0,a=o>2?n[2]:void 0;for(i=e.length>3&&typeof i=="function"?(o--,i):void 0,a&&Gi(n[0],n[1],a)&&(i=o<3?void 0:i,o=1),t=Object(t);++r-1}function Ote(e,t){var n=this.__data__,r=qb(n,e);return r<0?(++this.size,n.push([e,t])):n[r][1]=t,this}function Yu(e){var t=-1,n=e==null?0:e.length;for(this.clear();++t0&&n(l)?t>1?ci(l,t-1,n,r,o):sd(o,l):r||(o[o.length]=l)}return o}function SE(e){var t=e==null?0:e.length;return t?ci(e,1):[]}function qu(e){return bE(ML(e,void 0,SE),e+"")}var Hte=qu(AE),Qb=FL(Object.getPrototypeOf,Object),Wte="[object Object]",Ute=Function.prototype,Kte=Object.prototype,PL=Ute.toString,Yte=Kte.hasOwnProperty,Gte=PL.call(Object);function Dg(e){if(!co(e)||Ji(e)!=Wte)return!1;var t=Qb(e);if(t===null)return!0;var n=Yte.call(t,"constructor")&&t.constructor;return typeof n=="function"&&n instanceof n&&PL.call(n)==Gte}var qte="[object DOMException]",Xte="[object Error]";function kE(e){if(!co(e))return!1;var t=Ji(e);return t==Xte||t==qte||typeof e.message=="string"&&typeof e.name=="string"&&!Dg(e)}var IL=or(function(e,t){try{return Ka(e,void 0,t)}catch(n){return kE(n)?n:new Error(n)}}),Qte="Expected a function";function LL(e,t){var n;if(typeof t!="function")throw new TypeError(Qte);return e=er(e),function(){return--e>0&&(n=t.apply(this,arguments)),e<=1&&(t=void 0),n}}var Jte=1,Zte=32,Jb=or(function(e,t,n){var r=Jte;if(n.length){var o=Yc(n,vv(Jb));r|=Zte}return Uu(e,r,t,n,o)});Jb.placeholder={};var ene=qu(function(e,t){return Al(t,function(n){n=Qs(n),Ku(e,n,Jb(e[n],e))}),e}),tne=1,nne=2,rne=32,ME=or(function(e,t,n){var r=tne|nne;if(n.length){var o=Yc(n,vv(ME));r|=rne}return Uu(t,r,e,n,o)});ME.placeholder={};function _l(e,t,n){var r=-1,o=e.length;t<0&&(t=-t>o?0:o+t),n=n>o?o:n,n<0&&(n+=o),o=t>n?0:n-t>>>0,t>>>=0;for(var i=Array(o);++r=r?e:_l(e,t,n)}var one="\\ud800-\\udfff",ine="\\u0300-\\u036f",ane="\\ufe20-\\ufe2f",lne="\\u20d0-\\u20ff",sne=ine+ane+lne,une="\\ufe0e\\ufe0f",cne="\\u200d",dne=RegExp("["+cne+one+sne+une+"]");function yv(e){return dne.test(e)}function fne(e){return e.split("")}var BL="\\ud800-\\udfff",pne="\\u0300-\\u036f",hne="\\ufe20-\\ufe2f",vne="\\u20d0-\\u20ff",mne=pne+hne+vne,gne="\\ufe0e\\ufe0f",yne="["+BL+"]",l5="["+mne+"]",s5="\\ud83c[\\udffb-\\udfff]",_ne="(?:"+l5+"|"+s5+")",$L="[^"+BL+"]",zL="(?:\\ud83c[\\udde6-\\uddff]){2}",jL="[\\ud800-\\udbff][\\udc00-\\udfff]",bne="\\u200d",VL=_ne+"?",HL="["+gne+"]?",wne="(?:"+bne+"(?:"+[$L,zL,jL].join("|")+")"+HL+VL+")*",xne=HL+VL+wne,Cne="(?:"+[$L+l5+"?",l5,zL,jL,yne].join("|")+")",Ene=RegExp(s5+"(?="+s5+")|"+Cne+xne,"g");function Ane(e){return e.match(Ene)||[]}function ss(e){return yv(e)?Ane(e):fne(e)}function WL(e){return function(t){t=Nr(t);var n=yv(t)?ss(t):void 0,r=n?n[0]:t.charAt(0),o=n?ud(n,1).join(""):t.slice(1);return r[e]()+o}}var TE=WL("toUpperCase");function UL(e){return TE(Nr(e).toLowerCase())}function OE(e,t,n,r){var o=-1,i=e==null?0:e.length;for(r&&i&&(n=e[++o]);++o=t?e:t)),e}function gre(e,t,n){return n===void 0&&(n=t,t=void 0),n!==void 0&&(n=cl(n),n=n===n?n:0),t!==void 0&&(t=cl(t),t=t===t?t:0),Kf(cl(e),t,n)}function yre(){this.__data__=new Yu,this.size=0}function _re(e){var t=this.__data__,n=t.delete(e);return this.size=t.size,n}function bre(e){return this.__data__.get(e)}function wre(e){return this.__data__.has(e)}var xre=200;function Cre(e,t){var n=this.__data__;if(n instanceof Yu){var r=n.__data__;if(!Im||r.lengthl))return!1;var u=i.get(e),c=i.get(t);if(u&&c)return u==t&&c==e;var d=-1,f=!0,h=n&tie?new Tf:void 0;for(i.set(e,t),i.set(t,e);++d=t||M<0||d&&D>=i}function g(){var A=Z2();if(y(A))return b(A);l=setTimeout(g,m(A))}function b(A){return l=void 0,f&&r?h(A):(r=o=void 0,a)}function x(){l!==void 0&&clearTimeout(l),u=0,r=s=o=l=void 0}function C(){return l===void 0?a:b(Z2())}function E(){var A=Z2(),M=y(A);if(r=arguments,o=this,s=A,M){if(l===void 0)return v(s);if(d)return clearTimeout(l),l=setTimeout(g,t),h(s)}return l===void 0&&(l=setTimeout(g,t)),a}return E.cancel=x,E.flush=C,E}function Kie(e,t){return e==null||e!==e?t:e}var OB=Object.prototype,Yie=OB.hasOwnProperty,Gie=or(function(e,t){e=Object(e);var n=-1,r=t.length,o=r>2?t[2]:void 0;for(o&&Gi(t[0],t[1],o)&&(r=1);++n=eae&&(i=Lm,a=!1,t=new Tf(t));e:for(;++o=0&&e.slice(n,o)==t}function fae(e,t){return Jr(t,function(n){return[n,e[n]]})}function pae(e){var t=-1,n=Array(e.size);return e.forEach(function(r){n[++t]=[r,r]}),n}var hae="[object Map]",vae="[object Set]";function LB(e){return function(t){var n=wi(t);return n==hae?BE(t):n==vae?pae(t):fae(t,e(t))}}var Xu=LB(Go),mae=LB(ka),gae={"&":"&","<":"<",">":">",'"':""","'":"'"},yae=DE(gae),BB=/[&<>"']/g,_ae=RegExp(BB.source);function $B(e){return e=Nr(e),e&&_ae.test(e)?e.replace(BB,yae):e}var zB=/[\\^$.*+?()[\]{}|]/g,bae=RegExp(zB.source);function wae(e){return e=Nr(e),e&&bae.test(e)?e.replace(zB,"\\$&"):e}function jB(e,t){for(var n=-1,r=e==null?0:e.length;++no?0:o+n),r=r===void 0||r>o?o:er(r),r<0&&(r+=o),r=n>r?0:VB(r);n-1?o[i?t[a]:a]:void 0}}var Mae=Math.max;function GE(e,t,n){var r=e==null?0:e.length;if(!r)return-1;var o=n==null?0:er(n);return o<0&&(o=Mae(r+o,0)),Ub(e,Un(t),o)}var Xc=WB(GE);function UB(e,t,n){var r;return n(e,function(o,i,a){if(t(o,i,a))return r=i,!1}),r}function Tae(e,t){return UB(e,Un(t),Js)}var Oae=Math.max,Dae=Math.min;function qE(e,t,n){var r=e==null?0:e.length;if(!r)return-1;var o=r-1;return n!==void 0&&(o=er(n),o=n<0?Oae(r+o,0):Dae(o,r-1)),Ub(e,Un(t),o,!0)}var Nae=WB(qE);function Fae(e,t){return UB(e,Un(t),YE)}function Rae(e){return e&&e.length?e[0]:void 0}function KB(e,t){var n=-1,r=Sa(e)?Array(e.length):[];return dd(e,function(o,i,a){r[++n]=t(o,i,a)}),r}function qa(e,t){var n=Wn(e)?Jr:KB;return n(e,Un(t))}function YB(e,t){return ci(qa(e,t),1)}var Pae=1/0;function Iae(e,t){return ci(qa(e,t),Pae)}function Lae(e,t,n){return n=n===void 0?1:er(n),ci(qa(e,t),n)}var Bae=1/0;function GB(e){var t=e==null?0:e.length;return t?ci(e,Bae):[]}function $ae(e,t){var n=e==null?0:e.length;return n?(t=t===void 0?1:er(t),ci(e,t)):[]}var zae=512;function jae(e){return Uu(e,zae)}var Vae=NE("floor"),Hae="Expected a function",Wae=8,Uae=32,Kae=128,Yae=256;function qB(e){return qu(function(t){var n=t.length,r=n,o=gl.prototype.thru;for(e&&t.reverse();r--;){var i=t[r];if(typeof i!="function")throw new TypeError(Hae);if(o&&!a&&J2(i)=="wrapper")var a=new gl([],!0)}for(r=a?r:n;++rt}function o4(e){return function(t,n){return typeof t=="string"&&typeof n=="string"||(t=cl(t),n=cl(n)),e(t,n)}}var ile=o4(XE),ale=o4(function(e,t){return e>=t}),lle=Object.prototype,sle=lle.hasOwnProperty;function ule(e,t){return e!=null&&sle.call(e,t)}function i_(e,t){return e!=null&&EB(e,t,ule)}var cle=Math.max,dle=Math.min;function fle(e,t,n){return e>=dle(t,n)&&e-1:!!o&&hv(e,t,n)>-1}var gle=Math.max;function XB(e,t,n){var r=e==null?0:e.length;if(!r)return-1;var o=n==null?0:er(n);return o<0&&(o=gle(r+o,0)),hv(e,t,o)}function yle(e){var t=e==null?0:e.length;return t?_l(e,0,-1):[]}var _le=Math.min;function JE(e,t,n){for(var r=n?KE:Kb,o=e[0].length,i=e.length,a=i,l=Array(i),s=1/0,u=[];a--;){var c=e[a];a&&t&&(c=Jr(c,Ya(t))),s=_le(c.length,s),l[a]=!n&&(t||o>=120&&c.length>=120)?new Tf(a&&c):void 0}c=e[0];var d=-1,f=l[0];e:for(;++d=-uT&&e<=uT}function Iu(e){return e===void 0}var ose="[object WeakMap]";function ise(e){return co(e)&&wi(e)==ose}var ase="[object WeakSet]";function lse(e){return co(e)&&Ji(e)==ase}var sse=1;function use(e){return Un(typeof e=="function"?e:dl(e,sse))}var cse=Array.prototype,dse=cse.join;function fse(e,t){return e==null?"":dse.call(e,t)}var pse=_v(function(e,t,n){return e+(n?"-":"")+t.toLowerCase()}),hse=e4(function(e,t,n){Ku(e,n,t)});function vse(e,t,n){for(var r=n+1;r--;)if(e[r]===t)return r;return r}var mse=Math.max,gse=Math.min;function yse(e,t,n){var r=e==null?0:e.length;if(!r)return-1;var o=r;return n!==void 0&&(o=er(n),o=o<0?mse(r+o,0):gse(o,r-1)),t===t?vse(e,t,o):Ub(e,EL,o,!0)}var _se=_v(function(e,t,n){return e+(n?" ":"")+t.toLowerCase()}),bse=WL("toLowerCase");function tA(e,t){return e=this.__values__.length,t=e?void 0:this.__values__[this.__index__++];return{done:e,value:t}}function i$(e,t){var n=e.length;if(n)return t+=t<0?n:0,Wu(t,n)?e[t]:void 0}function Wse(e,t){return e&&e.length?i$(e,er(t)):void 0}function Use(e){return e=er(e),or(function(t){return i$(t,e)})}function rA(e,t){return t=ld(t,e),e=ZB(e,t),e==null||delete e[Qs(Ga(t))]}function Kse(e){return Dg(e)?void 0:e}var Yse=1,Gse=2,qse=4,Xse=qu(function(e,t){var n={};if(e==null)return n;var r=!1;t=Jr(t,function(i){return i=ld(i,e),r||(r=i.length>1),i}),Xs(e,PE(e),n),r&&(n=dl(n,Yse|Gse|qse,Kse));for(var o=t.length;o--;)rA(n,t[o]);return n});function Pg(e,t,n,r){if(!eo(e))return e;t=ld(t,e);for(var o=-1,i=t.length,a=i-1,l=e;l!=null&&++ot||i&&a&&s&&!l&&!u||r&&a&&s||!n&&s||!o)return 1;if(!r&&!i&&!u&&e=l)return s;var u=n[r];return s*(u=="desc"?-1:1)}}return e.index-t.index}function u$(e,t,n){t.length?t=Jr(t,function(i){return Wn(i)?function(a){return Uf(a,i.length===1?i[0]:i)}:i}):t=[Aa];var r=-1;t=Jr(t,Ya(Un));var o=KB(e,function(i,a,l){var s=Jr(t,function(u){return u(i)});return{criteria:s,index:++r,value:i}});return Zse(o,function(i,a){return eue(i,a,n)})}function tue(e,t,n,r){return e==null?[]:(Wn(t)||(t=t==null?[]:[t]),n=r?void 0:n,Wn(n)||(n=n==null?[]:[n]),u$(e,t,n))}function oA(e){return qu(function(t){return t=Jr(t,Ya(Un)),or(function(n){var r=this;return e(t,function(o){return Ka(o,r,n)})})})}var nue=oA(Jr),rue=or,oue=Math.min,iue=rue(function(e,t){t=t.length==1&&Wn(t[0])?Jr(t[0],Ya(Un)):Jr(ci(t,1),Ya(Un));var n=t.length;return or(function(r){for(var o=-1,i=oue(r.length,n);++osue)return n;do t%2&&(n+=e),t=uue(t/2),t&&(e+=e);while(t);return n}var cue=VE("length"),c$="\\ud800-\\udfff",due="\\u0300-\\u036f",fue="\\ufe20-\\ufe2f",pue="\\u20d0-\\u20ff",hue=due+fue+pue,vue="\\ufe0e\\ufe0f",mue="["+c$+"]",m5="["+hue+"]",g5="\\ud83c[\\udffb-\\udfff]",gue="(?:"+m5+"|"+g5+")",d$="[^"+c$+"]",f$="(?:\\ud83c[\\udde6-\\uddff]){2}",p$="[\\ud800-\\udbff][\\udc00-\\udfff]",yue="\\u200d",h$=gue+"?",v$="["+vue+"]?",_ue="(?:"+yue+"(?:"+[d$,f$,p$].join("|")+")"+v$+h$+")*",bue=v$+h$+_ue,wue="(?:"+[d$+m5+"?",m5,f$,p$,mue].join("|")+")",cT=RegExp(g5+"(?="+g5+")|"+wue+bue,"g");function xue(e){for(var t=cT.lastIndex=0;cT.test(e);)++t;return t}function wv(e){return yv(e)?xue(e):cue(e)}var Cue=Math.ceil;function s_(e,t){t=t===void 0?" ":Ua(t);var n=t.length;if(n<2)return n?v5(t,e):t;var r=v5(t,Cue(e/wv(t)));return yv(t)?ud(ss(r),0,e).join(""):r.slice(0,e)}var Eue=Math.ceil,Aue=Math.floor;function Sue(e,t,n){e=Nr(e),t=er(t);var r=t?wv(e):0;if(!t||r>=t)return e;var o=(t-r)/2;return s_(Aue(o),n)+e+s_(Eue(o),n)}function kue(e,t,n){e=Nr(e),t=er(t);var r=t?wv(e):0;return t&&r-1;)l!==e&&dT.call(l,s,1),dT.call(e,s,1);return e}function m$(e,t){return e&&e.length&&t&&t.length?aA(e,t):e}var zue=or(m$);function jue(e,t,n){return e&&e.length&&t&&t.length?aA(e,t,Un(n)):e}function Vue(e,t,n){return e&&e.length&&t&&t.length?aA(e,t,void 0,n):e}var Hue=Array.prototype,Wue=Hue.splice;function g$(e,t){for(var n=e?t.length:0,r=n-1;n--;){var o=t[n];if(n==r||o!==i){var i=o;Wu(o)?Wue.call(e,o,1):rA(e,o)}}return e}var Uue=qu(function(e,t){var n=e==null?0:e.length,r=AE(e,t);return g$(e,Jr(t,function(o){return Wu(o,n)?+o:o}).sort(s$)),r}),Kue=Math.floor,Yue=Math.random;function lA(e,t){return e+Kue(Yue()*(t-e+1))}var Gue=parseFloat,que=Math.min,Xue=Math.random;function Que(e,t,n){if(n&&typeof n!="boolean"&&Gi(e,t,n)&&(t=n=void 0),n===void 0&&(typeof t=="boolean"?(n=t,t=void 0):typeof e=="boolean"&&(n=e,e=void 0)),e===void 0&&t===void 0?(e=0,t=1):(e=Mu(e),t===void 0?(t=e,e=0):t=Mu(t)),e>t){var r=e;e=t,t=r}if(n||e%1||t%1){var o=Xue();return que(e+o*(t-e+Gue("1e-"+((o+"").length-1))),t)}return lA(e,t)}var Jue=Math.ceil,Zue=Math.max;function ece(e,t,n,r){for(var o=-1,i=Zue(Jue((t-e)/(n||1)),0),a=Array(i);i--;)a[r?i:++o]=e,e+=n;return a}function y$(e){return function(t,n,r){return r&&typeof r!="number"&&Gi(t,n,r)&&(n=r=void 0),t=Mu(t),n===void 0?(n=t,t=0):n=Mu(n),r=r===void 0?t1&&Gi(e,t[0],t[1])?t=[]:n>2&&Gi(t[0],t[1],t[2])&&(t=[t[0]]),u$(e,ci(t,1),[])}),Rce=4294967295,Pce=Rce-1,Ice=Math.floor,Lce=Math.min;function uA(e,t,n,r){var o=0,i=e==null?0:e.length;if(i===0)return 0;t=n(t);for(var a=t!==t,l=t===null,s=Wa(t),u=t===void 0;o>>1;function c4(e,t,n){var r=0,o=e==null?r:e.length;if(typeof t=="number"&&t===t&&o<=$ce){for(;r>>1,a=e[i];a!==null&&!Wa(a)&&(n?a<=t:a>>0,n?(e=Nr(e),e&&(typeof t=="string"||t!=null&&!eA(t))&&(t=Ua(t),!t&&yv(e))?ud(ss(e),0,n):e.split(t,n)):[]}var Xce="Expected a function",Qce=Math.max;function Jce(e,t){if(typeof e!="function")throw new TypeError(Xce);return t=t==null?0:Qce(er(t),0),or(function(n){var r=n[t],o=ud(n,0,t);return r&&sd(o,r),Ka(e,this,o)})}var Zce=_v(function(e,t,n){return e+(n?" ":"")+TE(t)});function ede(e,t,n){return e=Nr(e),n=n==null?0:Kf(er(n),0,e.length),t=Ua(t),e.slice(n,n+t.length)==t}function tde(){return{}}function nde(){return""}function rde(){return!0}var ode=Hb(function(e,t){return e-t},0);function ide(e){return e&&e.length?nA(e,Aa):0}function ade(e,t){return e&&e.length?nA(e,Un(t)):0}function lde(e){var t=e==null?0:e.length;return t?_l(e,1,t):[]}function sde(e,t,n){return e&&e.length?(t=n||t===void 0?1:er(t),_l(e,0,t<0?0:t)):[]}function ude(e,t,n){var r=e==null?0:e.length;return r?(t=n||t===void 0?1:er(t),t=r-t,_l(e,t<0?0:t,r)):[]}function cde(e,t){return e&&e.length?n4(e,Un(t),!1,!0):[]}function dde(e,t){return e&&e.length?n4(e,Un(t)):[]}function fde(e,t){return t(e),e}var C$=Object.prototype,pde=C$.hasOwnProperty;function fT(e,t,n,r){return e===void 0||fs(e,C$[n])&&!pde.call(r,n)?t:e}var hde={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"};function vde(e){return"\\"+hde[e]}var E$=/<%=([\s\S]+?)%>/g,mde=/<%-([\s\S]+?)%>/g,gde=/<%([\s\S]+?)%>/g,_5={escape:mde,evaluate:gde,interpolate:E$,variable:"",imports:{_:{escape:$B}}},yde="Invalid `variable` option passed into `_.template`",_de=/\b__p \+= '';/g,bde=/\b(__p \+=) '' \+/g,wde=/(__e\(.*?\)|\b__t\)) \+\n'';/g,xde=/[()=,{}\[\]\/\s]/,Cde=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,H1=/($^)/,Ede=/['\n\r\u2028\u2029\\]/g,Ade=Object.prototype,pT=Ade.hasOwnProperty;function Sde(e,t,n){var r=_5.imports._.templateSettings||_5;n&&Gi(e,t,n)&&(t=void 0),e=Nr(e),t=a5({},t,r,fT);var o=a5({},t.imports,r.imports,fT),i=Go(o),a=QE(o,i),l,s,u=0,c=t.interpolate||H1,d="__p += '",f=RegExp((t.escape||H1).source+"|"+c.source+"|"+(c===E$?Cde:H1).source+"|"+(t.evaluate||H1).source+"|$","g"),h=pT.call(t,"sourceURL")?"//# sourceURL="+(t.sourceURL+"").replace(/\s/g," ")+` +`:"";e.replace(f,function(y,g,b,x,C,E){return b||(b=x),d+=e.slice(u,E).replace(Ede,vde),g&&(l=!0,d+=`' + +__e(`+g+`) + +'`),C&&(s=!0,d+=`'; +`+C+`; +__p += '`),b&&(d+=`' + +((__t = (`+b+`)) == null ? '' : __t) + +'`),u=E+y.length,y}),d+=`'; +`;var v=pT.call(t,"variable")&&t.variable;if(!v)d=`with (obj) { +`+d+` +} +`;else if(xde.test(v))throw new Error(yde);d=(s?d.replace(_de,""):d).replace(bde,"$1").replace(wde,"$1;"),d="function("+(v||"obj")+`) { +`+(v?"":`obj || (obj = {}); +`)+"var __t, __p = ''"+(l?", __e = _.escape":"")+(s?`, __j = Array.prototype.join; +function print() { __p += __j.call(arguments, '') } +`:`; +`)+d+`return __p +}`;var m=IL(function(){return Function(i,h+"return "+d).apply(void 0,a)});if(m.source=d,kE(m))throw m;return m}var kde="Expected a function";function Tu(e,t,n){var r=!0,o=!0;if(typeof e!="function")throw new TypeError(kde);return eo(n)&&(r="leading"in n?!!n.leading:r,o="trailing"in n?!!n.trailing:o),Wo(e,t,{leading:r,maxWait:t,trailing:o})}function d4(e,t){return t(e)}var Mde=9007199254740991,f3=4294967295,Tde=Math.min;function Ode(e,t){if(e=er(e),e<1||e>Mde)return[];var n=f3,r=Tde(e,f3);t=Zs(t),e-=f3;for(var o=wE(r,t);++n-1;);return n}function k$(e,t){for(var n=-1,r=e.length;++n-1;);return n}function Bde(e,t,n){if(e=Nr(e),e&&(n||t===void 0))return mL(e);if(!e||!(t=Ua(t)))return e;var r=ss(e),o=ss(t),i=k$(r,o),a=S$(r,o)+1;return ud(r,i,a).join("")}function $de(e,t,n){if(e=Nr(e),e&&(n||t===void 0))return e.slice(0,vL(e)+1);if(!e||!(t=Ua(t)))return e;var r=ss(e),o=S$(r,ss(t))+1;return ud(r,0,o).join("")}var zde=/^\s+/;function jde(e,t,n){if(e=Nr(e),e&&(n||t===void 0))return e.replace(zde,"");if(!e||!(t=Ua(t)))return e;var r=ss(e),o=k$(r,ss(t));return ud(r,o).join("")}var Vde=30,Hde="...",Wde=/\w*$/;function Ude(e,t){var n=Vde,r=Hde;if(eo(t)){var o="separator"in t?t.separator:o;n="length"in t?er(t.length):n,r="omission"in t?Ua(t.omission):r}e=Nr(e);var i=e.length;if(yv(e)){var a=ss(e);i=a.length}if(n>=i)return e;var l=n-wv(r);if(l<1)return r;var s=a?ud(a,0,l).join(""):e.slice(0,l);if(o===void 0)return s+r;if(a&&(l+=s.length-l),eA(o)){if(e.slice(l).search(o)){var u,c=s;for(o.global||(o=RegExp(o.source,Nr(Wde.exec(o))+"g")),o.lastIndex=0;u=o.exec(c);)var d=u.index;s=s.slice(0,d===void 0?l:d)}}else if(e.indexOf(Ua(o),l)!=l){var f=s.lastIndexOf(o);f>-1&&(s=s.slice(0,f))}return s+r}function Kde(e){return kL(e,1)}var Yde={"&":"&","<":"<",">":">",""":'"',"'":"'"},Gde=DE(Yde),M$=/&(?:amp|lt|gt|quot|#39);/g,qde=RegExp(M$.source);function Xde(e){return e=Nr(e),e&&qde.test(e)?e.replace(M$,Gde):e}var Qde=1/0,Jde=fh&&1/Zb(new fh([,-0]))[1]==Qde?function(e){return new fh(e)}:ch,Zde=200;function Qc(e,t,n){var r=-1,o=Kb,i=e.length,a=!0,l=[],s=l;if(n)a=!1,o=KE;else if(i>=Zde){var u=t?null:Jde(e);if(u)return Zb(u);a=!1,o=Lm,s=new Tf}else s=t?[]:l;e:for(;++r1||this.__actions__.length||!(r instanceof cr)||!Wu(n)?this.thru(o):(r=r.slice(n,+n+(t?1:0)),r.__actions__.push({func:d4,args:[o],thisArg:void 0}),new gl(r,this.__chain__).thru(function(i){return t&&!i.length&&i.push(void 0),i}))});function mfe(){return lB(this)}function gfe(){var e=this.__wrapped__;if(e instanceof cr){var t=e;return this.__actions__.length&&(t=new cr(this)),t=t.reverse(),t.__actions__.push({func:d4,args:[y5],thisArg:void 0}),new gl(t,this.__chain__)}return this.thru(y5)}function dA(e,t,n){var r=e.length;if(r<2)return r?Qc(e[0]):[];for(var o=-1,i=Array(r);++o1?e[t-1]:void 0;return n=typeof n=="function"?(e.pop(),n):void 0,T$(e,n)});const dn={chunk:mre,compact:qoe,concat:Xoe,difference:tae,differenceBy:nae,differenceWith:rae,drop:iae,dropRight:aae,dropRightWhile:lae,dropWhile:sae,fill:Sae,findIndex:GE,findLastIndex:qE,flatten:SE,flattenDeep:GB,flattenDepth:$ae,fromPairs:Mh,head:Rae,indexOf:XB,initial:yle,intersection:ble,intersectionBy:wle,intersectionWith:xle,join:fse,lastIndexOf:yse,nth:Wse,pull:zue,pullAll:m$,pullAllBy:jue,pullAllWith:Vue,pullAt:Uue,remove:sce,reverse:y5,slice:Tce,sortedIndex:zce,sortedIndexBy:jce,sortedIndexOf:Vce,sortedLastIndex:Hce,sortedLastIndexBy:Wce,sortedLastIndexOf:Uce,sortedUniq:Kce,sortedUniqBy:Yce,tail:lde,take:sde,takeRight:ude,takeRightWhile:cde,takeWhile:dde,union:ey,unionBy:efe,unionWith:tfe,uniq:nfe,uniqBy:rfe,uniqWith:ofe,unzip:cA,unzipWith:T$,without:pfe,xor:yfe,xorBy:_fe,xorWith:bfe,zip:wfe,zipObject:xfe,zipObjectDeep:Cfe,zipWith:Efe},Br={countBy:$ie,every:Cae,filter:kae,find:Xc,findLast:Nae,flatMap:YB,flatMapDeep:Iae,flatMapDepth:Lae,forEach:xr,forEachRight:cae,groupBy:ole,includes:mle,invokeMap:Dle,keyBy:hse,map:qa,orderBy:tue,partition:Rue,reduce:b$,reduceRight:ace,reject:lce,sample:yce,sampleSize:wce,shuffle:Ace,size:Mce,some:Nce,sortBy:Fce},Afe={now:Z2},po={after:tZ,ary:kL,before:LL,bind:Jb,bindKey:ME,curry:WE,curryRight:UE,debounce:Wo,defer:Jie,delay:Zie,flip:jae,memoize:Og,once:Jse,overArgs:iue,partial:s4,partialRight:iA,rearg:oce,rest:fce,spread:Jce,throttle:Tu,unary:Kde,wrap:hfe},Tn={castArray:qc,clone:f5,cloneDeep:Cn,cloneDeepWith:Uoe,cloneWith:Yoe,conformsTo:Rie,eq:fs,gt:ile,gte:ale,isArguments:kf,isArrayBuffer:Rle,isArrayLike:Sa,isArrayLikeObject:_o,isBoolean:a_,isBuffer:Gc,isDate:Ble,isElement:$le,isEmpty:l_,isEqual:ii,isEqualWith:Wle,isError:kE,isFinite:Kle,isFunction:Hu,isInteger:e$,isLength:Gb,isMap:vB,isMatch:Yle,isMatchWith:Gle,isNaN:Xle,isNative:Zle,isNil:ir,isNull:ese,isNumber:t$,isObjectLike:co,isPlainObject:Dg,isRegExp:eA,isSafeInteger:rse,isSet:mB,isString:i4,isSymbol:Wa,isTypedArray:gv,isUndefined:Iu,isWeakMap:ise,isWeakSet:lse,lt:wse,lte:xse,toArray:o$,toFinite:Mu,toLength:VB,toNumber:cl,toPlainObject:DB,toSafeInteger:Pde,toString:Nr},Zi={add:KJ,ceil:pre,divide:oae,floor:Vae,max:Mse,maxBy:Tse,mean:Dse,meanBy:Nse,min:Pse,minBy:Ise,multiply:Bse,round:mce,subtract:ode,sum:ide,sumBy:ade},fA={clamp:gre,inRange:ple,random:Que},Vn={assign:mr,assignIn:ute,assignInWith:a5,assignWith:cte,at:Hte,create:zie,defaults:Gie,defaultsDeep:Xie,findKey:Tae,findLastKey:Fae,forIn:Xae,forInRight:Qae,forOwn:Jae,forOwnRight:Zae,functions:ele,functionsIn:tle,get:sr,has:i_,hasIn:jE,invert:Sle,invertBy:Tle,invoke:Ole,keysIn:ka,mapKeys:n$,mapValues:Cse,merge:Lu,mergeWith:FB,omit:Xse,omitBy:Qse,pick:Ei,pickBy:l$,result:pce,set:sA,setWith:xce,toPairs:Xu,toPairsIn:mae,transform:Lde,unset:lfe,update:ufe,updateWith:cfe,values:bv,valuesIn:ffe},eu={at:vfe,chain:lB,commit:Goe,next:Hse,plant:Iue,reverse:gfe,tap:fde,toIterator:Dde,value:Nde,wrapperChain:mfe},Tr={camelCase:cre,capitalize:UL,deburr:KL,endsWith:dae,escape:$B,escapeRegExp:wae,kebabCase:pse,lowerCase:_se,lowerFirst:bse,pad:Sue,padEnd:kue,padStart:Mue,parseInt:Due,repeat:uce,replace:cce,snakeCase:Oce,split:qce,startCase:Zce,startsWith:ede,template:Sde,templateSettings:_5,toLower:Fde,toUpper:Ide,trim:Bde,trimEnd:$de,trimStart:jde,truncate:Ude,unescape:Xde,upperCase:dfe,upperFirst:TE,words:aB},Rr={attempt:IL,bindAll:ene,cond:Oie,conforms:Fie,constant:_E,defaultTo:Kie,flow:Gae,flowRight:qae,iteratee:use,matches:Ase,matchesProperty:kse,method:Fse,methodOf:Rse,noop:ch,nthArg:Use,over:nue,overEvery:aue,overSome:lue,property:SB,propertyOf:Lue,range:tce,rangeRight:nce,stubArray:FE,stubFalse:xE,stubObject:tde,stubString:nde,stubTrue:rde,times:Ode,toPath:Rde,uniqueId:afe};function Sfe(){var e=new cr(this.__wrapped__);return e.__actions__=ga(this.__actions__),e.__dir__=this.__dir__,e.__filtered__=this.__filtered__,e.__iteratees__=ga(this.__iteratees__),e.__takeCount__=this.__takeCount__,e.__views__=ga(this.__views__),e}function kfe(){if(this.__filtered__){var e=new cr(this);e.__dir__=-1,e.__filtered__=!0}else e=this.clone(),e.__dir__*=-1;return e}var Mfe=Math.max,Tfe=Math.min;function Ofe(e,t,n){for(var r=-1,o=n.length;++r + * Build: `lodash modularize exports="es" -o ./` + * Copyright OpenJS Foundation and other contributors + * Released under MIT license + * Based on Underscore.js 1.8.3 + * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + */var Pfe="4.17.21",Ife=2,Lfe=1,Bfe=3,N$=4294967295,$fe=Array.prototype,zfe=Object.prototype,F$=zfe.hasOwnProperty,vT=ki?ki.iterator:void 0,jfe=Math.max,mT=Math.min,pA=function(e){return function(t,n,r){if(r==null){var o=eo(n),i=o&&Go(n),a=i&&i.length&&r4(n,i);(a?a.length:o)||(r=n,n=t,t=this)}return e(t,n,r)}}(Lse);ke.after=po.after;ke.ary=po.ary;ke.assign=Vn.assign;ke.assignIn=Vn.assignIn;ke.assignInWith=Vn.assignInWith;ke.assignWith=Vn.assignWith;ke.at=Vn.at;ke.before=po.before;ke.bind=po.bind;ke.bindAll=Rr.bindAll;ke.bindKey=po.bindKey;ke.castArray=Tn.castArray;ke.chain=eu.chain;ke.chunk=dn.chunk;ke.compact=dn.compact;ke.concat=dn.concat;ke.cond=Rr.cond;ke.conforms=Rr.conforms;ke.constant=Rr.constant;ke.countBy=Br.countBy;ke.create=Vn.create;ke.curry=po.curry;ke.curryRight=po.curryRight;ke.debounce=po.debounce;ke.defaults=Vn.defaults;ke.defaultsDeep=Vn.defaultsDeep;ke.defer=po.defer;ke.delay=po.delay;ke.difference=dn.difference;ke.differenceBy=dn.differenceBy;ke.differenceWith=dn.differenceWith;ke.drop=dn.drop;ke.dropRight=dn.dropRight;ke.dropRightWhile=dn.dropRightWhile;ke.dropWhile=dn.dropWhile;ke.fill=dn.fill;ke.filter=Br.filter;ke.flatMap=Br.flatMap;ke.flatMapDeep=Br.flatMapDeep;ke.flatMapDepth=Br.flatMapDepth;ke.flatten=dn.flatten;ke.flattenDeep=dn.flattenDeep;ke.flattenDepth=dn.flattenDepth;ke.flip=po.flip;ke.flow=Rr.flow;ke.flowRight=Rr.flowRight;ke.fromPairs=dn.fromPairs;ke.functions=Vn.functions;ke.functionsIn=Vn.functionsIn;ke.groupBy=Br.groupBy;ke.initial=dn.initial;ke.intersection=dn.intersection;ke.intersectionBy=dn.intersectionBy;ke.intersectionWith=dn.intersectionWith;ke.invert=Vn.invert;ke.invertBy=Vn.invertBy;ke.invokeMap=Br.invokeMap;ke.iteratee=Rr.iteratee;ke.keyBy=Br.keyBy;ke.keys=Go;ke.keysIn=Vn.keysIn;ke.map=Br.map;ke.mapKeys=Vn.mapKeys;ke.mapValues=Vn.mapValues;ke.matches=Rr.matches;ke.matchesProperty=Rr.matchesProperty;ke.memoize=po.memoize;ke.merge=Vn.merge;ke.mergeWith=Vn.mergeWith;ke.method=Rr.method;ke.methodOf=Rr.methodOf;ke.mixin=pA;ke.negate=l4;ke.nthArg=Rr.nthArg;ke.omit=Vn.omit;ke.omitBy=Vn.omitBy;ke.once=po.once;ke.orderBy=Br.orderBy;ke.over=Rr.over;ke.overArgs=po.overArgs;ke.overEvery=Rr.overEvery;ke.overSome=Rr.overSome;ke.partial=po.partial;ke.partialRight=po.partialRight;ke.partition=Br.partition;ke.pick=Vn.pick;ke.pickBy=Vn.pickBy;ke.property=Rr.property;ke.propertyOf=Rr.propertyOf;ke.pull=dn.pull;ke.pullAll=dn.pullAll;ke.pullAllBy=dn.pullAllBy;ke.pullAllWith=dn.pullAllWith;ke.pullAt=dn.pullAt;ke.range=Rr.range;ke.rangeRight=Rr.rangeRight;ke.rearg=po.rearg;ke.reject=Br.reject;ke.remove=dn.remove;ke.rest=po.rest;ke.reverse=dn.reverse;ke.sampleSize=Br.sampleSize;ke.set=Vn.set;ke.setWith=Vn.setWith;ke.shuffle=Br.shuffle;ke.slice=dn.slice;ke.sortBy=Br.sortBy;ke.sortedUniq=dn.sortedUniq;ke.sortedUniqBy=dn.sortedUniqBy;ke.split=Tr.split;ke.spread=po.spread;ke.tail=dn.tail;ke.take=dn.take;ke.takeRight=dn.takeRight;ke.takeRightWhile=dn.takeRightWhile;ke.takeWhile=dn.takeWhile;ke.tap=eu.tap;ke.throttle=po.throttle;ke.thru=d4;ke.toArray=Tn.toArray;ke.toPairs=Vn.toPairs;ke.toPairsIn=Vn.toPairsIn;ke.toPath=Rr.toPath;ke.toPlainObject=Tn.toPlainObject;ke.transform=Vn.transform;ke.unary=po.unary;ke.union=dn.union;ke.unionBy=dn.unionBy;ke.unionWith=dn.unionWith;ke.uniq=dn.uniq;ke.uniqBy=dn.uniqBy;ke.uniqWith=dn.uniqWith;ke.unset=Vn.unset;ke.unzip=dn.unzip;ke.unzipWith=dn.unzipWith;ke.update=Vn.update;ke.updateWith=Vn.updateWith;ke.values=Vn.values;ke.valuesIn=Vn.valuesIn;ke.without=dn.without;ke.words=Tr.words;ke.wrap=po.wrap;ke.xor=dn.xor;ke.xorBy=dn.xorBy;ke.xorWith=dn.xorWith;ke.zip=dn.zip;ke.zipObject=dn.zipObject;ke.zipObjectDeep=dn.zipObjectDeep;ke.zipWith=dn.zipWith;ke.entries=Vn.toPairs;ke.entriesIn=Vn.toPairsIn;ke.extend=Vn.assignIn;ke.extendWith=Vn.assignInWith;pA(ke,ke);ke.add=Zi.add;ke.attempt=Rr.attempt;ke.camelCase=Tr.camelCase;ke.capitalize=Tr.capitalize;ke.ceil=Zi.ceil;ke.clamp=fA.clamp;ke.clone=Tn.clone;ke.cloneDeep=Tn.cloneDeep;ke.cloneDeepWith=Tn.cloneDeepWith;ke.cloneWith=Tn.cloneWith;ke.conformsTo=Tn.conformsTo;ke.deburr=Tr.deburr;ke.defaultTo=Rr.defaultTo;ke.divide=Zi.divide;ke.endsWith=Tr.endsWith;ke.eq=Tn.eq;ke.escape=Tr.escape;ke.escapeRegExp=Tr.escapeRegExp;ke.every=Br.every;ke.find=Br.find;ke.findIndex=dn.findIndex;ke.findKey=Vn.findKey;ke.findLast=Br.findLast;ke.findLastIndex=dn.findLastIndex;ke.findLastKey=Vn.findLastKey;ke.floor=Zi.floor;ke.forEach=Br.forEach;ke.forEachRight=Br.forEachRight;ke.forIn=Vn.forIn;ke.forInRight=Vn.forInRight;ke.forOwn=Vn.forOwn;ke.forOwnRight=Vn.forOwnRight;ke.get=Vn.get;ke.gt=Tn.gt;ke.gte=Tn.gte;ke.has=Vn.has;ke.hasIn=Vn.hasIn;ke.head=dn.head;ke.identity=Aa;ke.includes=Br.includes;ke.indexOf=dn.indexOf;ke.inRange=fA.inRange;ke.invoke=Vn.invoke;ke.isArguments=Tn.isArguments;ke.isArray=Wn;ke.isArrayBuffer=Tn.isArrayBuffer;ke.isArrayLike=Tn.isArrayLike;ke.isArrayLikeObject=Tn.isArrayLikeObject;ke.isBoolean=Tn.isBoolean;ke.isBuffer=Tn.isBuffer;ke.isDate=Tn.isDate;ke.isElement=Tn.isElement;ke.isEmpty=Tn.isEmpty;ke.isEqual=Tn.isEqual;ke.isEqualWith=Tn.isEqualWith;ke.isError=Tn.isError;ke.isFinite=Tn.isFinite;ke.isFunction=Tn.isFunction;ke.isInteger=Tn.isInteger;ke.isLength=Tn.isLength;ke.isMap=Tn.isMap;ke.isMatch=Tn.isMatch;ke.isMatchWith=Tn.isMatchWith;ke.isNaN=Tn.isNaN;ke.isNative=Tn.isNative;ke.isNil=Tn.isNil;ke.isNull=Tn.isNull;ke.isNumber=Tn.isNumber;ke.isObject=eo;ke.isObjectLike=Tn.isObjectLike;ke.isPlainObject=Tn.isPlainObject;ke.isRegExp=Tn.isRegExp;ke.isSafeInteger=Tn.isSafeInteger;ke.isSet=Tn.isSet;ke.isString=Tn.isString;ke.isSymbol=Tn.isSymbol;ke.isTypedArray=Tn.isTypedArray;ke.isUndefined=Tn.isUndefined;ke.isWeakMap=Tn.isWeakMap;ke.isWeakSet=Tn.isWeakSet;ke.join=dn.join;ke.kebabCase=Tr.kebabCase;ke.last=Ga;ke.lastIndexOf=dn.lastIndexOf;ke.lowerCase=Tr.lowerCase;ke.lowerFirst=Tr.lowerFirst;ke.lt=Tn.lt;ke.lte=Tn.lte;ke.max=Zi.max;ke.maxBy=Zi.maxBy;ke.mean=Zi.mean;ke.meanBy=Zi.meanBy;ke.min=Zi.min;ke.minBy=Zi.minBy;ke.stubArray=Rr.stubArray;ke.stubFalse=Rr.stubFalse;ke.stubObject=Rr.stubObject;ke.stubString=Rr.stubString;ke.stubTrue=Rr.stubTrue;ke.multiply=Zi.multiply;ke.nth=dn.nth;ke.noop=Rr.noop;ke.now=Afe.now;ke.pad=Tr.pad;ke.padEnd=Tr.padEnd;ke.padStart=Tr.padStart;ke.parseInt=Tr.parseInt;ke.random=fA.random;ke.reduce=Br.reduce;ke.reduceRight=Br.reduceRight;ke.repeat=Tr.repeat;ke.replace=Tr.replace;ke.result=Vn.result;ke.round=Zi.round;ke.sample=Br.sample;ke.size=Br.size;ke.snakeCase=Tr.snakeCase;ke.some=Br.some;ke.sortedIndex=dn.sortedIndex;ke.sortedIndexBy=dn.sortedIndexBy;ke.sortedIndexOf=dn.sortedIndexOf;ke.sortedLastIndex=dn.sortedLastIndex;ke.sortedLastIndexBy=dn.sortedLastIndexBy;ke.sortedLastIndexOf=dn.sortedLastIndexOf;ke.startCase=Tr.startCase;ke.startsWith=Tr.startsWith;ke.subtract=Zi.subtract;ke.sum=Zi.sum;ke.sumBy=Zi.sumBy;ke.template=Tr.template;ke.times=Rr.times;ke.toFinite=Tn.toFinite;ke.toInteger=er;ke.toLength=Tn.toLength;ke.toLower=Tr.toLower;ke.toNumber=Tn.toNumber;ke.toSafeInteger=Tn.toSafeInteger;ke.toString=Tn.toString;ke.toUpper=Tr.toUpper;ke.trim=Tr.trim;ke.trimEnd=Tr.trimEnd;ke.trimStart=Tr.trimStart;ke.truncate=Tr.truncate;ke.unescape=Tr.unescape;ke.uniqueId=Rr.uniqueId;ke.upperCase=Tr.upperCase;ke.upperFirst=Tr.upperFirst;ke.each=Br.forEach;ke.eachRight=Br.forEachRight;ke.first=dn.head;pA(ke,function(){var e={};return Js(ke,function(t,n){F$.call(ke.prototype,n)||(e[n]=t)}),e}(),{chain:!1});ke.VERSION=Pfe;(ke.templateSettings=Tr.templateSettings).imports._=ke;Al(["bind","bindKey","curry","curryRight","partial","partialRight"],function(e){ke[e].placeholder=ke});Al(["drop","take"],function(e,t){cr.prototype[e]=function(n){n=n===void 0?1:jfe(er(n),0);var r=this.__filtered__&&!t?new cr(this):this.clone();return r.__filtered__?r.__takeCount__=mT(n,r.__takeCount__):r.__views__.push({size:mT(n,N$),type:e+(r.__dir__<0?"Right":"")}),r},cr.prototype[e+"Right"]=function(n){return this.reverse()[e](n).reverse()}});Al(["filter","map","takeWhile"],function(e,t){var n=t+1,r=n==Lfe||n==Bfe;cr.prototype[e]=function(o){var i=this.clone();return i.__iteratees__.push({iteratee:Un(o),type:n}),i.__filtered__=i.__filtered__||r,i}});Al(["head","last"],function(e,t){var n="take"+(t?"Right":"");cr.prototype[e]=function(){return this[n](1).value()[0]}});Al(["initial","tail"],function(e,t){var n="drop"+(t?"":"Right");cr.prototype[e]=function(){return this.__filtered__?new cr(this):this[n](1)}});cr.prototype.compact=function(){return this.filter(Aa)};cr.prototype.find=function(e){return this.filter(e).head()};cr.prototype.findLast=function(e){return this.reverse().find(e)};cr.prototype.invokeMap=or(function(e,t){return typeof e=="function"?new cr(this):this.map(function(n){return Rg(n,e,t)})});cr.prototype.reject=function(e){return this.filter(l4(Un(e)))};cr.prototype.slice=function(e,t){e=er(e);var n=this;return n.__filtered__&&(e>0||t<0)?new cr(n):(e<0?n=n.takeRight(-e):e&&(n=n.drop(e)),t!==void 0&&(t=er(t),n=t<0?n.dropRight(-t):n.take(t-e)),n)};cr.prototype.takeRightWhile=function(e){return this.reverse().takeWhile(e).reverse()};cr.prototype.toArray=function(){return this.take(N$)};Js(cr.prototype,function(e,t){var n=/^(?:filter|find|map|reject)|While$/.test(t),r=/^(?:head|last)$/.test(t),o=ke[r?"take"+(t=="last"?"Right":""):t],i=r||/^find/.test(t);o&&(ke.prototype[t]=function(){var a=this.__wrapped__,l=r?[1]:arguments,s=a instanceof cr,u=l[0],c=s||Wn(a),d=function(g){var b=o.apply(ke,sd([g],l));return r&&f?b[0]:b};c&&n&&typeof u=="function"&&u.length!=1&&(s=c=!1);var f=this.__chain__,h=!!this.__actions__.length,v=i&&!f,m=s&&!h;if(!i&&c){a=m?a:new cr(this);var y=e.apply(a,l);return y.__actions__.push({func:d4,args:[d],thisArg:void 0}),new gl(y,f)}return v&&m?e.apply(this,l):(y=this.thru(d),v?r?y.value()[0]:y.value():y)})});Al(["pop","push","shift","sort","splice","unshift"],function(e){var t=$fe[e],n=/^(?:push|sort|unshift)$/.test(e)?"tap":"thru",r=/^(?:pop|shift)$/.test(e);ke.prototype[e]=function(){var o=arguments;if(r&&!this.__chain__){var i=this.value();return t.apply(Wn(i)?i:[],o)}return this[n](function(a){return t.apply(Wn(a)?a:[],o)})}});Js(cr.prototype,function(e,t){var n=ke[t];if(n){var r=n.name+"";F$.call(dh,r)||(dh[r]=[]),dh[r].push({name:t,func:n})}});dh[Yb(void 0,Ife).name]=[{name:"wrapper",func:void 0}];cr.prototype.clone=Sfe;cr.prototype.reverse=kfe;cr.prototype.value=Rfe;ke.prototype.at=eu.at;ke.prototype.chain=eu.wrapperChain;ke.prototype.commit=eu.commit;ke.prototype.next=eu.next;ke.prototype.plant=eu.plant;ke.prototype.reverse=eu.reverse;ke.prototype.toJSON=ke.prototype.valueOf=ke.prototype.value=eu.value;ke.prototype.first=ke.prototype.head;vT&&(ke.prototype[vT]=eu.toIterator);const Vfe=e=>e===void 0,p3=e=>typeof e=="boolean",Of=e=>typeof e=="number",Hfe=e=>typeof Element>"u"?!1:e instanceof Element,Wfe=e=>Yt(e)?!Number.isNaN(Number(e)):!1;var Ufe=Object.defineProperty,Kfe=Object.defineProperties,Yfe=Object.getOwnPropertyDescriptors,gT=Object.getOwnPropertySymbols,Gfe=Object.prototype.hasOwnProperty,qfe=Object.prototype.propertyIsEnumerable,yT=(e,t,n)=>t in e?Ufe(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,Xfe=(e,t)=>{for(var n in t||(t={}))Gfe.call(t,n)&&yT(e,n,t[n]);if(gT)for(var n of gT(t))qfe.call(t,n)&&yT(e,n,t[n]);return e},Qfe=(e,t)=>Kfe(e,Yfe(t));function _T(e,t){var n;const r=pr();return ma(()=>{r.value=e()},Qfe(Xfe({},t),{flush:(n=void 0)!=null?n:"sync"})),cv(r)}var bT;const On=typeof window<"u",Jfe=e=>typeof e<"u",Zfe=e=>typeof e=="function",epe=e=>typeof e=="string",Th=()=>{},R$=On&&((bT=window==null?void 0:window.navigator)==null?void 0:bT.userAgent)&&/iP(ad|hone|od)/.test(window.navigator.userAgent);function Jc(e){return typeof e=="function"?e():w(e)}function P$(e,t){function n(...r){return new Promise((o,i)=>{Promise.resolve(e(()=>t.apply(this,r),{fn:t,thisArg:this,args:r})).then(o).catch(i)})}return n}function tpe(e,t={}){let n,r,o=Th;const i=l=>{clearTimeout(l),o(),o=Th};return l=>{const s=Jc(e),u=Jc(t.maxWait);return n&&i(n),s<=0||u!==void 0&&u<=0?(r&&(i(r),r=null),Promise.resolve(l())):new Promise((c,d)=>{o=t.rejectOnCancel?d:c,u&&!r&&(r=setTimeout(()=>{n&&i(n),r=null,c(l())},u)),n=setTimeout(()=>{r&&i(r),r=null,c(l())},s)})}}function npe(e,t=!0,n=!0,r=!1){let o=0,i,a=!0,l=Th,s;const u=()=>{i&&(clearTimeout(i),i=void 0,l(),l=Th)};return d=>{const f=Jc(e),h=Date.now()-o,v=()=>s=d();return u(),f<=0?(o=Date.now(),v()):(h>f&&(n||!a)?(o=Date.now(),v()):t&&(s=new Promise((m,y)=>{l=r?y:m,i=setTimeout(()=>{o=Date.now(),a=!0,m(v()),u()},Math.max(0,f-h))})),!n&&!i&&(i=setTimeout(()=>a=!0,f)),a=!1,s)}}function rpe(e){return e}function Ig(e){return XC()?(QC(e),!0):!1}function ope(e,t=200,n={}){return P$(tpe(t,n),e)}function ipe(e,t=200,n={}){const r=se(e.value),o=ope(()=>{r.value=e.value},t,n);return Ye(e,()=>o()),r}function I$(e,t=200,n=!1,r=!0,o=!1){return P$(npe(t,n,r,o),e)}function hA(e,t=!0){Kt()?en(e):t?e():vt(e)}function Zc(e,t,n={}){const{immediate:r=!0}=n,o=se(!1);let i=null;function a(){i&&(clearTimeout(i),i=null)}function l(){o.value=!1,a()}function s(...u){a(),o.value=!0,i=setTimeout(()=>{o.value=!1,i=null,e(...u)},Jc(t))}return r&&(o.value=!0,On&&s()),Ig(l),{isPending:cv(o),start:s,stop:l}}function ha(e){var t;const n=Jc(e);return(t=n==null?void 0:n.$el)!=null?t:n}const Yf=On?window:void 0,ape=On?window.document:void 0;function Dr(...e){let t,n,r,o;if(epe(e[0])||Array.isArray(e[0])?([n,r,o]=e,t=Yf):[t,n,r,o]=e,!t)return Th;Array.isArray(n)||(n=[n]),Array.isArray(r)||(r=[r]);const i=[],a=()=>{i.forEach(c=>c()),i.length=0},l=(c,d,f,h)=>(c.addEventListener(d,f,h),()=>c.removeEventListener(d,f,h)),s=Ye(()=>[ha(t),Jc(o)],([c,d])=>{a(),c&&i.push(...n.flatMap(f=>r.map(h=>l(c,f,h,d))))},{immediate:!0,flush:"post"}),u=()=>{s(),a()};return Ig(u),u}let wT=!1;function vA(e,t,n={}){const{window:r=Yf,ignore:o=[],capture:i=!0,detectIframe:a=!1}=n;if(!r)return;R$&&!wT&&(wT=!0,Array.from(r.document.body.children).forEach(f=>f.addEventListener("click",Th)));let l=!0;const s=f=>o.some(h=>{if(typeof h=="string")return Array.from(r.document.querySelectorAll(h)).some(v=>v===f.target||f.composedPath().includes(v));{const v=ha(h);return v&&(f.target===v||f.composedPath().includes(v))}}),c=[Dr(r,"click",f=>{const h=ha(e);if(!(!h||h===f.target||f.composedPath().includes(h))){if(f.detail===0&&(l=!s(f)),!l){l=!0;return}t(f)}},{passive:!0,capture:i}),Dr(r,"pointerdown",f=>{const h=ha(e);h&&(l=!f.composedPath().includes(h)&&!s(f))},{passive:!0}),a&&Dr(r,"blur",f=>{var h;const v=ha(e);((h=r.document.activeElement)==null?void 0:h.tagName)==="IFRAME"&&!(v!=null&&v.contains(r.document.activeElement))&&t(f)})].filter(Boolean);return()=>c.forEach(f=>f())}function L$(e,t=!1){const n=se(),r=()=>n.value=!!e();return r(),hA(r,t),n}function lpe(e){return JSON.parse(JSON.stringify(e))}const xT=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{},CT="__vueuse_ssr_handlers__";xT[CT]=xT[CT]||{};function spe(e,t,{window:n=Yf,initialValue:r=""}={}){const o=se(r),i=G(()=>{var a;return ha(t)||((a=n==null?void 0:n.document)==null?void 0:a.documentElement)});return Ye([i,()=>Jc(e)],([a,l])=>{var s;if(a&&n){const u=(s=n.getComputedStyle(a).getPropertyValue(l))==null?void 0:s.trim();o.value=u||r}},{immediate:!0}),Ye(o,a=>{var l;(l=i.value)!=null&&l.style&&i.value.style.setProperty(Jc(e),a)}),o}function upe({document:e=ape}={}){if(!e)return se("visible");const t=se(e.visibilityState);return Dr(e,"visibilitychange",()=>{t.value=e.visibilityState}),t}var ET=Object.getOwnPropertySymbols,cpe=Object.prototype.hasOwnProperty,dpe=Object.prototype.propertyIsEnumerable,fpe=(e,t)=>{var n={};for(var r in e)cpe.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(e!=null&&ET)for(var r of ET(e))t.indexOf(r)<0&&dpe.call(e,r)&&(n[r]=e[r]);return n};function ui(e,t,n={}){const r=n,{window:o=Yf}=r,i=fpe(r,["window"]);let a;const l=L$(()=>o&&"ResizeObserver"in o),s=()=>{a&&(a.disconnect(),a=void 0)},u=Ye(()=>ha(e),d=>{s(),l.value&&o&&d&&(a=new ResizeObserver(t),a.observe(d,i))},{immediate:!0,flush:"post"}),c=()=>{s(),u()};return Ig(c),{isSupported:l,stop:c}}function AT(e,t={}){const{reset:n=!0,windowResize:r=!0,windowScroll:o=!0,immediate:i=!0}=t,a=se(0),l=se(0),s=se(0),u=se(0),c=se(0),d=se(0),f=se(0),h=se(0);function v(){const m=ha(e);if(!m){n&&(a.value=0,l.value=0,s.value=0,u.value=0,c.value=0,d.value=0,f.value=0,h.value=0);return}const y=m.getBoundingClientRect();a.value=y.height,l.value=y.bottom,s.value=y.left,u.value=y.right,c.value=y.top,d.value=y.width,f.value=y.x,h.value=y.y}return ui(e,v),Ye(()=>ha(e),m=>!m&&v()),o&&Dr("scroll",v,{capture:!0,passive:!0}),r&&Dr("resize",v,{passive:!0}),hA(()=>{i&&v()}),{height:a,bottom:l,left:s,right:u,top:c,width:d,x:f,y:h,update:v}}var ST=Object.getOwnPropertySymbols,ppe=Object.prototype.hasOwnProperty,hpe=Object.prototype.propertyIsEnumerable,vpe=(e,t)=>{var n={};for(var r in e)ppe.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(e!=null&&ST)for(var r of ST(e))t.indexOf(r)<0&&hpe.call(e,r)&&(n[r]=e[r]);return n};function mpe(e,t,n={}){const r=n,{window:o=Yf}=r,i=vpe(r,["window"]);let a;const l=L$(()=>o&&"MutationObserver"in o),s=()=>{a&&(a.disconnect(),a=void 0)},u=Ye(()=>ha(e),d=>{s(),l.value&&o&&d&&(a=new MutationObserver(t),a.observe(d,i))},{immediate:!0}),c=()=>{s(),u()};return Ig(c),{isSupported:l,stop:c}}var kT;(function(e){e.UP="UP",e.RIGHT="RIGHT",e.DOWN="DOWN",e.LEFT="LEFT",e.NONE="NONE"})(kT||(kT={}));var gpe=Object.defineProperty,MT=Object.getOwnPropertySymbols,ype=Object.prototype.hasOwnProperty,_pe=Object.prototype.propertyIsEnumerable,TT=(e,t,n)=>t in e?gpe(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,bpe=(e,t)=>{for(var n in t||(t={}))ype.call(t,n)&&TT(e,n,t[n]);if(MT)for(var n of MT(t))_pe.call(t,n)&&TT(e,n,t[n]);return e};const wpe={easeInSine:[.12,0,.39,0],easeOutSine:[.61,1,.88,1],easeInOutSine:[.37,0,.63,1],easeInQuad:[.11,0,.5,0],easeOutQuad:[.5,1,.89,1],easeInOutQuad:[.45,0,.55,1],easeInCubic:[.32,0,.67,0],easeOutCubic:[.33,1,.68,1],easeInOutCubic:[.65,0,.35,1],easeInQuart:[.5,0,.75,0],easeOutQuart:[.25,1,.5,1],easeInOutQuart:[.76,0,.24,1],easeInQuint:[.64,0,.78,0],easeOutQuint:[.22,1,.36,1],easeInOutQuint:[.83,0,.17,1],easeInExpo:[.7,0,.84,0],easeOutExpo:[.16,1,.3,1],easeInOutExpo:[.87,0,.13,1],easeInCirc:[.55,0,1,.45],easeOutCirc:[0,.55,.45,1],easeInOutCirc:[.85,0,.15,1],easeInBack:[.36,0,.66,-.56],easeOutBack:[.34,1.56,.64,1],easeInOutBack:[.68,-.6,.32,1.6]};bpe({linear:rpe},wpe);function xpe(e,t,n,r={}){var o,i,a;const{clone:l=!1,passive:s=!1,eventName:u,deep:c=!1,defaultValue:d}=r,f=Kt(),h=(f==null?void 0:f.emit)||((o=f==null?void 0:f.$emit)==null?void 0:o.bind(f))||((a=(i=f==null?void 0:f.proxy)==null?void 0:i.$emit)==null?void 0:a.bind(f==null?void 0:f.proxy));let v=u;v=u||v||`update:${t.toString()}`;const m=g=>l?Zfe(l)?l(g):lpe(g):g,y=()=>Jfe(e[t])?m(e[t]):d;if(s){const g=y(),b=se(g);return Ye(()=>e[t],x=>b.value=m(x)),Ye(b,x=>{(x!==e[t]||c)&&h(v,x)},{deep:c}),b}else return G({get(){return y()},set(g){h(v,g)}})}function Cpe({window:e=Yf}={}){if(!e)return se(!1);const t=se(e.document.hasFocus());return Dr(e,"blur",()=>{t.value=!1}),Dr(e,"focus",()=>{t.value=!0}),t}function Epe(e={}){const{window:t=Yf,initialWidth:n=1/0,initialHeight:r=1/0,listenOrientation:o=!0,includeScrollbar:i=!0}=e,a=se(n),l=se(r),s=()=>{t&&(i?(a.value=t.innerWidth,l.value=t.innerHeight):(a.value=t.document.documentElement.clientWidth,l.value=t.document.documentElement.clientHeight))};return s(),hA(s),Dr("resize",s,{passive:!0}),o&&Dr("orientationchange",s,{passive:!0}),{width:a,height:l}}const OT={current:0},DT=se(0),B$=2e3,NT=Symbol("elZIndexContextKey"),$$=Symbol("zIndexContextKey"),Ape=e=>{const t=Kt()?kt(NT,OT):OT,n=e||(Kt()?kt($$,void 0):void 0),r=G(()=>{const a=w(n);return Of(a)?a:B$}),o=G(()=>r.value+DT.value),i=()=>(t.current++,DT.value=t.current,o.value);return!On&&kt(NT),{initialZIndex:r,currentZIndex:o,nextZIndex:i}};var Spe={name:"en",el:{breadcrumb:{label:"Breadcrumb"},colorpicker:{confirm:"OK",clear:"Clear",defaultLabel:"color picker",description:"current color is {color}. press enter to select a new color.",alphaLabel:"pick alpha value",alphaDescription:"alpha {alpha}, current color is {color}",hueLabel:"pick hue value",hueDescription:"hue {hue}, current color is {color}"},datepicker:{now:"Now",today:"Today",cancel:"Cancel",clear:"Clear",confirm:"OK",dateTablePrompt:"Use the arrow keys and enter to select the day of the month",monthTablePrompt:"Use the arrow keys and enter to select the month",yearTablePrompt:"Use the arrow keys and enter to select the year",selectedDate:"Selected date",selectDate:"Select date",selectTime:"Select time",startDate:"Start Date",startTime:"Start Time",endDate:"End Date",endTime:"End Time",prevYear:"Previous Year",nextYear:"Next Year",prevMonth:"Previous Month",nextMonth:"Next Month",year:"",month1:"January",month2:"February",month3:"March",month4:"April",month5:"May",month6:"June",month7:"July",month8:"August",month9:"September",month10:"October",month11:"November",month12:"December",weeks:{sun:"Sun",mon:"Mon",tue:"Tue",wed:"Wed",thu:"Thu",fri:"Fri",sat:"Sat"},weeksFull:{sun:"Sunday",mon:"Monday",tue:"Tuesday",wed:"Wednesday",thu:"Thursday",fri:"Friday",sat:"Saturday"},months:{jan:"Jan",feb:"Feb",mar:"Mar",apr:"Apr",may:"May",jun:"Jun",jul:"Jul",aug:"Aug",sep:"Sep",oct:"Oct",nov:"Nov",dec:"Dec"}},inputNumber:{decrease:"decrease number",increase:"increase number"},select:{loading:"Loading",noMatch:"No matching data",noData:"No data",placeholder:"Select"},mention:{loading:"Loading"},dropdown:{toggleDropdown:"Toggle Dropdown"},cascader:{noMatch:"No matching data",loading:"Loading",placeholder:"Select",noData:"No data"},pagination:{goto:"Go to",pagesize:"/page",total:"Total {total}",pageClassifier:"",page:"Page",prev:"Go to previous page",next:"Go to next page",currentPage:"page {pager}",prevPages:"Previous {pager} pages",nextPages:"Next {pager} pages",deprecationWarning:"Deprecated usages detected, please refer to the el-pagination documentation for more details"},dialog:{close:"Close this dialog"},drawer:{close:"Close this dialog"},messagebox:{title:"Message",confirm:"OK",cancel:"Cancel",error:"Illegal input",close:"Close this dialog"},upload:{deleteTip:"press delete to remove",delete:"Delete",preview:"Preview",continue:"Continue"},slider:{defaultLabel:"slider between {min} and {max}",defaultRangeStartLabel:"pick start value",defaultRangeEndLabel:"pick end value"},table:{emptyText:"No Data",confirmFilter:"Confirm",resetFilter:"Reset",clearFilter:"All",sumText:"Sum"},tour:{next:"Next",previous:"Previous",finish:"Finish",close:"Close this dialog"},tree:{emptyText:"No Data"},transfer:{noMatch:"No matching data",noData:"No data",titles:["List 1","List 2"],filterPlaceholder:"Enter keyword",noCheckedFormat:"{total} items",hasCheckedFormat:"{checked}/{total} checked"},image:{error:"FAILED"},pageHeader:{title:"Back"},popconfirm:{confirmButtonText:"Yes",cancelButtonText:"No"},carousel:{leftArrow:"Carousel arrow left",rightArrow:"Carousel arrow right",indicator:"Carousel switch to index {index}"}}};const kpe=e=>(t,n)=>Mpe(t,n,w(e)),Mpe=(e,t,n)=>sr(n,e,e).replace(/\{(\w+)\}/g,(r,o)=>{var i;return`${(i=t==null?void 0:t[o])!=null?i:`{${o}}`}`}),Tpe=e=>{const t=G(()=>w(e).name),n=Mn(e)?e:se(e);return{lang:t,locale:n,t:kpe(e)}},z$=Symbol("localeContextKey"),Ope=e=>{const t=e||kt(z$,se());return Tpe(G(()=>t.value||Spe))},j$="__epPropKey",Df=e=>e,Dpe=e=>hn(e)&&!!e[j$],V$=(e,t)=>{if(!hn(e)||Dpe(e))return e;const{values:n,required:r,default:o,type:i,validator:a}=e,s={type:i,required:!!r,validator:n||a?u=>{let c=!1,d=[];if(n&&(d=Array.from(n),kn(e,"default")&&d.push(o),c||(c=d.includes(u))),a&&(c||(c=a(u))),!c&&d.length>0){const f=[...new Set(d)].map(h=>JSON.stringify(h)).join(", ");EI(`Invalid prop: validation failed${t?` for prop "${t}"`:""}. Expected one of [${f}], got value ${JSON.stringify(u)}.`)}return c}:void 0,[j$]:!0};return kn(e,"default")&&(s.default=o),s},f4=e=>Mh(Object.entries(e).map(([t,n])=>[t,V$(n,t)])),Npe=["","default","small","large"],rct=V$({type:String,values:Npe,required:!1}),Fpe=Symbol("size"),Rpe=Symbol("emptyValuesContextKey"),oct=f4({emptyValues:Array,valueOnClear:{type:Df([String,Number,Boolean,Function]),default:void 0,validator:e=>(e=jt(e)?e():e,Mt(e)?e.every(t=>!t):!e)}}),FT=e=>Object.keys(e),u_=se();function H$(e,t=void 0){return Kt()?kt(dL,u_):u_}function Ppe(e,t){const n=H$(),r=gE(e,G(()=>{var l;return((l=n.value)==null?void 0:l.namespace)||X2})),o=Ope(G(()=>{var l;return(l=n.value)==null?void 0:l.locale})),i=Ape(G(()=>{var l;return((l=n.value)==null?void 0:l.zIndex)||B$})),a=G(()=>{var l;return w(t)||((l=n.value)==null?void 0:l.size)||""});return Ipe(G(()=>w(n)||{})),{ns:r,locale:o,zIndex:i,size:a}}const Ipe=(e,t,n=!1)=>{var r;const o=!!Kt(),i=o?H$():void 0,a=(r=void 0)!=null?r:o?_n:void 0;if(!a)return;const l=G(()=>{const s=w(e);return i!=null&&i.value?Lpe(i.value,s):s});return a(dL,l),a(z$,G(()=>l.value.locale)),a(fL,G(()=>l.value.namespace)),a($$,G(()=>l.value.zIndex)),a(Fpe,{size:G(()=>l.value.size||"")}),a(Rpe,G(()=>({emptyValues:l.value.emptyValues,valueOnClear:l.value.valueOnClear}))),(n||!u_.value)&&(u_.value=l.value),l},Lpe=(e,t)=>{const n=[...new Set([...FT(e),...FT(t)])],r={};for(const o of n)r[o]=t[o]!==void 0?t[o]:e[o];return r};var mA=(e,t)=>{const n=e.__vccOpts||e;for(const[r,o]of t)n[r]=o;return n};function b5(e,t="px"){if(!e)return"";if(Of(e)||Wfe(e))return`${e}${t}`;if(Yt(e))return e}const W$=(e,t)=>(e.install=n=>{for(const r of[e,...Object.values({})])n.component(r.name,r)},e),Bpe=(e,t)=>(e.install=n=>{e._context=n._context,n.config.globalProperties[t]=e},e),$pe=f4({size:{type:Df([Number,String])},color:{type:String}}),zpe=ee({name:"ElIcon",inheritAttrs:!1}),jpe=ee({...zpe,props:$pe,setup(e){const t=e,n=gE("icon"),r=G(()=>{const{size:o,color:i}=t;return!o&&!i?{}:{fontSize:Vfe(o)?void 0:b5(o),"--color":i}});return(o,i)=>(R(),W("i",Qt({class:w(n).b(),style:w(r)},o.$attrs),[Qe(o.$slots,"default")],16))}});var Vpe=mA(jpe,[["__file","icon.vue"]]);const RT=W$(Vpe);/*! Element Plus Icons Vue v2.3.2 */var Hpe=ee({name:"CircleCloseFilled",__name:"circle-close-filled",setup(e){return(t,n)=>(R(),W("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[$("path",{fill:"currentColor",d:"M512 64a448 448 0 1 1 0 896 448 448 0 0 1 0-896m0 393.664L407.936 353.6a38.4 38.4 0 1 0-54.336 54.336L457.664 512 353.6 616.064a38.4 38.4 0 1 0 54.336 54.336L512 566.336 616.064 670.4a38.4 38.4 0 1 0 54.336-54.336L566.336 512 670.4 407.936a38.4 38.4 0 1 0-54.336-54.336z"})]))}}),Wpe=Hpe,Upe=ee({name:"Close",__name:"close",setup(e){return(t,n)=>(R(),W("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[$("path",{fill:"currentColor",d:"M764.288 214.592 512 466.88 259.712 214.592a31.936 31.936 0 0 0-45.12 45.12L466.752 512 214.528 764.224a31.936 31.936 0 1 0 45.12 45.184L512 557.184l252.288 252.288a31.936 31.936 0 0 0 45.12-45.12L557.12 512.064l252.288-252.352a31.936 31.936 0 1 0-45.12-45.184z"})]))}}),Kpe=Upe,Ype=ee({name:"InfoFilled",__name:"info-filled",setup(e){return(t,n)=>(R(),W("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[$("path",{fill:"currentColor",d:"M512 64a448 448 0 1 1 0 896.064A448 448 0 0 1 512 64m67.2 275.072c33.28 0 60.288-23.104 60.288-57.344s-27.072-57.344-60.288-57.344c-33.28 0-60.16 23.104-60.16 57.344s26.88 57.344 60.16 57.344M590.912 699.2c0-6.848 2.368-24.64 1.024-34.752l-52.608 60.544c-10.88 11.456-24.512 19.392-30.912 17.28a12.99 12.99 0 0 1-8.256-14.72l87.68-276.992c7.168-35.136-12.544-67.2-54.336-71.296-44.096 0-108.992 44.736-148.48 101.504 0 6.784-1.28 23.68.064 33.792l52.544-60.608c10.88-11.328 23.552-19.328 29.952-17.152a12.8 12.8 0 0 1 7.808 16.128L388.48 728.576c-10.048 32.256 8.96 63.872 55.04 71.04 67.84 0 107.904-43.648 147.456-100.416z"})]))}}),PT=Ype,Gpe=ee({name:"SuccessFilled",__name:"success-filled",setup(e){return(t,n)=>(R(),W("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[$("path",{fill:"currentColor",d:"M512 64a448 448 0 1 1 0 896 448 448 0 0 1 0-896m-55.808 536.384-99.52-99.584a38.4 38.4 0 1 0-54.336 54.336l126.72 126.72a38.27 38.27 0 0 0 54.336 0l262.4-262.464a38.4 38.4 0 1 0-54.272-54.336z"})]))}}),qpe=Gpe,Xpe=ee({name:"WarningFilled",__name:"warning-filled",setup(e){return(t,n)=>(R(),W("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[$("path",{fill:"currentColor",d:"M512 64a448 448 0 1 1 0 896 448 448 0 0 1 0-896m0 192a58.43 58.43 0 0 0-58.24 63.744l23.36 256.384a35.072 35.072 0 0 0 69.76 0l23.296-256.384A58.43 58.43 0 0 0 512 256m0 512a51.2 51.2 0 1 0 0-102.4 51.2 51.2 0 0 0 0 102.4"})]))}}),Qpe=Xpe;const Jpe=Df([String,Object,Function]),Zpe={Close:Kpe},IT={primary:PT,success:qpe,warning:Qpe,error:Wpe,info:PT},ehe=()=>On&&/android/i.test(window.navigator.userAgent),the=e=>e,w5={tab:"Tab",enter:"Enter",space:"Space",left:"ArrowLeft",up:"ArrowUp",right:"ArrowRight",down:"ArrowDown",esc:"Escape",delete:"Delete",backspace:"Backspace",numpadEnter:"NumpadEnter",pageUp:"PageUp",pageDown:"PageDown",home:"Home",end:"End"},nhe=e=>{if(e.code&&e.code!=="Unidentified")return e.code;const t=rhe(e);if(t){if(Object.values(w5).includes(t))return t;switch(t){case" ":return w5.space;default:return""}}return""},rhe=e=>{let t=e.key&&e.key!=="Unidentified"?e.key:"";if(!t&&e.type==="keyup"&&ehe()){const n=e.target;t=n.value.charAt(n.selectionStart-1)}return t};var ya="top",bl="bottom",wl="right",_a="left",gA="auto",Lg=[ya,bl,wl,_a],Oh="start",Bm="end",ohe="clippingParents",U$="viewport",m0="popper",ihe="reference",LT=Lg.reduce(function(e,t){return e.concat([t+"-"+Oh,t+"-"+Bm])},[]),Gf=[].concat(Lg,[gA]).reduce(function(e,t){return e.concat([t,t+"-"+Oh,t+"-"+Bm])},[]),ahe="beforeRead",lhe="read",she="afterRead",uhe="beforeMain",che="main",dhe="afterMain",fhe="beforeWrite",phe="write",hhe="afterWrite",vhe=[ahe,lhe,she,uhe,che,dhe,fhe,phe,hhe];function js(e){return e?(e.nodeName||"").toLowerCase():null}function ps(e){if(e==null)return window;if(e.toString()!=="[object Window]"){var t=e.ownerDocument;return t&&t.defaultView||window}return e}function Dh(e){var t=ps(e).Element;return e instanceof t||e instanceof Element}function fl(e){var t=ps(e).HTMLElement;return e instanceof t||e instanceof HTMLElement}function yA(e){if(typeof ShadowRoot>"u")return!1;var t=ps(e).ShadowRoot;return e instanceof t||e instanceof ShadowRoot}function mhe(e){var t=e.state;Object.keys(t.elements).forEach(function(n){var r=t.styles[n]||{},o=t.attributes[n]||{},i=t.elements[n];!fl(i)||!js(i)||(Object.assign(i.style,r),Object.keys(o).forEach(function(a){var l=o[a];l===!1?i.removeAttribute(a):i.setAttribute(a,l===!0?"":l)}))})}function ghe(e){var t=e.state,n={popper:{position:t.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(t.elements.popper.style,n.popper),t.styles=n,t.elements.arrow&&Object.assign(t.elements.arrow.style,n.arrow),function(){Object.keys(t.elements).forEach(function(r){var o=t.elements[r],i=t.attributes[r]||{},a=Object.keys(t.styles.hasOwnProperty(r)?t.styles[r]:n[r]),l=a.reduce(function(s,u){return s[u]="",s},{});!fl(o)||!js(o)||(Object.assign(o.style,l),Object.keys(i).forEach(function(s){o.removeAttribute(s)}))})}}var K$={name:"applyStyles",enabled:!0,phase:"write",fn:mhe,effect:ghe,requires:["computeStyles"]};function Ps(e){return e.split("-")[0]}var mf=Math.max,c_=Math.min,Nh=Math.round;function Fh(e,t){t===void 0&&(t=!1);var n=e.getBoundingClientRect(),r=1,o=1;if(fl(e)&&t){var i=e.offsetHeight,a=e.offsetWidth;a>0&&(r=Nh(n.width)/a||1),i>0&&(o=Nh(n.height)/i||1)}return{width:n.width/r,height:n.height/o,top:n.top/o,right:n.right/r,bottom:n.bottom/o,left:n.left/r,x:n.left/r,y:n.top/o}}function _A(e){var t=Fh(e),n=e.offsetWidth,r=e.offsetHeight;return Math.abs(t.width-n)<=1&&(n=t.width),Math.abs(t.height-r)<=1&&(r=t.height),{x:e.offsetLeft,y:e.offsetTop,width:n,height:r}}function Y$(e,t){var n=t.getRootNode&&t.getRootNode();if(e.contains(t))return!0;if(n&&yA(n)){var r=t;do{if(r&&e.isSameNode(r))return!0;r=r.parentNode||r.host}while(r)}return!1}function Bu(e){return ps(e).getComputedStyle(e)}function yhe(e){return["table","td","th"].indexOf(js(e))>=0}function fd(e){return((Dh(e)?e.ownerDocument:e.document)||window.document).documentElement}function p4(e){return js(e)==="html"?e:e.assignedSlot||e.parentNode||(yA(e)?e.host:null)||fd(e)}function BT(e){return!fl(e)||Bu(e).position==="fixed"?null:e.offsetParent}function _he(e){var t=navigator.userAgent.toLowerCase().indexOf("firefox")!==-1,n=navigator.userAgent.indexOf("Trident")!==-1;if(n&&fl(e)){var r=Bu(e);if(r.position==="fixed")return null}var o=p4(e);for(yA(o)&&(o=o.host);fl(o)&&["html","body"].indexOf(js(o))<0;){var i=Bu(o);if(i.transform!=="none"||i.perspective!=="none"||i.contain==="paint"||["transform","perspective"].indexOf(i.willChange)!==-1||t&&i.willChange==="filter"||t&&i.filter&&i.filter!=="none")return o;o=o.parentNode}return null}function Bg(e){for(var t=ps(e),n=BT(e);n&&yhe(n)&&Bu(n).position==="static";)n=BT(n);return n&&(js(n)==="html"||js(n)==="body"&&Bu(n).position==="static")?t:n||_he(e)||t}function bA(e){return["top","bottom"].indexOf(e)>=0?"x":"y"}function G0(e,t,n){return mf(e,c_(t,n))}function bhe(e,t,n){var r=G0(e,t,n);return r>n?n:r}function G$(){return{top:0,right:0,bottom:0,left:0}}function q$(e){return Object.assign({},G$(),e)}function X$(e,t){return t.reduce(function(n,r){return n[r]=e,n},{})}var whe=function(e,t){return e=typeof e=="function"?e(Object.assign({},t.rects,{placement:t.placement})):e,q$(typeof e!="number"?e:X$(e,Lg))};function xhe(e){var t,n=e.state,r=e.name,o=e.options,i=n.elements.arrow,a=n.modifiersData.popperOffsets,l=Ps(n.placement),s=bA(l),u=[_a,wl].indexOf(l)>=0,c=u?"height":"width";if(!(!i||!a)){var d=whe(o.padding,n),f=_A(i),h=s==="y"?ya:_a,v=s==="y"?bl:wl,m=n.rects.reference[c]+n.rects.reference[s]-a[s]-n.rects.popper[c],y=a[s]-n.rects.reference[s],g=Bg(i),b=g?s==="y"?g.clientHeight||0:g.clientWidth||0:0,x=m/2-y/2,C=d[h],E=b-f[c]-d[v],A=b/2-f[c]/2+x,M=G0(C,A,E),D=s;n.modifiersData[r]=(t={},t[D]=M,t.centerOffset=M-A,t)}}function Che(e){var t=e.state,n=e.options,r=n.element,o=r===void 0?"[data-popper-arrow]":r;o!=null&&(typeof o=="string"&&(o=t.elements.popper.querySelector(o),!o)||!Y$(t.elements.popper,o)||(t.elements.arrow=o))}var Ehe={name:"arrow",enabled:!0,phase:"main",fn:xhe,effect:Che,requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function Rh(e){return e.split("-")[1]}var Ahe={top:"auto",right:"auto",bottom:"auto",left:"auto"};function She(e){var t=e.x,n=e.y,r=window,o=r.devicePixelRatio||1;return{x:Nh(t*o)/o||0,y:Nh(n*o)/o||0}}function $T(e){var t,n=e.popper,r=e.popperRect,o=e.placement,i=e.variation,a=e.offsets,l=e.position,s=e.gpuAcceleration,u=e.adaptive,c=e.roundOffsets,d=e.isFixed,f=a.x,h=f===void 0?0:f,v=a.y,m=v===void 0?0:v,y=typeof c=="function"?c({x:h,y:m}):{x:h,y:m};h=y.x,m=y.y;var g=a.hasOwnProperty("x"),b=a.hasOwnProperty("y"),x=_a,C=ya,E=window;if(u){var A=Bg(n),M="clientHeight",D="clientWidth";if(A===ps(n)&&(A=fd(n),Bu(A).position!=="static"&&l==="absolute"&&(M="scrollHeight",D="scrollWidth")),A=A,o===ya||(o===_a||o===wl)&&i===Bm){C=bl;var I=d&&A===E&&E.visualViewport?E.visualViewport.height:A[M];m-=I-r.height,m*=s?1:-1}if(o===_a||(o===ya||o===bl)&&i===Bm){x=wl;var T=d&&A===E&&E.visualViewport?E.visualViewport.width:A[D];h-=T-r.width,h*=s?1:-1}}var N=Object.assign({position:l},u&&Ahe),L=c===!0?She({x:h,y:m}):{x:h,y:m};if(h=L.x,m=L.y,s){var P;return Object.assign({},N,(P={},P[C]=b?"0":"",P[x]=g?"0":"",P.transform=(E.devicePixelRatio||1)<=1?"translate("+h+"px, "+m+"px)":"translate3d("+h+"px, "+m+"px, 0)",P))}return Object.assign({},N,(t={},t[C]=b?m+"px":"",t[x]=g?h+"px":"",t.transform="",t))}function khe(e){var t=e.state,n=e.options,r=n.gpuAcceleration,o=r===void 0?!0:r,i=n.adaptive,a=i===void 0?!0:i,l=n.roundOffsets,s=l===void 0?!0:l,u={placement:Ps(t.placement),variation:Rh(t.placement),popper:t.elements.popper,popperRect:t.rects.popper,gpuAcceleration:o,isFixed:t.options.strategy==="fixed"};t.modifiersData.popperOffsets!=null&&(t.styles.popper=Object.assign({},t.styles.popper,$T(Object.assign({},u,{offsets:t.modifiersData.popperOffsets,position:t.options.strategy,adaptive:a,roundOffsets:s})))),t.modifiersData.arrow!=null&&(t.styles.arrow=Object.assign({},t.styles.arrow,$T(Object.assign({},u,{offsets:t.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:s})))),t.attributes.popper=Object.assign({},t.attributes.popper,{"data-popper-placement":t.placement})}var Q$={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:khe,data:{}},W1={passive:!0};function Mhe(e){var t=e.state,n=e.instance,r=e.options,o=r.scroll,i=o===void 0?!0:o,a=r.resize,l=a===void 0?!0:a,s=ps(t.elements.popper),u=[].concat(t.scrollParents.reference,t.scrollParents.popper);return i&&u.forEach(function(c){c.addEventListener("scroll",n.update,W1)}),l&&s.addEventListener("resize",n.update,W1),function(){i&&u.forEach(function(c){c.removeEventListener("scroll",n.update,W1)}),l&&s.removeEventListener("resize",n.update,W1)}}var J$={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:Mhe,data:{}},The={left:"right",right:"left",bottom:"top",top:"bottom"};function ty(e){return e.replace(/left|right|bottom|top/g,function(t){return The[t]})}var Ohe={start:"end",end:"start"};function zT(e){return e.replace(/start|end/g,function(t){return Ohe[t]})}function wA(e){var t=ps(e),n=t.pageXOffset,r=t.pageYOffset;return{scrollLeft:n,scrollTop:r}}function xA(e){return Fh(fd(e)).left+wA(e).scrollLeft}function Dhe(e){var t=ps(e),n=fd(e),r=t.visualViewport,o=n.clientWidth,i=n.clientHeight,a=0,l=0;return r&&(o=r.width,i=r.height,/^((?!chrome|android).)*safari/i.test(navigator.userAgent)||(a=r.offsetLeft,l=r.offsetTop)),{width:o,height:i,x:a+xA(e),y:l}}function Nhe(e){var t,n=fd(e),r=wA(e),o=(t=e.ownerDocument)==null?void 0:t.body,i=mf(n.scrollWidth,n.clientWidth,o?o.scrollWidth:0,o?o.clientWidth:0),a=mf(n.scrollHeight,n.clientHeight,o?o.scrollHeight:0,o?o.clientHeight:0),l=-r.scrollLeft+xA(e),s=-r.scrollTop;return Bu(o||n).direction==="rtl"&&(l+=mf(n.clientWidth,o?o.clientWidth:0)-i),{width:i,height:a,x:l,y:s}}function CA(e){var t=Bu(e),n=t.overflow,r=t.overflowX,o=t.overflowY;return/auto|scroll|overlay|hidden/.test(n+o+r)}function Z$(e){return["html","body","#document"].indexOf(js(e))>=0?e.ownerDocument.body:fl(e)&&CA(e)?e:Z$(p4(e))}function q0(e,t){var n;t===void 0&&(t=[]);var r=Z$(e),o=r===((n=e.ownerDocument)==null?void 0:n.body),i=ps(r),a=o?[i].concat(i.visualViewport||[],CA(r)?r:[]):r,l=t.concat(a);return o?l:l.concat(q0(p4(a)))}function x5(e){return Object.assign({},e,{left:e.x,top:e.y,right:e.x+e.width,bottom:e.y+e.height})}function Fhe(e){var t=Fh(e);return t.top=t.top+e.clientTop,t.left=t.left+e.clientLeft,t.bottom=t.top+e.clientHeight,t.right=t.left+e.clientWidth,t.width=e.clientWidth,t.height=e.clientHeight,t.x=t.left,t.y=t.top,t}function jT(e,t){return t===U$?x5(Dhe(e)):Dh(t)?Fhe(t):x5(Nhe(fd(e)))}function Rhe(e){var t=q0(p4(e)),n=["absolute","fixed"].indexOf(Bu(e).position)>=0,r=n&&fl(e)?Bg(e):e;return Dh(r)?t.filter(function(o){return Dh(o)&&Y$(o,r)&&js(o)!=="body"}):[]}function Phe(e,t,n){var r=t==="clippingParents"?Rhe(e):[].concat(t),o=[].concat(r,[n]),i=o[0],a=o.reduce(function(l,s){var u=jT(e,s);return l.top=mf(u.top,l.top),l.right=c_(u.right,l.right),l.bottom=c_(u.bottom,l.bottom),l.left=mf(u.left,l.left),l},jT(e,i));return a.width=a.right-a.left,a.height=a.bottom-a.top,a.x=a.left,a.y=a.top,a}function ez(e){var t=e.reference,n=e.element,r=e.placement,o=r?Ps(r):null,i=r?Rh(r):null,a=t.x+t.width/2-n.width/2,l=t.y+t.height/2-n.height/2,s;switch(o){case ya:s={x:a,y:t.y-n.height};break;case bl:s={x:a,y:t.y+t.height};break;case wl:s={x:t.x+t.width,y:l};break;case _a:s={x:t.x-n.width,y:l};break;default:s={x:t.x,y:t.y}}var u=o?bA(o):null;if(u!=null){var c=u==="y"?"height":"width";switch(i){case Oh:s[u]=s[u]-(t[c]/2-n[c]/2);break;case Bm:s[u]=s[u]+(t[c]/2-n[c]/2);break}}return s}function $m(e,t){t===void 0&&(t={});var n=t,r=n.placement,o=r===void 0?e.placement:r,i=n.boundary,a=i===void 0?ohe:i,l=n.rootBoundary,s=l===void 0?U$:l,u=n.elementContext,c=u===void 0?m0:u,d=n.altBoundary,f=d===void 0?!1:d,h=n.padding,v=h===void 0?0:h,m=q$(typeof v!="number"?v:X$(v,Lg)),y=c===m0?ihe:m0,g=e.rects.popper,b=e.elements[f?y:c],x=Phe(Dh(b)?b:b.contextElement||fd(e.elements.popper),a,s),C=Fh(e.elements.reference),E=ez({reference:C,element:g,placement:o}),A=x5(Object.assign({},g,E)),M=c===m0?A:C,D={top:x.top-M.top+m.top,bottom:M.bottom-x.bottom+m.bottom,left:x.left-M.left+m.left,right:M.right-x.right+m.right},I=e.modifiersData.offset;if(c===m0&&I){var T=I[o];Object.keys(D).forEach(function(N){var L=[wl,bl].indexOf(N)>=0?1:-1,P=[ya,bl].indexOf(N)>=0?"y":"x";D[N]+=T[P]*L})}return D}function Ihe(e,t){t===void 0&&(t={});var n=t,r=n.placement,o=n.boundary,i=n.rootBoundary,a=n.padding,l=n.flipVariations,s=n.allowedAutoPlacements,u=s===void 0?Gf:s,c=Rh(r),d=c?l?LT:LT.filter(function(v){return Rh(v)===c}):Lg,f=d.filter(function(v){return u.indexOf(v)>=0});f.length===0&&(f=d);var h=f.reduce(function(v,m){return v[m]=$m(e,{placement:m,boundary:o,rootBoundary:i,padding:a})[Ps(m)],v},{});return Object.keys(h).sort(function(v,m){return h[v]-h[m]})}function Lhe(e){if(Ps(e)===gA)return[];var t=ty(e);return[zT(e),t,zT(t)]}function Bhe(e){var t=e.state,n=e.options,r=e.name;if(!t.modifiersData[r]._skip){for(var o=n.mainAxis,i=o===void 0?!0:o,a=n.altAxis,l=a===void 0?!0:a,s=n.fallbackPlacements,u=n.padding,c=n.boundary,d=n.rootBoundary,f=n.altBoundary,h=n.flipVariations,v=h===void 0?!0:h,m=n.allowedAutoPlacements,y=t.options.placement,g=Ps(y),b=g===y,x=s||(b||!v?[ty(y)]:Lhe(y)),C=[y].concat(x).reduce(function(ne,fe){return ne.concat(Ps(fe)===gA?Ihe(t,{placement:fe,boundary:c,rootBoundary:d,padding:u,flipVariations:v,allowedAutoPlacements:m}):fe)},[]),E=t.rects.reference,A=t.rects.popper,M=new Map,D=!0,I=C[0],T=0;T=0,O=j?"width":"height",z=$m(t,{placement:N,boundary:c,rootBoundary:d,altBoundary:f,padding:u}),H=j?P?wl:_a:P?bl:ya;E[O]>A[O]&&(H=ty(H));var K=ty(H),B=[];if(i&&B.push(z[L]<=0),l&&B.push(z[H]<=0,z[K]<=0),B.every(function(ne){return ne})){I=N,D=!1;break}M.set(N,B)}if(D)for(var Z=v?3:1,oe=function(ne){var fe=C.find(function(Q){var ie=M.get(Q);if(ie)return ie.slice(0,ne).every(function(he){return he})});if(fe)return I=fe,"break"},V=Z;V>0;V--){var q=oe(V);if(q==="break")break}t.placement!==I&&(t.modifiersData[r]._skip=!0,t.placement=I,t.reset=!0)}}var $he={name:"flip",enabled:!0,phase:"main",fn:Bhe,requiresIfExists:["offset"],data:{_skip:!1}};function VT(e,t,n){return n===void 0&&(n={x:0,y:0}),{top:e.top-t.height-n.y,right:e.right-t.width+n.x,bottom:e.bottom-t.height+n.y,left:e.left-t.width-n.x}}function HT(e){return[ya,wl,bl,_a].some(function(t){return e[t]>=0})}function zhe(e){var t=e.state,n=e.name,r=t.rects.reference,o=t.rects.popper,i=t.modifiersData.preventOverflow,a=$m(t,{elementContext:"reference"}),l=$m(t,{altBoundary:!0}),s=VT(a,r),u=VT(l,o,i),c=HT(s),d=HT(u);t.modifiersData[n]={referenceClippingOffsets:s,popperEscapeOffsets:u,isReferenceHidden:c,hasPopperEscaped:d},t.attributes.popper=Object.assign({},t.attributes.popper,{"data-popper-reference-hidden":c,"data-popper-escaped":d})}var jhe={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:zhe};function Vhe(e,t,n){var r=Ps(e),o=[_a,ya].indexOf(r)>=0?-1:1,i=typeof n=="function"?n(Object.assign({},t,{placement:e})):n,a=i[0],l=i[1];return a=a||0,l=(l||0)*o,[_a,wl].indexOf(r)>=0?{x:l,y:a}:{x:a,y:l}}function Hhe(e){var t=e.state,n=e.options,r=e.name,o=n.offset,i=o===void 0?[0,0]:o,a=Gf.reduce(function(c,d){return c[d]=Vhe(d,t.rects,i),c},{}),l=a[t.placement],s=l.x,u=l.y;t.modifiersData.popperOffsets!=null&&(t.modifiersData.popperOffsets.x+=s,t.modifiersData.popperOffsets.y+=u),t.modifiersData[r]=a}var Whe={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:Hhe};function Uhe(e){var t=e.state,n=e.name;t.modifiersData[n]=ez({reference:t.rects.reference,element:t.rects.popper,placement:t.placement})}var tz={name:"popperOffsets",enabled:!0,phase:"read",fn:Uhe,data:{}};function Khe(e){return e==="x"?"y":"x"}function Yhe(e){var t=e.state,n=e.options,r=e.name,o=n.mainAxis,i=o===void 0?!0:o,a=n.altAxis,l=a===void 0?!1:a,s=n.boundary,u=n.rootBoundary,c=n.altBoundary,d=n.padding,f=n.tether,h=f===void 0?!0:f,v=n.tetherOffset,m=v===void 0?0:v,y=$m(t,{boundary:s,rootBoundary:u,padding:d,altBoundary:c}),g=Ps(t.placement),b=Rh(t.placement),x=!b,C=bA(g),E=Khe(C),A=t.modifiersData.popperOffsets,M=t.rects.reference,D=t.rects.popper,I=typeof m=="function"?m(Object.assign({},t.rects,{placement:t.placement})):m,T=typeof I=="number"?{mainAxis:I,altAxis:I}:Object.assign({mainAxis:0,altAxis:0},I),N=t.modifiersData.offset?t.modifiersData.offset[t.placement]:null,L={x:0,y:0};if(A){if(i){var P,j=C==="y"?ya:_a,O=C==="y"?bl:wl,z=C==="y"?"height":"width",H=A[C],K=H+y[j],B=H-y[O],Z=h?-D[z]/2:0,oe=b===Oh?M[z]:D[z],V=b===Oh?-D[z]:-M[z],q=t.elements.arrow,ne=h&&q?_A(q):{width:0,height:0},fe=t.modifiersData["arrow#persistent"]?t.modifiersData["arrow#persistent"].padding:G$(),Q=fe[j],ie=fe[O],he=G0(0,M[z],ne[z]),ae=x?M[z]/2-Z-he-Q-T.mainAxis:oe-he-Q-T.mainAxis,le=x?-M[z]/2+Z+he+ie+T.mainAxis:V+he+ie+T.mainAxis,J=t.elements.arrow&&Bg(t.elements.arrow),ce=J?C==="y"?J.clientTop||0:J.clientLeft||0:0,ye=(P=N==null?void 0:N[C])!=null?P:0,Ne=H+ae-ye-ce,Be=H+le-ye,Ge=G0(h?c_(K,Ne):K,H,h?mf(B,Be):B);A[C]=Ge,L[C]=Ge-H}if(l){var qe,Te=C==="x"?ya:_a,pe=C==="x"?bl:wl,me=A[E],Ee=E==="y"?"height":"width",$e=me+y[Te],De=me-y[pe],Xe=[ya,_a].indexOf(g)!==-1,Ie=(qe=N==null?void 0:N[E])!=null?qe:0,Je=Xe?$e:me-M[Ee]-D[Ee]-Ie+T.altAxis,ct=Xe?me+M[Ee]+D[Ee]-Ie-T.altAxis:De,wt=h&&Xe?bhe(Je,me,ct):G0(h?Je:$e,me,h?ct:De);A[E]=wt,L[E]=wt-me}t.modifiersData[r]=L}}var Ghe={name:"preventOverflow",enabled:!0,phase:"main",fn:Yhe,requiresIfExists:["offset"]};function qhe(e){return{scrollLeft:e.scrollLeft,scrollTop:e.scrollTop}}function Xhe(e){return e===ps(e)||!fl(e)?wA(e):qhe(e)}function Qhe(e){var t=e.getBoundingClientRect(),n=Nh(t.width)/e.offsetWidth||1,r=Nh(t.height)/e.offsetHeight||1;return n!==1||r!==1}function Jhe(e,t,n){n===void 0&&(n=!1);var r=fl(t),o=fl(t)&&Qhe(t),i=fd(t),a=Fh(e,o),l={scrollLeft:0,scrollTop:0},s={x:0,y:0};return(r||!r&&!n)&&((js(t)!=="body"||CA(i))&&(l=Xhe(t)),fl(t)?(s=Fh(t,!0),s.x+=t.clientLeft,s.y+=t.clientTop):i&&(s.x=xA(i))),{x:a.left+l.scrollLeft-s.x,y:a.top+l.scrollTop-s.y,width:a.width,height:a.height}}function Zhe(e){var t=new Map,n=new Set,r=[];e.forEach(function(i){t.set(i.name,i)});function o(i){n.add(i.name);var a=[].concat(i.requires||[],i.requiresIfExists||[]);a.forEach(function(l){if(!n.has(l)){var s=t.get(l);s&&o(s)}}),r.push(i)}return e.forEach(function(i){n.has(i.name)||o(i)}),r}function eve(e){var t=Zhe(e);return vhe.reduce(function(n,r){return n.concat(t.filter(function(o){return o.phase===r}))},[])}function tve(e){var t;return function(){return t||(t=new Promise(function(n){Promise.resolve().then(function(){t=void 0,n(e())})})),t}}function nve(e){var t=e.reduce(function(n,r){var o=n[r.name];return n[r.name]=o?Object.assign({},o,r,{options:Object.assign({},o.options,r.options),data:Object.assign({},o.data,r.data)}):r,n},{});return Object.keys(t).map(function(n){return t[n]})}var WT={placement:"bottom",modifiers:[],strategy:"absolute"};function UT(){for(var e=arguments.length,t=new Array(e),n=0;nn.isDot?"":Of(n.value)&&Of(n.max)?n.max{var a,l,s,u,c;return[{backgroundColor:n.color,marginRight:b5(-((l=(a=n.offset)==null?void 0:a[0])!=null?l:0)),marginTop:b5((u=(s=n.offset)==null?void 0:s[1])!=null?u:0)},(c=n.badgeStyle)!=null?c:{}]});return t({content:o}),(a,l)=>(R(),W("div",{class:re(w(r).b())},[Qe(a.$slots,"default"),Y(Gr,{name:`${w(r).namespace.value}-zoom-in-center`,persisted:""},{default:X(()=>[It($("sup",{class:re([w(r).e("content"),w(r).em("content",a.type),w(r).is("fixed",!!a.$slots.default),w(r).is("dot",a.isDot),w(r).is("hide-zero",!a.showZero&&a.value===0),a.badgeClass]),style:bt(w(i))},[Qe(a.$slots,"content",{value:w(o)},()=>[ot(Pe(w(o)),1)])],6),[[rn,!a.hidden&&(w(o)||a.isDot||a.$slots.content)]])]),_:3},8,["name"])],2))}});var sve=mA(lve,[["__file","badge.vue"]]);const uve=W$(sve);function ai(e,t){cve(e)&&(e="100%");var n=dve(e);return e=t===360?e:Math.min(t,Math.max(0,parseFloat(e))),n&&(e=parseInt(String(e*t),10)/100),Math.abs(e-t)<1e-6?1:(t===360?e=(e<0?e%t+t:e%t)/parseFloat(String(t)):e=e%t/parseFloat(String(t)),e)}function U1(e){return Math.min(1,Math.max(0,e))}function cve(e){return typeof e=="string"&&e.indexOf(".")!==-1&&parseFloat(e)===1}function dve(e){return typeof e=="string"&&e.indexOf("%")!==-1}function rz(e){return e=parseFloat(e),(isNaN(e)||e<0||e>1)&&(e=1),e}function K1(e){return e<=1?"".concat(Number(e)*100,"%"):e}function lf(e){return e.length===1?"0"+e:String(e)}function fve(e,t,n){return{r:ai(e,255)*255,g:ai(t,255)*255,b:ai(n,255)*255}}function KT(e,t,n){e=ai(e,255),t=ai(t,255),n=ai(n,255);var r=Math.max(e,t,n),o=Math.min(e,t,n),i=0,a=0,l=(r+o)/2;if(r===o)a=0,i=0;else{var s=r-o;switch(a=l>.5?s/(2-r-o):s/(r+o),r){case e:i=(t-n)/s+(t1&&(n-=1),n<1/6?e+(t-e)*(6*n):n<1/2?t:n<2/3?e+(t-e)*(2/3-n)*6:e}function pve(e,t,n){var r,o,i;if(e=ai(e,360),t=ai(t,100),n=ai(n,100),t===0)o=n,i=n,r=n;else{var a=n<.5?n*(1+t):n+t-n*t,l=2*n-a;r=h3(l,a,e+1/3),o=h3(l,a,e),i=h3(l,a,e-1/3)}return{r:r*255,g:o*255,b:i*255}}function YT(e,t,n){e=ai(e,255),t=ai(t,255),n=ai(n,255);var r=Math.max(e,t,n),o=Math.min(e,t,n),i=0,a=r,l=r-o,s=r===0?0:l/r;if(r===o)i=0;else{switch(r){case e:i=(t-n)/l+(t>16,g:(e&65280)>>8,b:e&255}}var C5={aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgreen:"#006400",darkgrey:"#a9a9a9",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",goldenrod:"#daa520",gold:"#ffd700",gray:"#808080",green:"#008000",greenyellow:"#adff2f",grey:"#808080",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavenderblush:"#fff0f5",lavender:"#e6e6fa",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgreen:"#90ee90",lightgrey:"#d3d3d3",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370db",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#db7093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",rebeccapurple:"#663399",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"};function yve(e){var t={r:0,g:0,b:0},n=1,r=null,o=null,i=null,a=!1,l=!1;return typeof e=="string"&&(e=wve(e)),typeof e=="object"&&(fu(e.r)&&fu(e.g)&&fu(e.b)?(t=fve(e.r,e.g,e.b),a=!0,l=String(e.r).substr(-1)==="%"?"prgb":"rgb"):fu(e.h)&&fu(e.s)&&fu(e.v)?(r=K1(e.s),o=K1(e.v),t=hve(e.h,r,o),a=!0,l="hsv"):fu(e.h)&&fu(e.s)&&fu(e.l)&&(r=K1(e.s),i=K1(e.l),t=pve(e.h,r,i),a=!0,l="hsl"),Object.prototype.hasOwnProperty.call(e,"a")&&(n=e.a)),n=rz(n),{ok:a,format:e.format||l,r:Math.min(255,Math.max(t.r,0)),g:Math.min(255,Math.max(t.g,0)),b:Math.min(255,Math.max(t.b,0)),a:n}}var _ve="[-\\+]?\\d+%?",bve="[-\\+]?\\d*\\.\\d+%?",Dc="(?:".concat(bve,")|(?:").concat(_ve,")"),v3="[\\s|\\(]+(".concat(Dc,")[,|\\s]+(").concat(Dc,")[,|\\s]+(").concat(Dc,")\\s*\\)?"),m3="[\\s|\\(]+(".concat(Dc,")[,|\\s]+(").concat(Dc,")[,|\\s]+(").concat(Dc,")[,|\\s]+(").concat(Dc,")\\s*\\)?"),Bl={CSS_UNIT:new RegExp(Dc),rgb:new RegExp("rgb"+v3),rgba:new RegExp("rgba"+m3),hsl:new RegExp("hsl"+v3),hsla:new RegExp("hsla"+m3),hsv:new RegExp("hsv"+v3),hsva:new RegExp("hsva"+m3),hex3:/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,hex6:/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,hex4:/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,hex8:/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/};function wve(e){if(e=e.trim().toLowerCase(),e.length===0)return!1;var t=!1;if(C5[e])e=C5[e],t=!0;else if(e==="transparent")return{r:0,g:0,b:0,a:0,format:"name"};var n=Bl.rgb.exec(e);return n?{r:n[1],g:n[2],b:n[3]}:(n=Bl.rgba.exec(e),n?{r:n[1],g:n[2],b:n[3],a:n[4]}:(n=Bl.hsl.exec(e),n?{h:n[1],s:n[2],l:n[3]}:(n=Bl.hsla.exec(e),n?{h:n[1],s:n[2],l:n[3],a:n[4]}:(n=Bl.hsv.exec(e),n?{h:n[1],s:n[2],v:n[3]}:(n=Bl.hsva.exec(e),n?{h:n[1],s:n[2],v:n[3],a:n[4]}:(n=Bl.hex8.exec(e),n?{r:Fa(n[1]),g:Fa(n[2]),b:Fa(n[3]),a:qT(n[4]),format:t?"name":"hex8"}:(n=Bl.hex6.exec(e),n?{r:Fa(n[1]),g:Fa(n[2]),b:Fa(n[3]),format:t?"name":"hex"}:(n=Bl.hex4.exec(e),n?{r:Fa(n[1]+n[1]),g:Fa(n[2]+n[2]),b:Fa(n[3]+n[3]),a:qT(n[4]+n[4]),format:t?"name":"hex8"}:(n=Bl.hex3.exec(e),n?{r:Fa(n[1]+n[1]),g:Fa(n[2]+n[2]),b:Fa(n[3]+n[3]),format:t?"name":"hex"}:!1)))))))))}function fu(e){return!!Bl.CSS_UNIT.exec(String(e))}var oz=function(){function e(t,n){t===void 0&&(t=""),n===void 0&&(n={});var r;if(t instanceof e)return t;typeof t=="number"&&(t=gve(t)),this.originalInput=t;var o=yve(t);this.originalInput=t,this.r=o.r,this.g=o.g,this.b=o.b,this.a=o.a,this.roundA=Math.round(100*this.a)/100,this.format=(r=n.format)!==null&&r!==void 0?r:o.format,this.gradientType=n.gradientType,this.r<1&&(this.r=Math.round(this.r)),this.g<1&&(this.g=Math.round(this.g)),this.b<1&&(this.b=Math.round(this.b)),this.isValid=o.ok}return e.prototype.isDark=function(){return this.getBrightness()<128},e.prototype.isLight=function(){return!this.isDark()},e.prototype.getBrightness=function(){var t=this.toRgb();return(t.r*299+t.g*587+t.b*114)/1e3},e.prototype.getLuminance=function(){var t=this.toRgb(),n,r,o,i=t.r/255,a=t.g/255,l=t.b/255;return i<=.03928?n=i/12.92:n=Math.pow((i+.055)/1.055,2.4),a<=.03928?r=a/12.92:r=Math.pow((a+.055)/1.055,2.4),l<=.03928?o=l/12.92:o=Math.pow((l+.055)/1.055,2.4),.2126*n+.7152*r+.0722*o},e.prototype.getAlpha=function(){return this.a},e.prototype.setAlpha=function(t){return this.a=rz(t),this.roundA=Math.round(100*this.a)/100,this},e.prototype.isMonochrome=function(){var t=this.toHsl().s;return t===0},e.prototype.toHsv=function(){var t=YT(this.r,this.g,this.b);return{h:t.h*360,s:t.s,v:t.v,a:this.a}},e.prototype.toHsvString=function(){var t=YT(this.r,this.g,this.b),n=Math.round(t.h*360),r=Math.round(t.s*100),o=Math.round(t.v*100);return this.a===1?"hsv(".concat(n,", ").concat(r,"%, ").concat(o,"%)"):"hsva(".concat(n,", ").concat(r,"%, ").concat(o,"%, ").concat(this.roundA,")")},e.prototype.toHsl=function(){var t=KT(this.r,this.g,this.b);return{h:t.h*360,s:t.s,l:t.l,a:this.a}},e.prototype.toHslString=function(){var t=KT(this.r,this.g,this.b),n=Math.round(t.h*360),r=Math.round(t.s*100),o=Math.round(t.l*100);return this.a===1?"hsl(".concat(n,", ").concat(r,"%, ").concat(o,"%)"):"hsla(".concat(n,", ").concat(r,"%, ").concat(o,"%, ").concat(this.roundA,")")},e.prototype.toHex=function(t){return t===void 0&&(t=!1),GT(this.r,this.g,this.b,t)},e.prototype.toHexString=function(t){return t===void 0&&(t=!1),"#"+this.toHex(t)},e.prototype.toHex8=function(t){return t===void 0&&(t=!1),vve(this.r,this.g,this.b,this.a,t)},e.prototype.toHex8String=function(t){return t===void 0&&(t=!1),"#"+this.toHex8(t)},e.prototype.toHexShortString=function(t){return t===void 0&&(t=!1),this.a===1?this.toHexString(t):this.toHex8String(t)},e.prototype.toRgb=function(){return{r:Math.round(this.r),g:Math.round(this.g),b:Math.round(this.b),a:this.a}},e.prototype.toRgbString=function(){var t=Math.round(this.r),n=Math.round(this.g),r=Math.round(this.b);return this.a===1?"rgb(".concat(t,", ").concat(n,", ").concat(r,")"):"rgba(".concat(t,", ").concat(n,", ").concat(r,", ").concat(this.roundA,")")},e.prototype.toPercentageRgb=function(){var t=function(n){return"".concat(Math.round(ai(n,255)*100),"%")};return{r:t(this.r),g:t(this.g),b:t(this.b),a:this.a}},e.prototype.toPercentageRgbString=function(){var t=function(n){return Math.round(ai(n,255)*100)};return this.a===1?"rgb(".concat(t(this.r),"%, ").concat(t(this.g),"%, ").concat(t(this.b),"%)"):"rgba(".concat(t(this.r),"%, ").concat(t(this.g),"%, ").concat(t(this.b),"%, ").concat(this.roundA,")")},e.prototype.toName=function(){if(this.a===0)return"transparent";if(this.a<1)return!1;for(var t="#"+GT(this.r,this.g,this.b,!1),n=0,r=Object.entries(C5);n=0,i=!n&&o&&(t.startsWith("hex")||t==="name");return i?t==="name"&&this.a===0?this.toName():this.toRgbString():(t==="rgb"&&(r=this.toRgbString()),t==="prgb"&&(r=this.toPercentageRgbString()),(t==="hex"||t==="hex6")&&(r=this.toHexString()),t==="hex3"&&(r=this.toHexString(!0)),t==="hex4"&&(r=this.toHex8String(!0)),t==="hex8"&&(r=this.toHex8String()),t==="name"&&(r=this.toName()),t==="hsl"&&(r=this.toHslString()),t==="hsv"&&(r=this.toHsvString()),r||this.toHexString())},e.prototype.toNumber=function(){return(Math.round(this.r)<<16)+(Math.round(this.g)<<8)+Math.round(this.b)},e.prototype.clone=function(){return new e(this.toString())},e.prototype.lighten=function(t){t===void 0&&(t=10);var n=this.toHsl();return n.l+=t/100,n.l=U1(n.l),new e(n)},e.prototype.brighten=function(t){t===void 0&&(t=10);var n=this.toRgb();return n.r=Math.max(0,Math.min(255,n.r-Math.round(255*-(t/100)))),n.g=Math.max(0,Math.min(255,n.g-Math.round(255*-(t/100)))),n.b=Math.max(0,Math.min(255,n.b-Math.round(255*-(t/100)))),new e(n)},e.prototype.darken=function(t){t===void 0&&(t=10);var n=this.toHsl();return n.l-=t/100,n.l=U1(n.l),new e(n)},e.prototype.tint=function(t){return t===void 0&&(t=10),this.mix("white",t)},e.prototype.shade=function(t){return t===void 0&&(t=10),this.mix("black",t)},e.prototype.desaturate=function(t){t===void 0&&(t=10);var n=this.toHsl();return n.s-=t/100,n.s=U1(n.s),new e(n)},e.prototype.saturate=function(t){t===void 0&&(t=10);var n=this.toHsl();return n.s+=t/100,n.s=U1(n.s),new e(n)},e.prototype.greyscale=function(){return this.desaturate(100)},e.prototype.spin=function(t){var n=this.toHsl(),r=(n.h+t)%360;return n.h=r<0?360+r:r,new e(n)},e.prototype.mix=function(t,n){n===void 0&&(n=50);var r=this.toRgb(),o=new e(t).toRgb(),i=n/100,a={r:(o.r-r.r)*i+r.r,g:(o.g-r.g)*i+r.g,b:(o.b-r.b)*i+r.b,a:(o.a-r.a)*i+r.a};return new e(a)},e.prototype.analogous=function(t,n){t===void 0&&(t=6),n===void 0&&(n=30);var r=this.toHsl(),o=360/n,i=[this];for(r.h=(r.h-(o*t>>1)+720)%360;--t;)r.h=(r.h+o)%360,i.push(new e(r));return i},e.prototype.complement=function(){var t=this.toHsl();return t.h=(t.h+180)%360,new e(t)},e.prototype.monochromatic=function(t){t===void 0&&(t=6);for(var n=this.toHsv(),r=n.h,o=n.s,i=n.v,a=[],l=1/t;t--;)a.push(new e({h:r,s:o,v:i})),i=(i+l)%1;return a},e.prototype.splitcomplement=function(){var t=this.toHsl(),n=t.h;return[this,new e({h:(n+72)%360,s:t.s,l:t.l}),new e({h:(n+216)%360,s:t.s,l:t.l})]},e.prototype.onBackground=function(t){var n=this.toRgb(),r=new e(t).toRgb(),o=n.a+r.a*(1-n.a);return new e({r:(n.r*n.a+r.r*r.a*(1-n.a))/o,g:(n.g*n.a+r.g*r.a*(1-n.a))/o,b:(n.b*n.a+r.b*r.a*(1-n.a))/o,a:o})},e.prototype.triad=function(){return this.polyad(3)},e.prototype.tetrad=function(){return this.polyad(4)},e.prototype.polyad=function(t){for(var n=this.toHsl(),r=n.h,o=[this],i=360/t,a=1;a=O?j:""+Array(O+1-H.length).join(z)+j},C={s:x,z:function(j){var O=-j.utcOffset(),z=Math.abs(O),H=Math.floor(z/60),K=z%60;return(O<=0?"+":"-")+x(H,2,"0")+":"+x(K,2,"0")},m:function j(O,z){if(O.date()1)return j(Z[0])}else{var oe=O.name;A[oe]=O,K=oe}return!H&&K&&(E=K),K||!H&&E},T=function(j,O){if(D(j))return j.clone();var z=typeof O=="object"?O:{};return z.date=j,z.args=arguments,new L(z)},N=C;N.l=I,N.i=D,N.w=function(j,O){return T(j,{locale:O.$L,utc:O.$u,x:O.$x,$offset:O.$offset})};var L=function(){function j(z){this.$L=I(z.locale,null,!0),this.parse(z),this.$x=this.$x||z.x||{},this[M]=!0}var O=j.prototype;return O.parse=function(z){this.$d=function(H){var K=H.date,B=H.utc;if(K===null)return new Date(NaN);if(N.u(K))return new Date;if(K instanceof Date)return new Date(K);if(typeof K=="string"&&!/Z$/i.test(K)){var Z=K.match(y);if(Z){var oe=Z[2]-1||0,V=(Z[7]||"0").substring(0,3);return B?new Date(Date.UTC(Z[1],oe,Z[3]||1,Z[4]||0,Z[5]||0,Z[6]||0,V)):new Date(Z[1],oe,Z[3]||1,Z[4]||0,Z[5]||0,Z[6]||0,V)}}return new Date(K)}(z),this.init()},O.init=function(){var z=this.$d;this.$y=z.getFullYear(),this.$M=z.getMonth(),this.$D=z.getDate(),this.$W=z.getDay(),this.$H=z.getHours(),this.$m=z.getMinutes(),this.$s=z.getSeconds(),this.$ms=z.getMilliseconds()},O.$utils=function(){return N},O.isValid=function(){return this.$d.toString()!==m},O.isSame=function(z,H){var K=T(z);return this.startOf(H)<=K&&K<=this.endOf(H)},O.isAfter=function(z,H){return T(z)68?1900:2e3)},c=function(y){return function(g){this[y]=+g}},d=[/[+-]\d\d:?(\d\d)?|Z/,function(y){(this.zone||(this.zone={})).offset=function(g){if(!g||g==="Z")return 0;var b=g.match(/([+-]|\d\d)/g),x=60*b[1]+(+b[2]||0);return x===0?0:b[0]==="+"?-x:x}(y)}],f=function(y){var g=s[y];return g&&(g.indexOf?g:g.s.concat(g.f))},h=function(y,g){var b,x=s.meridiem;if(x){for(var C=1;C<=24;C+=1)if(y.indexOf(x(C,0,g))>-1){b=C>12;break}}else b=y===(g?"pm":"PM");return b},v={A:[l,function(y){this.afternoon=h(y,!1)}],a:[l,function(y){this.afternoon=h(y,!0)}],Q:[o,function(y){this.month=3*(y-1)+1}],S:[o,function(y){this.milliseconds=100*+y}],SS:[i,function(y){this.milliseconds=10*+y}],SSS:[/\d{3}/,function(y){this.milliseconds=+y}],s:[a,c("seconds")],ss:[a,c("seconds")],m:[a,c("minutes")],mm:[a,c("minutes")],H:[a,c("hours")],h:[a,c("hours")],HH:[a,c("hours")],hh:[a,c("hours")],D:[a,c("day")],DD:[i,c("day")],Do:[l,function(y){var g=s.ordinal,b=y.match(/\d+/);if(this.day=b[0],g)for(var x=1;x<=31;x+=1)g(x).replace(/\[|\]/g,"")===y&&(this.day=x)}],w:[a,c("week")],ww:[i,c("week")],M:[a,c("month")],MM:[i,c("month")],MMM:[l,function(y){var g=f("months"),b=(f("monthsShort")||g.map(function(x){return x.slice(0,3)})).indexOf(y)+1;if(b<1)throw new Error;this.month=b%12||b}],MMMM:[l,function(y){var g=f("months").indexOf(y)+1;if(g<1)throw new Error;this.month=g%12||g}],Y:[/[+-]?\d+/,c("year")],YY:[i,function(y){this.year=u(y)}],YYYY:[/\d{4}/,c("year")],Z:d,ZZ:d};function m(y){var g,b;g=y,b=s&&s.formats;for(var x=(y=g.replace(/(\[[^\]]+])|(LTS?|l{1,4}|L{1,4})/g,function(T,N,L){var P=L&&L.toUpperCase();return N||b[L]||n[L]||b[P].replace(/(\[[^\]]+])|(MMMM|MM|DD|dddd)/g,function(j,O,z){return O||z.slice(1)})})).match(r),C=x.length,E=0;E-1)return new Date((K==="X"?1e3:1)*H);var oe=m(K)(H),V=oe.year,q=oe.month,ne=oe.day,fe=oe.hours,Q=oe.minutes,ie=oe.seconds,he=oe.milliseconds,ae=oe.zone,le=oe.week,J=new Date,ce=ne||(V||q?1:J.getDate()),ye=V||J.getFullYear(),Ne=0;V&&!q||(Ne=q>0?q-1:J.getMonth());var Be,Ge=fe||0,qe=Q||0,Te=ie||0,pe=he||0;return ae?new Date(Date.UTC(ye,Ne,ce,Ge,qe,Te,pe+60*ae.offset*1e3)):B?new Date(Date.UTC(ye,Ne,ce,Ge,qe,Te,pe)):(Be=new Date(ye,Ne,ce,Ge,qe,Te,pe),le&&(Be=Z(Be).week(le).toDate()),Be)}catch{return new Date("")}}(A,I,M,b),this.init(),P&&P!==!0&&(this.$L=this.locale(P).$L),L&&A!=this.format(I)&&(this.$d=new Date("")),s={}}else if(I instanceof Array)for(var j=I.length,O=1;O<=j;O+=1){D[1]=I[O-1];var z=b.apply(this,D);if(z.isValid()){this.$d=z.$d,this.$L=z.$L,this.init();break}O===j&&(this.$d=new Date(""))}else C.call(this,E)}}})})(sz);var Eve=sz.exports;const AA=ea(Eve);var uz={exports:{}};(function(e,t){(function(n,r){e.exports=r()})(pl,function(){return function(n,r){var o=r.prototype,i=o.format;o.format=function(a){var l=this,s=this.$locale();if(!this.isValid())return i.bind(this)(a);var u=this.$utils(),c=(a||"YYYY-MM-DDTHH:mm:ssZ").replace(/\[([^\]]+)]|Q|wo|ww|w|WW|W|zzz|z|gggg|GGGG|Do|X|x|k{1,2}|S/g,function(d){switch(d){case"Q":return Math.ceil((l.$M+1)/3);case"Do":return s.ordinal(l.$D);case"gggg":return l.weekYear();case"GGGG":return l.isoWeekYear();case"wo":return s.ordinal(l.week(),"W");case"w":case"ww":return u.s(l.week(),d==="w"?1:2,"0");case"W":case"WW":return u.s(l.isoWeek(),d==="W"?1:2,"0");case"k":case"kk":return u.s(String(l.$H===0?24:l.$H),d==="k"?1:2,"0");case"X":return Math.floor(l.$d.getTime()/1e3);case"x":return l.$d.getTime();case"z":return"["+l.offsetName()+"]";case"zzz":return"["+l.offsetName("long")+"]";default:return d}});return i.bind(this)(c)}}})})(uz);var Ave=uz.exports;const Sve=ea(Ave);var cz={exports:{}};(function(e,t){(function(n,r){e.exports=r()})(pl,function(){var n="week",r="year";return function(o,i,a){var l=i.prototype;l.week=function(s){if(s===void 0&&(s=null),s!==null)return this.add(7*(s-this.week()),"day");var u=this.$locale().yearStart||1;if(this.month()===11&&this.date()>25){var c=a(this).startOf(r).add(1,r).date(u),d=a(this).endOf(n);if(c.isBefore(d))return 1}var f=a(this).startOf(r).date(u).startOf(n).subtract(1,"millisecond"),h=this.diff(f,n,!0);return h<0?a(this).startOf("week").week():Math.ceil(h)},l.weeks=function(s){return s===void 0&&(s=null),this.week(s)}}})})(cz);var kve=cz.exports;const Mve=ea(kve);var dz={exports:{}};(function(e,t){(function(n,r){e.exports=r()})(pl,function(){return function(n,r){r.prototype.weekYear=function(){var o=this.month(),i=this.week(),a=this.year();return i===1&&o===11?a+1:o===0&&i>=52?a-1:a}}})})(dz);var Tve=dz.exports;const Ove=ea(Tve);var fz={exports:{}};(function(e,t){(function(n,r){e.exports=r()})(pl,function(){return function(n,r,o){r.prototype.dayOfYear=function(i){var a=Math.round((o(this).startOf("day")-o(this).startOf("year"))/864e5)+1;return i==null?a:this.add(i-a,"day")}}})})(fz);var Dve=fz.exports;const Nve=ea(Dve);var pz={exports:{}};(function(e,t){(function(n,r){e.exports=r()})(pl,function(){return function(n,r){r.prototype.isSameOrAfter=function(o,i){return this.isSame(o,i)||this.isAfter(o,i)}}})})(pz);var Fve=pz.exports;const Rve=ea(Fve);var hz={exports:{}};(function(e,t){(function(n,r){e.exports=r()})(pl,function(){return function(n,r){r.prototype.isSameOrBefore=function(o,i){return this.isSame(o,i)||this.isBefore(o,i)}}})})(hz);var Pve=hz.exports;const Ive=ea(Pve);function sf(){return sf=Object.assign?Object.assign.bind():function(e){for(var t=1;t"u"||!Reflect.construct||Reflect.construct.sham)return!1;if(typeof Proxy=="function")return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){})),!0}catch{return!1}}function ny(e,t,n){return Bve()?ny=Reflect.construct.bind():ny=function(o,i,a){var l=[null];l.push.apply(l,i);var s=Function.bind.apply(o,l),u=new s;return a&&zm(u,a.prototype),u},ny.apply(null,arguments)}function $ve(e){return Function.toString.call(e).indexOf("[native code]")!==-1}function A5(e){var t=typeof Map=="function"?new Map:void 0;return A5=function(r){if(r===null||!$ve(r))return r;if(typeof r!="function")throw new TypeError("Super expression must either be null or a function");if(typeof t<"u"){if(t.has(r))return t.get(r);t.set(r,o)}function o(){return ny(r,arguments,E5(this).constructor)}return o.prototype=Object.create(r.prototype,{constructor:{value:o,enumerable:!1,writable:!0,configurable:!0}}),zm(o,r)},A5(e)}var zve=/%[sdj%]/g,jve=function(){};function S5(e){if(!e||!e.length)return null;var t={};return e.forEach(function(n){var r=n.field;t[r]=t[r]||[],t[r].push(n)}),t}function za(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),r=1;r=i)return l;switch(l){case"%s":return String(n[o++]);case"%d":return Number(n[o++]);case"%j":try{return JSON.stringify(n[o++])}catch{return"[Circular]"}break;default:return l}});return a}return e}function Vve(e){return e==="string"||e==="url"||e==="hex"||e==="email"||e==="date"||e==="pattern"}function Lo(e,t){return!!(e==null||t==="array"&&Array.isArray(e)&&!e.length||Vve(t)&&typeof e=="string"&&!e)}function Hve(e,t,n){var r=[],o=0,i=e.length;function a(l){r.push.apply(r,l||[]),o++,o===i&&n(r)}e.forEach(function(l){t(l,a)})}function XT(e,t,n){var r=0,o=e.length;function i(a){if(a&&a.length){n(a);return}var l=r;r=r+1,l()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+\.)+[a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]{2,}))$/,hex:/^#?([a-f0-9]{6}|[a-f0-9]{3})$/i},R0={integer:function(t){return R0.number(t)&&parseInt(t,10)===t},float:function(t){return R0.number(t)&&!R0.integer(t)},array:function(t){return Array.isArray(t)},regexp:function(t){if(t instanceof RegExp)return!0;try{return!!new RegExp(t)}catch{return!1}},date:function(t){return typeof t.getTime=="function"&&typeof t.getMonth=="function"&&typeof t.getYear=="function"&&!isNaN(t.getTime())},number:function(t){return isNaN(t)?!1:typeof t=="number"},object:function(t){return typeof t=="object"&&!R0.array(t)},method:function(t){return typeof t=="function"},email:function(t){return typeof t=="string"&&t.length<=320&&!!t.match(eO.email)},url:function(t){return typeof t=="string"&&t.length<=2048&&!!t.match(qve())},hex:function(t){return typeof t=="string"&&!!t.match(eO.hex)}},Xve=function(t,n,r,o,i){if(t.required&&n===void 0){vz(t,n,r,o,i);return}var a=["integer","float","array","regexp","object","method","email","number","date","url","hex"],l=t.type;a.indexOf(l)>-1?R0[l](n)||o.push(za(i.messages.types[l],t.fullField,t.type)):l&&typeof n!==t.type&&o.push(za(i.messages.types[l],t.fullField,t.type))},Qve=function(t,n,r,o,i){var a=typeof t.len=="number",l=typeof t.min=="number",s=typeof t.max=="number",u=/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,c=n,d=null,f=typeof n=="number",h=typeof n=="string",v=Array.isArray(n);if(f?d="number":h?d="string":v&&(d="array"),!d)return!1;v&&(c=n.length),h&&(c=n.replace(u,"_").length),a?c!==t.len&&o.push(za(i.messages[d].len,t.fullField,t.len)):l&&!s&&ct.max?o.push(za(i.messages[d].max,t.fullField,t.max)):l&&s&&(ct.max)&&o.push(za(i.messages[d].range,t.fullField,t.min,t.max))},wp="enum",Jve=function(t,n,r,o,i){t[wp]=Array.isArray(t[wp])?t[wp]:[],t[wp].indexOf(n)===-1&&o.push(za(i.messages[wp],t.fullField,t[wp].join(", ")))},Zve=function(t,n,r,o,i){if(t.pattern){if(t.pattern instanceof RegExp)t.pattern.lastIndex=0,t.pattern.test(n)||o.push(za(i.messages.pattern.mismatch,t.fullField,n,t.pattern));else if(typeof t.pattern=="string"){var a=new RegExp(t.pattern);a.test(n)||o.push(za(i.messages.pattern.mismatch,t.fullField,n,t.pattern))}}},ur={required:vz,whitespace:Gve,type:Xve,range:Qve,enum:Jve,pattern:Zve},e0e=function(t,n,r,o,i){var a=[],l=t.required||!t.required&&o.hasOwnProperty(t.field);if(l){if(Lo(n,"string")&&!t.required)return r();ur.required(t,n,o,a,i,"string"),Lo(n,"string")||(ur.type(t,n,o,a,i),ur.range(t,n,o,a,i),ur.pattern(t,n,o,a,i),t.whitespace===!0&&ur.whitespace(t,n,o,a,i))}r(a)},t0e=function(t,n,r,o,i){var a=[],l=t.required||!t.required&&o.hasOwnProperty(t.field);if(l){if(Lo(n)&&!t.required)return r();ur.required(t,n,o,a,i),n!==void 0&&ur.type(t,n,o,a,i)}r(a)},n0e=function(t,n,r,o,i){var a=[],l=t.required||!t.required&&o.hasOwnProperty(t.field);if(l){if(n===""&&(n=void 0),Lo(n)&&!t.required)return r();ur.required(t,n,o,a,i),n!==void 0&&(ur.type(t,n,o,a,i),ur.range(t,n,o,a,i))}r(a)},r0e=function(t,n,r,o,i){var a=[],l=t.required||!t.required&&o.hasOwnProperty(t.field);if(l){if(Lo(n)&&!t.required)return r();ur.required(t,n,o,a,i),n!==void 0&&ur.type(t,n,o,a,i)}r(a)},o0e=function(t,n,r,o,i){var a=[],l=t.required||!t.required&&o.hasOwnProperty(t.field);if(l){if(Lo(n)&&!t.required)return r();ur.required(t,n,o,a,i),Lo(n)||ur.type(t,n,o,a,i)}r(a)},i0e=function(t,n,r,o,i){var a=[],l=t.required||!t.required&&o.hasOwnProperty(t.field);if(l){if(Lo(n)&&!t.required)return r();ur.required(t,n,o,a,i),n!==void 0&&(ur.type(t,n,o,a,i),ur.range(t,n,o,a,i))}r(a)},a0e=function(t,n,r,o,i){var a=[],l=t.required||!t.required&&o.hasOwnProperty(t.field);if(l){if(Lo(n)&&!t.required)return r();ur.required(t,n,o,a,i),n!==void 0&&(ur.type(t,n,o,a,i),ur.range(t,n,o,a,i))}r(a)},l0e=function(t,n,r,o,i){var a=[],l=t.required||!t.required&&o.hasOwnProperty(t.field);if(l){if(n==null&&!t.required)return r();ur.required(t,n,o,a,i,"array"),n!=null&&(ur.type(t,n,o,a,i),ur.range(t,n,o,a,i))}r(a)},s0e=function(t,n,r,o,i){var a=[],l=t.required||!t.required&&o.hasOwnProperty(t.field);if(l){if(Lo(n)&&!t.required)return r();ur.required(t,n,o,a,i),n!==void 0&&ur.type(t,n,o,a,i)}r(a)},u0e="enum",c0e=function(t,n,r,o,i){var a=[],l=t.required||!t.required&&o.hasOwnProperty(t.field);if(l){if(Lo(n)&&!t.required)return r();ur.required(t,n,o,a,i),n!==void 0&&ur[u0e](t,n,o,a,i)}r(a)},d0e=function(t,n,r,o,i){var a=[],l=t.required||!t.required&&o.hasOwnProperty(t.field);if(l){if(Lo(n,"string")&&!t.required)return r();ur.required(t,n,o,a,i),Lo(n,"string")||ur.pattern(t,n,o,a,i)}r(a)},f0e=function(t,n,r,o,i){var a=[],l=t.required||!t.required&&o.hasOwnProperty(t.field);if(l){if(Lo(n,"date")&&!t.required)return r();if(ur.required(t,n,o,a,i),!Lo(n,"date")){var s;n instanceof Date?s=n:s=new Date(n),ur.type(t,s,o,a,i),s&&ur.range(t,s.getTime(),o,a,i)}}r(a)},p0e=function(t,n,r,o,i){var a=[],l=Array.isArray(n)?"array":typeof n;ur.required(t,n,o,a,i,l),r(a)},g3=function(t,n,r,o,i){var a=t.type,l=[],s=t.required||!t.required&&o.hasOwnProperty(t.field);if(s){if(Lo(n,a)&&!t.required)return r();ur.required(t,n,o,l,i,a),Lo(n,a)||ur.type(t,n,o,l,i)}r(l)},h0e=function(t,n,r,o,i){var a=[],l=t.required||!t.required&&o.hasOwnProperty(t.field);if(l){if(Lo(n)&&!t.required)return r();ur.required(t,n,o,a,i)}r(a)},X0={string:e0e,method:t0e,number:n0e,boolean:r0e,regexp:o0e,integer:i0e,float:a0e,array:l0e,object:s0e,enum:c0e,pattern:d0e,date:f0e,url:g3,hex:g3,email:g3,required:p0e,any:h0e};function k5(){return{default:"Validation error on field %s",required:"%s is required",enum:"%s must be one of %s",whitespace:"%s cannot be empty",date:{format:"%s date %s is invalid for format %s",parse:"%s date could not be parsed, %s is invalid ",invalid:"%s date %s is invalid"},types:{string:"%s is not a %s",method:"%s is not a %s (function)",array:"%s is not an %s",object:"%s is not an %s",number:"%s is not a %s",date:"%s is not a %s",boolean:"%s is not a %s",integer:"%s is not an %s",float:"%s is not a %s",regexp:"%s is not a valid %s",email:"%s is not a valid %s",url:"%s is not a valid %s",hex:"%s is not a valid %s"},string:{len:"%s must be exactly %s characters",min:"%s must be at least %s characters",max:"%s cannot be longer than %s characters",range:"%s must be between %s and %s characters"},number:{len:"%s must equal %s",min:"%s cannot be less than %s",max:"%s cannot be greater than %s",range:"%s must be between %s and %s"},array:{len:"%s must be exactly %s in length",min:"%s cannot be less than %s in length",max:"%s cannot be greater than %s in length",range:"%s must be between %s and %s in length"},pattern:{mismatch:"%s value %s does not match pattern %s"},clone:function(){var t=JSON.parse(JSON.stringify(this));return t.clone=this.clone,t}}}var M5=k5(),$g=function(){function e(n){this.rules=null,this._messages=M5,this.define(n)}var t=e.prototype;return t.define=function(r){var o=this;if(!r)throw new Error("Cannot configure a schema with no rules");if(typeof r!="object"||Array.isArray(r))throw new Error("Rules must be an object");this.rules={},Object.keys(r).forEach(function(i){var a=r[i];o.rules[i]=Array.isArray(a)?a:[a]})},t.messages=function(r){return r&&(this._messages=ZT(k5(),r)),this._messages},t.validate=function(r,o,i){var a=this;o===void 0&&(o={}),i===void 0&&(i=function(){});var l=r,s=o,u=i;if(typeof s=="function"&&(u=s,s={}),!this.rules||Object.keys(this.rules).length===0)return u&&u(null,l),Promise.resolve(l);function c(m){var y=[],g={};function b(C){if(Array.isArray(C)){var E;y=(E=y).concat.apply(E,C)}else y.push(C)}for(var x=0;xXd},ie64:function(){return I5.ie()&&gz},firefox:function(){return ca()||T5},opera:function(){return ca()||O5},webkit:function(){return ca()||ry},safari:function(){return I5.webkit()},chrome:function(){return ca()||oy},windows:function(){return ca()||D5},osx:function(){return ca()||iy},linux:function(){return ca()||N5},iphone:function(){return ca()||R5},mobile:function(){return ca()||R5||P5||F5||_z},nativeApp:function(){return ca()||yz},android:function(){return ca()||F5},ipad:function(){return ca()||P5}},y0e=I5,_0e=!!(typeof window<"u"&&window.document&&window.document.createElement),b0e={canUseDOM:_0e},bz=b0e,wz;bz.canUseDOM&&(wz=document.implementation&&document.implementation.hasFeature&&document.implementation.hasFeature("","")!==!0);function w0e(e,t){if(!bz.canUseDOM||t&&!("addEventListener"in document))return!1;var n="on"+e,r=n in document;if(!r){var o=document.createElement("div");o.setAttribute(n,"return;"),r=typeof o[n]=="function"}return!r&&wz&&e==="wheel"&&(r=document.implementation.hasFeature("Events.wheel","3.0")),r}var x0e=w0e,rO=10,oO=40,iO=800;function xz(e){var t=0,n=0,r=0,o=0;return"detail"in e&&(n=e.detail),"wheelDelta"in e&&(n=-e.wheelDelta/120),"wheelDeltaY"in e&&(n=-e.wheelDeltaY/120),"wheelDeltaX"in e&&(t=-e.wheelDeltaX/120),"axis"in e&&e.axis===e.HORIZONTAL_AXIS&&(t=n,n=0),r=t*rO,o=n*rO,"deltaY"in e&&(o=e.deltaY),"deltaX"in e&&(r=e.deltaX),(r||o)&&e.deltaMode&&(e.deltaMode==1?(r*=oO,o*=oO):(r*=iO,o*=iO)),r&&!t&&(t=r<1?-1:1),o&&!n&&(n=o<1?-1:1),{spinX:t,spinY:n,pixelX:r,pixelY:o}}xz.getEventType=function(){return y0e.firefox()?"DOMMouseScroll":x0e("wheel")?"wheel":"mousewheel"};var C0e=xz;/** +* Checks if an event is supported in the current execution environment. +* +* NOTE: This will not work correctly for non-generic events such as `change`, +* `reset`, `load`, `error`, and `select`. +* +* Borrows from Modernizr. +* +* @param {string} eventNameSuffix Event name, e.g. "click". +* @param {?boolean} capture Check if the capture phase is supported. +* @return {boolean} True if the event is supported. +* @internal +* @license Modernizr 3.0.0pre (Custom Build) | MIT +*/const jm=Math.min,ph=Math.max,d_=Math.round,Is=e=>({x:e,y:e});function E0e(e,t,n){return ph(e,jm(t,n))}function Cz(e,t){return typeof e=="function"?e(t):e}function SA(e){return e.split("-")[0]}function kA(e){return e.split("-")[1]}function A0e(e){return e==="x"?"y":"x"}function Ez(e){return e==="y"?"height":"width"}const S0e=new Set(["top","bottom"]);function MA(e){return S0e.has(SA(e))?"y":"x"}function Az(e){return A0e(MA(e))}function k0e(e){return{top:0,right:0,bottom:0,left:0,...e}}function M0e(e){return typeof e!="number"?k0e(e):{top:e,right:e,bottom:e,left:e}}function Sz(e){const{x:t,y:n,width:r,height:o}=e;return{width:r,height:o,top:n,left:t,right:t+r,bottom:n+o,x:t,y:n}}function aO(e,t,n){let{reference:r,floating:o}=e;const i=MA(t),a=Az(t),l=Ez(a),s=SA(t),u=i==="y",c=r.x+r.width/2-o.width/2,d=r.y+r.height/2-o.height/2,f=r[l]/2-o[l]/2;let h;switch(s){case"top":h={x:c,y:r.y-o.height};break;case"bottom":h={x:c,y:r.y+r.height};break;case"right":h={x:r.x+r.width,y:d};break;case"left":h={x:r.x-o.width,y:d};break;default:h={x:r.x,y:r.y}}switch(kA(t)){case"start":h[a]-=f*(n&&u?-1:1);break;case"end":h[a]+=f*(n&&u?-1:1);break}return h}const T0e=async(e,t,n)=>{const{placement:r="bottom",strategy:o="absolute",middleware:i=[],platform:a}=n,l=i.filter(Boolean),s=await(a.isRTL==null?void 0:a.isRTL(t));let u=await a.getElementRects({reference:e,floating:t,strategy:o}),{x:c,y:d}=aO(u,r,s),f=r,h={},v=0;for(let m=0;m({name:"arrow",options:e,async fn(t){const{x:n,y:r,placement:o,rects:i,platform:a,elements:l,middlewareData:s}=t,{element:u,padding:c=0}=Cz(e,t)||{};if(u==null)return{};const d=M0e(c),f={x:n,y:r},h=Az(o),v=Ez(h),m=await a.getDimensions(u),y=h==="y",g=y?"top":"left",b=y?"bottom":"right",x=y?"clientHeight":"clientWidth",C=i.reference[v]+i.reference[h]-f[h]-i.floating[v],E=f[h]-i.reference[h],A=await(a.getOffsetParent==null?void 0:a.getOffsetParent(u));let M=A?A[x]:0;(!M||!await(a.isElement==null?void 0:a.isElement(A)))&&(M=l.floating[x]||i.floating[v]);const D=C/2-E/2,I=M/2-m[v]/2-1,T=jm(d[g],I),N=jm(d[b],I),L=T,P=M-m[v]-N,j=M/2-m[v]/2+D,O=E0e(L,j,P),z=!s.arrow&&kA(o)!=null&&j!==O&&i.reference[v]/2-(j"u"?!1:e instanceof ShadowRoot||e instanceof Ha(e).ShadowRoot}const R0e=new Set(["inline","contents"]);function zg(e){const{overflow:t,overflowX:n,overflowY:r,display:o}=cs(e);return/auto|scroll|overlay|hidden|clip/.test(t+r+n)&&!R0e.has(o)}const P0e=new Set(["table","td","th"]);function I0e(e){return P0e.has(xv(e))}const L0e=[":popover-open",":modal"];function v4(e){return L0e.some(t=>{try{return e.matches(t)}catch{return!1}})}const B0e=["transform","translate","scale","rotate","perspective"],$0e=["transform","translate","scale","rotate","perspective","filter"],z0e=["paint","layout","strict","content"];function TA(e){const t=OA(),n=us(e)?cs(e):e;return B0e.some(r=>n[r]?n[r]!=="none":!1)||(n.containerType?n.containerType!=="normal":!1)||!t&&(n.backdropFilter?n.backdropFilter!=="none":!1)||!t&&(n.filter?n.filter!=="none":!1)||$0e.some(r=>(n.willChange||"").includes(r))||z0e.some(r=>(n.contain||"").includes(r))}function j0e(e){let t=ed(e);for(;Vs(t)&&!Ph(t);){if(TA(t))return t;if(v4(t))return null;t=ed(t)}return null}function OA(){return typeof CSS>"u"||!CSS.supports?!1:CSS.supports("-webkit-backdrop-filter","none")}const V0e=new Set(["html","body","#document"]);function Ph(e){return V0e.has(xv(e))}function cs(e){return Ha(e).getComputedStyle(e)}function m4(e){return us(e)?{scrollLeft:e.scrollLeft,scrollTop:e.scrollTop}:{scrollLeft:e.scrollX,scrollTop:e.scrollY}}function ed(e){if(xv(e)==="html")return e;const t=e.assignedSlot||e.parentNode||lO(e)&&e.host||Qu(e);return lO(t)?t.host:t}function Mz(e){const t=ed(e);return Ph(t)?e.ownerDocument?e.ownerDocument.body:e.body:Vs(t)&&zg(t)?t:Mz(t)}function Tz(e,t,n){var r;t===void 0&&(t=[]);const o=Mz(e),i=o===((r=e.ownerDocument)==null?void 0:r.body),a=Ha(o);return i?(L5(a),t.concat(a,a.visualViewport||[],zg(o)?o:[],[])):t.concat(o,Tz(o,[]))}function L5(e){return e.parent&&Object.getPrototypeOf(e.parent)?e.frameElement:null}function Oz(e){const t=cs(e);let n=parseFloat(t.width)||0,r=parseFloat(t.height)||0;const o=Vs(e),i=o?e.offsetWidth:n,a=o?e.offsetHeight:r,l=d_(n)!==i||d_(r)!==a;return l&&(n=i,r=a),{width:n,height:r,$:l}}function Dz(e){return us(e)?e:e.contextElement}function hh(e){const t=Dz(e);if(!Vs(t))return Is(1);const n=t.getBoundingClientRect(),{width:r,height:o,$:i}=Oz(t);let a=(i?d_(n.width):n.width)/r,l=(i?d_(n.height):n.height)/o;return(!a||!Number.isFinite(a))&&(a=1),(!l||!Number.isFinite(l))&&(l=1),{x:a,y:l}}const H0e=Is(0);function Nz(e){const t=Ha(e);return!OA()||!t.visualViewport?H0e:{x:t.visualViewport.offsetLeft,y:t.visualViewport.offsetTop}}function W0e(e,t,n){return t===void 0&&(t=!1),!n||t&&n!==Ha(e)?!1:t}function Vm(e,t,n,r){t===void 0&&(t=!1),n===void 0&&(n=!1);const o=e.getBoundingClientRect(),i=Dz(e);let a=Is(1);t&&(r?us(r)&&(a=hh(r)):a=hh(e));const l=W0e(i,n,r)?Nz(i):Is(0);let s=(o.left+l.x)/a.x,u=(o.top+l.y)/a.y,c=o.width/a.x,d=o.height/a.y;if(i){const f=Ha(i),h=r&&us(r)?Ha(r):r;let v=f,m=L5(v);for(;m&&r&&h!==v;){const y=hh(m),g=m.getBoundingClientRect(),b=cs(m),x=g.left+(m.clientLeft+parseFloat(b.paddingLeft))*y.x,C=g.top+(m.clientTop+parseFloat(b.paddingTop))*y.y;s*=y.x,u*=y.y,c*=y.x,d*=y.y,s+=x,u+=C,v=Ha(m),m=L5(v)}}return Sz({width:c,height:d,x:s,y:u})}function g4(e,t){const n=m4(e).scrollLeft;return t?t.left+n:Vm(Qu(e)).left+n}function Fz(e,t){const n=e.getBoundingClientRect(),r=n.left+t.scrollLeft-g4(e,n),o=n.top+t.scrollTop;return{x:r,y:o}}function U0e(e){let{elements:t,rect:n,offsetParent:r,strategy:o}=e;const i=o==="fixed",a=Qu(r),l=t?v4(t.floating):!1;if(r===a||l&&i)return n;let s={scrollLeft:0,scrollTop:0},u=Is(1);const c=Is(0),d=Vs(r);if((d||!d&&!i)&&((xv(r)!=="body"||zg(a))&&(s=m4(r)),Vs(r))){const h=Vm(r);u=hh(r),c.x=h.x+r.clientLeft,c.y=h.y+r.clientTop}const f=a&&!d&&!i?Fz(a,s):Is(0);return{width:n.width*u.x,height:n.height*u.y,x:n.x*u.x-s.scrollLeft*u.x+c.x+f.x,y:n.y*u.y-s.scrollTop*u.y+c.y+f.y}}function K0e(e){return Array.from(e.getClientRects())}function Y0e(e){const t=Qu(e),n=m4(e),r=e.ownerDocument.body,o=ph(t.scrollWidth,t.clientWidth,r.scrollWidth,r.clientWidth),i=ph(t.scrollHeight,t.clientHeight,r.scrollHeight,r.clientHeight);let a=-n.scrollLeft+g4(e);const l=-n.scrollTop;return cs(r).direction==="rtl"&&(a+=ph(t.clientWidth,r.clientWidth)-o),{width:o,height:i,x:a,y:l}}const sO=25;function G0e(e,t){const n=Ha(e),r=Qu(e),o=n.visualViewport;let i=r.clientWidth,a=r.clientHeight,l=0,s=0;if(o){i=o.width,a=o.height;const c=OA();(!c||c&&t==="fixed")&&(l=o.offsetLeft,s=o.offsetTop)}const u=g4(r);if(u<=0){const c=r.ownerDocument,d=c.body,f=getComputedStyle(d),h=c.compatMode==="CSS1Compat"&&parseFloat(f.marginLeft)+parseFloat(f.marginRight)||0,v=Math.abs(r.clientWidth-d.clientWidth-h);v<=sO&&(i-=v)}else u<=sO&&(i+=u);return{width:i,height:a,x:l,y:s}}const q0e=new Set(["absolute","fixed"]);function X0e(e,t){const n=Vm(e,!0,t==="fixed"),r=n.top+e.clientTop,o=n.left+e.clientLeft,i=Vs(e)?hh(e):Is(1),a=e.clientWidth*i.x,l=e.clientHeight*i.y,s=o*i.x,u=r*i.y;return{width:a,height:l,x:s,y:u}}function uO(e,t,n){let r;if(t==="viewport")r=G0e(e,n);else if(t==="document")r=Y0e(Qu(e));else if(us(t))r=X0e(t,n);else{const o=Nz(e);r={x:t.x-o.x,y:t.y-o.y,width:t.width,height:t.height}}return Sz(r)}function Rz(e,t){const n=ed(e);return n===t||!us(n)||Ph(n)?!1:cs(n).position==="fixed"||Rz(n,t)}function Q0e(e,t){const n=t.get(e);if(n)return n;let r=Tz(e,[]).filter(l=>us(l)&&xv(l)!=="body"),o=null;const i=cs(e).position==="fixed";let a=i?ed(e):e;for(;us(a)&&!Ph(a);){const l=cs(a),s=TA(a);!s&&l.position==="fixed"&&(o=null),(i?!s&&!o:!s&&l.position==="static"&&!!o&&q0e.has(o.position)||zg(a)&&!s&&Rz(e,a))?r=r.filter(c=>c!==a):o=l,a=ed(a)}return t.set(e,r),r}function J0e(e){let{element:t,boundary:n,rootBoundary:r,strategy:o}=e;const a=[...n==="clippingAncestors"?v4(t)?[]:Q0e(t,this._c):[].concat(n),r],l=a[0],s=a.reduce((u,c)=>{const d=uO(t,c,o);return u.top=ph(d.top,u.top),u.right=jm(d.right,u.right),u.bottom=jm(d.bottom,u.bottom),u.left=ph(d.left,u.left),u},uO(t,l,o));return{width:s.right-s.left,height:s.bottom-s.top,x:s.left,y:s.top}}function Z0e(e){const{width:t,height:n}=Oz(e);return{width:t,height:n}}function eme(e,t,n){const r=Vs(t),o=Qu(t),i=n==="fixed",a=Vm(e,!0,i,t);let l={scrollLeft:0,scrollTop:0};const s=Is(0);function u(){s.x=g4(o)}if(r||!r&&!i)if((xv(t)!=="body"||zg(o))&&(l=m4(t)),r){const h=Vm(t,!0,i,t);s.x=h.x+t.clientLeft,s.y=h.y+t.clientTop}else o&&u();i&&!r&&o&&u();const c=o&&!r&&!i?Fz(o,l):Is(0),d=a.left+l.scrollLeft-s.x-c.x,f=a.top+l.scrollTop-s.y-c.y;return{x:d,y:f,width:a.width,height:a.height}}function y3(e){return cs(e).position==="static"}function cO(e,t){if(!Vs(e)||cs(e).position==="fixed")return null;if(t)return t(e);let n=e.offsetParent;return Qu(e)===n&&(n=n.ownerDocument.body),n}function Pz(e,t){const n=Ha(e);if(v4(e))return n;if(!Vs(e)){let o=ed(e);for(;o&&!Ph(o);){if(us(o)&&!y3(o))return o;o=ed(o)}return n}let r=cO(e,t);for(;r&&I0e(r)&&y3(r);)r=cO(r,t);return r&&Ph(r)&&y3(r)&&!TA(r)?n:r||j0e(e)||n}const tme=async function(e){const t=this.getOffsetParent||Pz,n=this.getDimensions,r=await n(e.floating);return{reference:eme(e.reference,await t(e.floating),e.strategy),floating:{x:0,y:0,width:r.width,height:r.height}}};function nme(e){return cs(e).direction==="rtl"}const rme={convertOffsetParentRelativeRectToViewportRelativeRect:U0e,getDocumentElement:Qu,getClippingRect:J0e,getOffsetParent:Pz,getElementRects:tme,getClientRects:K0e,getDimensions:Z0e,getScale:hh,isElement:us,isRTL:nme},ome=F0e,ime=O0e,ame=(e,t,n)=>{const r=new Map,o={platform:rme,...n},i={...o.platform,_c:r};return T0e(e,t,{...o,platform:i})},Iz=["primary","success","info","warning","error"],Lz=["top","top-left","top-right","bottom","bottom-left","bottom-right"],Hm="top",yi=the({customClass:"",dangerouslyUseHTMLString:!1,duration:3e3,icon:void 0,id:"",message:"",onClose:void 0,showClose:!1,type:"info",plain:!1,offset:16,placement:void 0,zIndex:0,grouping:!1,repeatNum:1,appendTo:On?document.body:void 0}),lme=f4({customClass:{type:String,default:yi.customClass},dangerouslyUseHTMLString:{type:Boolean,default:yi.dangerouslyUseHTMLString},duration:{type:Number,default:yi.duration},icon:{type:Jpe,default:yi.icon},id:{type:String,default:yi.id},message:{type:Df([String,Object,Function]),default:yi.message},onClose:{type:Df(Function),default:yi.onClose},showClose:{type:Boolean,default:yi.showClose},type:{type:String,values:Iz,default:yi.type},plain:{type:Boolean,default:yi.plain},offset:{type:Number,default:yi.offset},placement:{type:String,values:Lz,default:yi.placement},zIndex:{type:Number,default:yi.zIndex},grouping:{type:Boolean,default:yi.grouping},repeatNum:{type:Number,default:yi.repeatNum}}),sme={destroy:()=>!0},es=Nb({}),ume=e=>(es[e]||(es[e]=Nb([])),es[e]),cme=(e,t)=>{const n=es[t]||[],r=n.findIndex(a=>a.id===e),o=n[r];let i;return r>0&&(i=n[r-1]),{current:o,prev:i}},dme=(e,t)=>{const{prev:n}=cme(e,t);return n?n.vm.exposed.bottom.value:0},fme=(e,t,n)=>(es[n]||[]).findIndex(i=>i.id===e)>0?16:t,pme=ee({name:"ElMessage"}),hme=ee({...pme,props:lme,emits:sme,setup(e,{expose:t,emit:n}){const r=e,{Close:o}=Zpe,i=se(!1),{ns:a,zIndex:l}=Ppe("message"),{currentZIndex:s,nextZIndex:u}=l,c=se(),d=se(!1),f=se(0);let h;const v=G(()=>r.type?r.type==="error"?"danger":r.type:"info"),m=G(()=>{const L=r.type;return{[a.bm("icon",L)]:L&&IT[L]}}),y=G(()=>r.icon||IT[r.type]||""),g=G(()=>r.placement||Hm),b=G(()=>dme(r.id,g.value)),x=G(()=>fme(r.id,r.offset,g.value)+b.value),C=G(()=>f.value+x.value),E=G(()=>g.value.includes("left")?a.is("left"):g.value.includes("right")?a.is("right"):a.is("center")),A=G(()=>g.value.startsWith("top")?"top":"bottom"),M=G(()=>({[A.value]:`${x.value}px`,zIndex:s.value}));function D(){r.duration!==0&&({stop:h}=Zc(()=>{T()},r.duration))}function I(){h==null||h()}function T(){d.value=!1,vt(()=>{var L;i.value||((L=r.onClose)==null||L.call(r),n("destroy"))})}function N(L){nhe(L)===w5.esc&&T()}return en(()=>{D(),u(),d.value=!0}),Ye(()=>r.repeatNum,()=>{I(),D()}),Dr(document,"keydown",N),ui(c,()=>{f.value=c.value.getBoundingClientRect().height}),t({visible:d,bottom:C,close:T}),(L,P)=>(R(),be(Gr,{name:w(a).b("fade"),onBeforeEnter:j=>i.value=!0,onBeforeLeave:L.onClose,onAfterLeave:j=>L.$emit("destroy"),persisted:""},{default:X(()=>[It($("div",{id:L.id,ref_key:"messageRef",ref:c,class:re([w(a).b(),{[w(a).m(L.type)]:L.type},w(a).is("closable",L.showClose),w(a).is("plain",L.plain),w(a).is("bottom",w(A)==="bottom"),w(E),L.customClass]),style:bt(w(M)),role:"alert",onMouseenter:I,onMouseleave:D},[L.repeatNum>1?(R(),be(w(uve),{key:0,value:L.repeatNum,type:w(v),class:re(w(a).e("badge"))},null,8,["value","type","class"])):xe("v-if",!0),w(y)?(R(),be(w(RT),{key:1,class:re([w(a).e("icon"),w(m)])},{default:X(()=>[(R(),be(cn(w(y))))]),_:1},8,["class"])):xe("v-if",!0),Qe(L.$slots,"default",{},()=>[L.dangerouslyUseHTMLString?(R(),W(at,{key:1},[xe(" Caution here, message could've been compromised, never use user's input as message "),$("p",{class:re(w(a).e("content")),innerHTML:L.message},null,10,["innerHTML"])],2112)):(R(),W("p",{key:0,class:re(w(a).e("content"))},Pe(L.message),3))]),L.showClose?(R(),be(w(RT),{key:2,class:re(w(a).e("closeBtn")),onClick:St(T,["stop"])},{default:X(()=>[Y(w(o))]),_:1},8,["class","onClick"])):xe("v-if",!0)],46,["id"]),[[rn,d.value]])]),_:3},8,["name","onBeforeEnter","onBeforeLeave","onAfterLeave"]))}});var vme=mA(hme,[["__file","message.vue"]]);let mme=1;const gme=e=>{if(!e.appendTo)e.appendTo=document.body;else if(Yt(e.appendTo)){let n=document.querySelector(e.appendTo);Hfe(n)||(n=document.body),e.appendTo=n}},yme=e=>{!e.placement&&Yt(Vi.placement)&&Vi.placement&&(e.placement=Vi.placement),e.placement||(e.placement=Hm),Lz.includes(e.placement)||(e.placement=Hm)},Bz=e=>{const t=!e||Yt(e)||Er(e)||jt(e)?{message:e}:e,n={...yi,...t};return gme(n),yme(n),p3(Vi.grouping)&&!n.grouping&&(n.grouping=Vi.grouping),Of(Vi.duration)&&n.duration===3e3&&(n.duration=Vi.duration),Of(Vi.offset)&&n.offset===16&&(n.offset=Vi.offset),p3(Vi.showClose)&&!n.showClose&&(n.showClose=Vi.showClose),p3(Vi.plain)&&!n.plain&&(n.plain=Vi.plain),n},_me=e=>{const t=e.props.placement||Hm,n=es[t],r=n.indexOf(e);if(r===-1)return;n.splice(r,1);const{handler:o}=e;o.close()},bme=({appendTo:e,...t},n)=>{const r=`message_${mme++}`,o=t.onClose,i=document.createElement("div"),a={...t,id:r,onClose:()=>{o==null||o(),_me(c)},onDestroy:()=>{Pu(null,i)}},l=Y(vme,a,jt(a.message)||Er(a.message)?{default:jt(a.message)?a.message:()=>a.message}:null);l.appContext=n||Nf._context,Pu(l,i),e.appendChild(i.firstElementChild);const s=l.component,c={id:r,vnode:l,vm:s,handler:{close:()=>{s.exposed.close()}},props:l.component.props};return c},Nf=(e={},t)=>{if(!On)return{close:()=>{}};const n=Bz(e),r=ume(n.placement||Hm);if(n.grouping&&r.length){const i=r.find(({vnode:a})=>{var l;return((l=a.props)==null?void 0:l.message)===n.message});if(i)return i.props.repeatNum+=1,i.props.type=n.type,i.handler}if(Of(Vi.max)&&r.length>=Vi.max)return{close:()=>{}};const o=bme(n,t);return r.push(o),o.handler};Iz.forEach(e=>{Nf[e]=(t={},n)=>{const r=Bz(t);return Nf({...r,type:e},n)}});function wme(e){for(const t in es)if(kn(es,t)){const n=[...es[t]];for(const r of n)(!e||e===r.props.type)&&r.handler.close()}}function xme(e){if(!es[e])return;[...es[e]].forEach(n=>n.handler.close())}Nf.closeAll=wme;Nf.closeAllByPlacement=xme;Nf._context=null;const Cme=Bpe(Nf,"$message");/*! + * @form-create/element-ui v3.2.31 + * (c) 2018-2025 xaboy + * Github https://github.com/xaboy/form-create + * Released under the MIT License. + */function dO(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(o){return Object.getOwnPropertyDescriptor(e,o).enumerable})),n.push.apply(n,r)}return n}function nn(e){for(var t=1;t"u"||!Reflect.construct||Reflect.construct.sham)return!1;if(typeof Proxy=="function")return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){})),!0}catch{return!1}}function kme(e){if(e===void 0)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function Mme(e,t){if(t&&(typeof t=="object"||typeof t=="function"))return t;if(t!==void 0)throw new TypeError("Derived constructors may only return object or undefined");return kme(e)}function Tme(e){var t=Sme();return function(){var r=f_(e),o;if(t){var i=f_(this).constructor;o=Reflect.construct(r,arguments,i)}else o=r.apply(this,arguments);return Mme(this,o)}}function Ome(e,t){return Nme(e)||Rme(e,t)||$z(e,t)||Ime()}function nr(e){return Dme(e)||Fme(e)||$z(e)||Pme()}function Dme(e){if(Array.isArray(e))return $5(e)}function Nme(e){if(Array.isArray(e))return e}function Fme(e){if(typeof Symbol<"u"&&e[Symbol.iterator]!=null||e["@@iterator"]!=null)return Array.from(e)}function Rme(e,t){var n=e==null?null:typeof Symbol<"u"&&e[Symbol.iterator]||e["@@iterator"];if(n!=null){var r=[],o=!0,i=!1,a,l;try{for(n=n.call(e);!(o=(a=n.next()).done)&&(r.push(a.value),!(t&&r.length===t));o=!0);}catch(s){i=!0,l=s}finally{try{!o&&n.return!=null&&n.return()}finally{if(i)throw l}}return r}}function $z(e,t){if(e){if(typeof e=="string")return $5(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);if(n==="Object"&&e.constructor&&(n=e.constructor.name),n==="Map"||n==="Set")return Array.from(e);if(n==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return $5(e,t)}}function $5(e,t){(t==null||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n-1?[]:[e]}var Lme="fcCheckbox",Bme=ee({name:Lme,inheritAttrs:!1,props:{formCreateInject:Object,modelValue:{type:Array,default:function(){return[]}},type:String,options:Array,input:Boolean,inputValue:String},emits:["update:modelValue","fc.el"],setup:function(t,n){var r=In(t.formCreateInject,"options",[]),o=In(t,"options"),i=In(t,"modelValue"),a=In(t,"inputValue",""),l=se(a.value),s=In(t,"input",!1),u=function(h){var v=nr(va(i.value)),m=v.indexOf(l.value);l.value=h,m>-1&&(v.splice(m,1),v.push(h),d(v))};Ye(a,function(f){if(!s.value){l.value=f;return}u(f)});var c=G(function(){var f=r.value||[];return o.value&&(f=o.value||[]),Array.isArray(f)?f:[]});Ye(i,function(f){var h=null;if(!a.value&&f!=null&&Array.isArray(f)&&f.length>0&&s.value){var v=c.value.map(function(m){return m.value});f.forEach(function(m){v.indexOf(m)===-1&&(h=m)})}h!=null&&(l.value=h)},{immediate:!0});var d=function(h){n.emit("update:modelValue",h)};return{options:c,value:i,onInput:d,updateCustomValue:u,makeInput:function(h){if(s.value)return Y(h,{value:l.value||void 0,label:l.value||void 0},{default:function(){return[Y(we("ElInput"),{size:"small",modelValue:l.value,"onUpdate:modelValue":u},null)]}})}}},render:function(){var t,n,r=this,o=this.type==="button"?"ElCheckboxButton":"ElCheckbox",i=we(o);return Y(we("ElCheckboxGroup"),Qt(this.$attrs,{modelValue:this.value,"onUpdate:modelValue":this.onInput,ref:"el"}),nn({default:function(){return[r.options.map(function(l,s){var u=nn({},l),c=u.value,d=u.label;return delete u.value,delete u.label,Y(i,Qt(u,{label:c,value:c,key:o+s+"-"+c}),{default:function(){return[d||c||""]}})}),(t=(n=r.$slots).default)===null||t===void 0?void 0:t.call(n),r.makeInput(i)]}},y4(this.$slots,["default"])))},mounted:function(){this.$emit("fc.el",this.$refs.el)}});function zz(e){e=e||new Map;var t={$on:function(r,o){var i=e.get(r),a=i&&i.push(o);a||e.set(r,[o])},$once:function(r,o){o._once=!0,t.$on(r,o)},$off:function(r,o){var i=e.get(r);i&&i.splice(i.indexOf(o)>>>0,1)},$emit:function(r){for(var o=arguments.length,i=new Array(o>1?o-1:0),a=1;a"u")){var r=document.head||document.getElementsByTagName("head")[0],o=document.createElement("style");o.type="text/css",n==="top"&&r.firstChild?r.insertBefore(o,r.firstChild):r.appendChild(o),o.styleSheet?o.styleSheet.cssText=e:o.appendChild(document.createTextNode(e))}}var $me="._fc-frame ._fc-files img{display:inline-block;height:100%;vertical-align:top;width:100%}._fc-frame ._fc-upload-btn{border:1px dashed #c0ccda;cursor:pointer}._fc-frame._fc-disabled ._fc-upload-btn,._fc-frame._fc-disabled .el-button{color:#999;cursor:not-allowed!important}._fc-frame ._fc-upload-cover{background:rgba(0,0,0,.6);bottom:0;left:0;opacity:0;position:absolute;right:0;top:0;-webkit-transition:opacity .3s;-o-transition:opacity .3s;transition:opacity .3s}._fc-frame ._fc-upload-cover i{color:#fff;cursor:pointer;font-size:20px;margin:0 2px}._fc-frame ._fc-files:hover ._fc-upload-cover{opacity:1}._fc-frame .el-upload{display:block}._fc-frame ._fc-upload-icon{cursor:pointer}._fc-files,._fc-frame ._fc-upload-btn{background:#fff;border:1px solid #c0ccda;border-radius:4px;-webkit-box-shadow:2px 2px 5px rgba(0,0,0,.1);box-shadow:2px 2px 5px rgba(0,0,0,.1);-webkit-box-sizing:border-box;box-sizing:border-box;display:inline-block;height:58px;line-height:58px;margin-right:4px;overflow:hidden;position:relative;text-align:center;width:58px}";_4($me);var jz={name:"IconCircleClose"},zme={class:"icon",viewBox:"0 0 1024 1024",xmlns:"http://www.w3.org/2000/svg"},jme=$("path",{fill:"currentColor",d:"M466.752 512l-90.496-90.496a32 32 0 0145.248-45.248L512 466.752l90.496-90.496a32 32 0 1145.248 45.248L557.248 512l90.496 90.496a32 32 0 11-45.248 45.248L512 557.248l-90.496 90.496a32 32 0 01-45.248-45.248L466.752 512z"},null,-1),Vme=$("path",{fill:"currentColor",d:"M512 896a384 384 0 100-768 384 384 0 000 768zm0 64a448 448 0 110-896 448 448 0 010 896z"},null,-1),Hme=[jme,Vme];function Wme(e,t,n,r,o,i){return R(),W("svg",zme,Hme)}jz.render=Wme;var Vz={name:"IconDocument"},Ume={class:"icon",viewBox:"0 0 1024 1024",xmlns:"http://www.w3.org/2000/svg"},Kme=$("path",{fill:"currentColor",d:"M832 384H576V128H192v768h640V384zm-26.496-64L640 154.496V320h165.504zM160 64h480l256 256v608a32 32 0 01-32 32H160a32 32 0 01-32-32V96a32 32 0 0132-32zm160 448h384v64H320v-64zm0-192h160v64H320v-64zm0 384h384v64H320v-64z"},null,-1),Yme=[Kme];function Gme(e,t,n,r,o,i){return R(),W("svg",Ume,Yme)}Vz.render=Gme;var Hz={name:"IconDelete"},qme={class:"icon",viewBox:"0 0 1024 1024",xmlns:"http://www.w3.org/2000/svg"},Xme=$("path",{fill:"currentColor",d:"M160 256H96a32 32 0 010-64h256V95.936a32 32 0 0132-32h256a32 32 0 0132 32V192h256a32 32 0 110 64h-64v672a32 32 0 01-32 32H192a32 32 0 01-32-32V256zm448-64v-64H416v64h192zM224 896h576V256H224v640zm192-128a32 32 0 01-32-32V416a32 32 0 0164 0v320a32 32 0 01-32 32zm192 0a32 32 0 01-32-32V416a32 32 0 0164 0v320a32 32 0 01-32 32z"},null,-1),Qme=[Xme];function Jme(e,t,n,r,o,i){return R(),W("svg",qme,Qme)}Hz.render=Jme;var Wz={name:"IconView"},Zme={class:"icon",viewBox:"0 0 1024 1024",xmlns:"http://www.w3.org/2000/svg"},ege=$("path",{fill:"currentColor",d:"M512 160c320 0 512 352 512 352S832 864 512 864 0 512 0 512s192-352 512-352zm0 64c-225.28 0-384.128 208.064-436.8 288 52.608 79.872 211.456 288 436.8 288 225.28 0 384.128-208.064 436.8-288-52.608-79.872-211.456-288-436.8-288zm0 64a224 224 0 110 448 224 224 0 010-448zm0 64a160.192 160.192 0 00-160 160c0 88.192 71.744 160 160 160s160-71.808 160-160-71.744-160-160-160z"},null,-1),tge=[ege];function nge(e,t,n,r,o,i){return R(),W("svg",Zme,tge)}Wz.render=nge;var Uz={name:"IconFolderOpened"},rge={class:"icon",viewBox:"0 0 1024 1024",xmlns:"http://www.w3.org/2000/svg"},oge=$("path",{fill:"currentColor",d:"M878.08 448H241.92l-96 384h636.16l96-384zM832 384v-64H485.76L357.504 192H128v448l57.92-231.744A32 32 0 01216.96 384H832zm-24.96 512H96a32 32 0 01-32-32V160a32 32 0 0132-32h287.872l128.384 128H864a32 32 0 0132 32v96h23.04a32 32 0 0131.04 39.744l-112 448A32 32 0 01807.04 896z"},null,-1),ige=[oge];function age(e,t,n,r,o,i){return R(),W("svg",rge,ige)}Uz.render=age;var lge="fcFrame",sge=ee({name:lge,props:{type:{type:String,default:"input"},field:String,helper:{type:Boolean,default:!0},disabled:{type:Boolean,default:!1},src:{type:String,required:!0},icon:{type:String,default:"IconFolderOpened"},width:{type:String,default:"500px"},height:{type:String,default:"370px"},maxLength:{type:Number,default:0},okBtnText:{type:String,default:""},closeBtnText:{type:String,default:""},modalTitle:String,handleIcon:{type:[String,Boolean],default:void 0},title:String,allowRemove:{type:Boolean,default:!0},onOpen:{type:Function,default:function(){}},onOk:{type:Function,default:function(){}},onCancel:{type:Function,default:function(){}},onLoad:{type:Function,default:function(){}},onBeforeRemove:{type:Function,default:function(){}},onRemove:{type:Function,default:function(){}},onHandle:Function,modal:{type:Object,default:function(){return{}}},srcKey:[String,Number],modelValue:[Array,String,Number,Object],previewMask:void 0,footer:{type:Boolean,default:!0},reload:{type:Boolean,default:!0},closeBtn:{type:Boolean,default:!0},okBtn:{type:Boolean,default:!0},formCreateInject:Object},emits:["update:modelValue","change"],components:{IconFolderOpened:Uz,IconView:Wz},data:function(){return{fileList:va(this.modelValue),previewVisible:!1,frameVisible:!1,previewImage:"",bus:new zz}},watch:{modelValue:function(t){this.fileList=va(t)}},methods:{close:function(){this.closeModel(!0)},closeModel:function(t){this.bus.$emit(t?"$close":"$ok"),this.reload&&(this.bus.$off("$ok"),this.bus.$off("$close")),this.frameVisible=!1},handleCancel:function(){this.previewVisible=!1},showModel:function(){this.disabled||this.onOpen()===!1||(this.frameVisible=!0)},input:function(){var t=this.fileList,n=this.maxLength===1?t[0]||"":t;this.$emit("update:modelValue",n),this.$emit("change",n)},makeInput:function(){var t=this;return Y(we("ElInput"),Qt({type:"text",modelValue:this.fileList.map(function(n){return t.getSrc(n)}).toString(),readonly:!0},{key:1}),{append:function(){return Y(we("ElButton"),{icon:we(t.icon),onClick:function(){return t.showModel()}},null)},suffix:function(){return t.fileList.length&&!t.disabled?Y(we("ElIcon"),{class:"el-input__icon _fc-upload-icon",onClick:function(){t.fileList=[],t.input()}},{default:function(){return[Y(jz,null,null)]}}):null}})},makeGroup:function(t){return(!this.maxLength||this.fileList.length0},Function:function(t){var n=this.getType(t);return n==="Function"||n==="AsyncFunction"},getType:function(t){var n=Object.prototype.toString.call(t);return/^\[object (.*)\]$/.exec(n)[1]},empty:function(t){return t==null||Array.isArray(t)&&Array.isArray(t)&&!t.length?!0:typeof t=="string"&&!t}};["Date","Object","String","Boolean","Array","Number"].forEach(function(e){Ft[e]=function(t){return Ft.type(t,e)}});function no(e,t){return{}.hasOwnProperty.call(e,t)}var dge="fcSelect",fge=ee({name:dge,inheritAttrs:!1,props:{formCreateInject:Object,modelValue:{type:[Array,String,Number,Boolean,Object],default:void 0},type:String},emits:["update:modelValue","fc.el"],setup:function(t){var n=In(t.formCreateInject,"options",[]),r=In(t,"modelValue"),o=function(){return Array.isArray(n.value)?n.value:[]};return{options:o,value:r}},render:function(){var t=this,n,r,o=function(s,u){return Y(we("ElOption"),Qt(s,{key:""+u+"-"+s.value}),null)},i=function(s,u){return Y(we("ElOptionGroup"),{label:s.label,key:""+u+"-"+s.label},{default:function(){return[Ft.trueArray(s.options)&&s.options.map(function(d,f){return o(d,f)})]}})},a=this.options();return Y(we("ElSelect"),Qt(this.$attrs,{modelValue:this.value,"onUpdate:modelValue":function(s){return t.$emit("update:modelValue",s)},ref:"el"}),nn({default:function(){return[a.map(function(s,u){return no(s||"","options")?i(s,u):o(s,u)}),(n=(r=t.$slots).default)===null||n===void 0?void 0:n.call(r)]}},y4(this.$slots,["default"])))},mounted:function(){this.$emit("fc.el",this.$refs.el)}}),pge="fcTree",hge=ee({name:pge,inheritAttrs:!1,formCreateParser:{mergeProp:function(t){var n=t.prop.props;n.nodeKey||(n.nodeKey="id"),n.props||(n.props={label:"title"})}},props:{type:String,modelValue:{type:[Array,String,Number],default:function(){return[]}}},emits:["update:modelValue","fc.el"],watch:{modelValue:function(){this.setValue()}},methods:{updateValue:function(){if(this.$refs.tree){var t;this.type==="selected"?t=this.$refs.tree.getCurrentKey():t=this.$refs.tree.getCheckedKeys(),this.$emit("update:modelValue",t)}},setValue:function(){if(this.$refs.tree){var t=this.type;t==="selected"?this.$refs.tree.setCurrentKey(this.modelValue):this.$refs.tree.setCheckedKeys(va(this.modelValue))}}},render:function(){return Y(we("ElTree"),Qt(this.$attrs,{ref:"tree",onCheck:this.updateValue,onNodeClick:this.updateValue}),this.$slots)},mounted:function(){this.setValue(),this.$emit("fc.el",this.$refs.tree)}}),vge="._fc-upload{width:100%}._fc-exceed .el-upload{display:none}.el-upload-list.is-disabled .el-upload{cursor:not-allowed!important}";_4(vge);var Kz={name:"IconUpload"},mge={class:"icon",viewBox:"0 0 1024 1024",xmlns:"http://www.w3.org/2000/svg"},gge=$("path",{fill:"currentColor",d:"M160 832h704a32 32 0 110 64H160a32 32 0 110-64zm384-578.304V704h-64V247.296L237.248 490.048 192 444.8 508.8 128l316.8 316.8-45.312 45.248L544 253.696z"},null,-1),yge=[gge];function _ge(e,t,n,r,o,i){return R(),W("svg",mge,yge)}Kz.render=_ge;function fO(e,t){return dr(e)==="object"?e:{url:e,is_string:!0,name:bge(e),uid:t}}function pO(e){return nn(nn({},e),{},{file:e,value:e})}function bge(e){return(""+e).split("/").pop()}var wge="fcUpload",xge=ee({name:wge,inheritAttrs:!1,formCreateParser:{toFormValue:function(t){return va(t)},toValue:function(t,n){return n.prop.props.limit===1?t[0]||"":t}},props:{previewMask:void 0,onPreview:Function,httpRequest:Function,modalTitle:String,listType:String,uploadText:String,formCreateInject:Object,modelValue:[Array,String,Object]},emits:["update:modelValue","change","remove","fc.el"],data:function(){return{previewVisible:!1,previewImage:"",fileList:[]}},created:function(){this.fileList=va(this.modelValue).map(fO).map(pO)},watch:{modelValue:function(t){this.fileList=va(t).map(fO).map(pO)}},methods:{handlePreview:function(t){this.onPreview?this.onPreview.apply(this,arguments):this.listType==="text"?window.open(t.url):(this.previewImage=t.url,this.previewVisible=!0)},update:function(t){var n=t.map(function(r){return r.is_string?r.url:r.value||r.url}).filter(function(r){return r!==void 0});this.$emit("update:modelValue",n)},handleCancel:function(){this.previewVisible=!1},handleChange:function(t,n){this.$emit.apply(this,["change"].concat(Array.prototype.slice.call(arguments))),t.status==="success"&&this.update(n)},handleRemove:function(t,n){this.$emit.apply(this,["remove"].concat(Array.prototype.slice.call(arguments))),this.update(n)},doHttpRequest:function(t){if(this.httpRequest)return this.httpRequest(t);t.source="upload",this.formCreateInject.api.fetch(t)}},render:function(){var t,n,r=this,o=va(this.modelValue).length;return Y("div",{class:"_fc-upload"},[Y(we("ElUpload"),Qt({key:o},this.$attrs,{listType:this.listType||"picture-card",class:{"_fc-exceed":this.$attrs.limit?this.$attrs.limit<=o:!1},onPreview:this.handlePreview,onChange:this.handleChange,onRemove:this.handleRemove,httpRequest:this.doHttpRequest,fileList:this.fileList,ref:"upload"}),nn({default:function(){return[((t=(n=r.$slots).default)===null||t===void 0?void 0:t.call(n))||(["text","picture"].indexOf(r.listType)===-1?Y(we("ElIcon"),null,{default:function(){return[Y(Kz,null,null)]}}):Y(we("ElButton"),{type:"primary"},{default:function(){return[r.formCreateInject.t("clickToUpload")||r.uploadText||"点击上传"]}}))]}},y4(this.$slots,["default"]))),Y(we("ElDialog"),{appendToBody:!0,modal:this.previewMask,title:this.modalTitle,modelValue:this.previewVisible,onClose:this.handleCancel},{default:function(){return[Y("img",{style:"width: 100%",src:r.previewImage},null)]}})])},mounted:function(){this.$emit("fc.el",this.$refs.upload)}});function rl(e,t,n){e[t]=n}function Id(e,t){delete e[t]}function jg(e){var t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},n=arguments.length>2?arguments[2]:void 0,r=!1;for(var o in t)if(Object.prototype.hasOwnProperty.call(t,o)){var i=t[o];if((r=Array.isArray(i))||Ft.Object(i)){var a=e[o]===void 0;if(r)r=!1,a&&rl(e,o,[]);else if(i._clone&&n!==void 0)if(n)i=i.getRule(),a&&rl(e,o,{});else{rl(e,o,i._clone());continue}else a&&rl(e,o,{});e[o]=jg(e[o],i,n)}else rl(e,o,i),Ft.Undef(i)||(Ft.Undef(i.__json)||(e[o].__json=i.__json),Ft.Undef(i.__origin)||(e[o].__origin=i.__origin))}return n!==void 0&&Array.isArray(e)?e.filter(function(l){return!l||!l.__ctrl}):e}function ts(e){return jg({},{value:e}).value}var Cge=Object.assign||function(e){for(var t,n=1;n4){var n=e.trim(),r=!1;try{if(n.indexOf(V5)>0&&n.indexOf(j5)===0)n=n.replace(V5,"").replace(j5,""),r=!0;else if(n.indexOf("$FN:")===0)n=n.substring(4),r=!0;else if(n.indexOf("$EXEC:")===0)n=n.substring(6),r=!0;else if(n.indexOf("$GLOBAL:")===0){var o=n.substring(8);return n=function(){for(var l=arguments.length,s=new Array(l),u=0;u0&&this.expandRule(n)},modelValue:{handler:function(t){var n=this;t=t||[];var r=this.sort,o=r.length,i=o-t.length;if(i<0){for(var a=i;a<0;a++)this.addRule(t.length+a,!0);for(var l=0;l0)for(var s=0;s0,i=tthis.min&&i.push(this.delIcon(n,r)),this.sortBtn){var a=this.sortIcon(n,t);a&&i.push(a)}return i},emitEvent:function(t,n,r,o){this.$emit.apply(this,[t].concat(nr(n),[this.cacheRule[o].$f,r]))},expandRule:function(t){for(var n=0;n0&&this.expandRule(t)},render:function(){var t=this,n=this.sort,r=this.button,o=this.form,i=this.disabled,a=this.type==="card",l=n.length===0?this.$slots.default?this.$slots.default({vm:this,add:this.add}):Y("div",{key:"a_def",class:"_fc-group-plus-minus _fc-group-add fc-clock",onClick:this.add},null):n.map(function(s,u){var c=t.cacheRule[s],d=c.rule,f=c.options,h=r&&!i?t.makeIcon(n.length,u,s):[],v=t.getTitle(u,s);return a?Y("div",{class:"_fc-group-container",key:s},[Y("div",{class:"_fc-group-header"},[v===!1?Y("div",{class:"_fc-group-idx"},[u+1]):null,v!==!1?Y("div",{class:"_fc-group-title"},[v]):null,Y("div",{class:"_fc-group-handle fc-clock"},[h.length?h:null])]),Y("div",{class:"_fc-group-content"},[Y(o,Qt({key:s},{disabled:i,"onUpdate:modelValue":function(y){return t.formData(s,y)},"onEmit-event":function(y){for(var g=arguments.length,b=new Array(g>1?g-1:0),x=1;x1?g-1:0),x=1;x1&&arguments[1]!==void 0?arguments[1]:{},r=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{},o=[].concat(Jz,nr(r.normal||[])),i=[].concat(Zz,nr(r.array||[])),a=[].concat(ej,nr(r.functional||[])),l=r.props||[];return t.reduce(function(s,u){for(var c in u)if(s[c])if(l.indexOf(c)>-1)s[c]=e([u[c]],s[c]);else if(o.indexOf(c)>-1)s[c]=nn(nn({},s[c]),u[c]);else if(i.indexOf(c)>-1){var d=s[c]instanceof Array?s[c]:[s[c]],f=u[c]instanceof Array?u[c]:[u[c]];s[c]=[].concat(nr(d),nr(f))}else if(a.indexOf(c)>-1)for(var h in u[c])if(s[c][h]){var v=s[c][h]instanceof Array?s[c][h]:[s[c][h]],m=u[c][h]instanceof Array?u[c][h]:[u[c][h]];s[c][h]=[].concat(nr(v),nr(m))}else s[c][h]=u[c][h];else if(c==="hook")for(var y in u[c])s[c][y]?s[c][y]=Lge(s[c][y],u[c][y]):s[c][y]=u[c][y];else s[c]=u[c];else o.indexOf(c)>-1||a.indexOf(c)>-1||l.indexOf(c)>-1?s[c]=nn({},u[c]):i.indexOf(c)>-1?s[c]=u[c]instanceof Array?nr(u[c]):dr(u[c])==="object"?nn({},u[c]):u[c]:s[c]=u[c];return s},n)},Lge=function(t,n){return function(){t&&t.apply(this,arguments),n&&n.apply(this,arguments)}},H5=["type","slot","ignore","emitPrefix","value","name","native","hidden","display","inject","options","emit","link","prefix","suffix","update","sync","optionsTo","key","slotUpdate","computed","preview","component","cache","modelEmit"],h_=["validate","children","control"],v_=["effect","deep","renderSlots"];function tj(){return[].concat(H5,nr(Jz),nr(Zz),nr(ej),h_,v_)}function Xl(e){var t=e.replace(/(-[a-z])/g,function(n){return n.replace("-","").toLocaleUpperCase()});return nj(t)}function nj(e){return e.replace(e[0],e[0].toLowerCase())}function W5(e,t){return{value:e,enumerable:!1,configurable:!1,writable:!0}}function rj(e,t){return oj([e],t||!1)[0]}function oj(e,t){return jg([],nr(e),t||!1)}function Ou(e,t){return Nc(Array.isArray(t)?t:[t],e,{array:h_,normal:v_}),e}function hO(e){var t=Ft.Function(e.getRule)?e.getRule():e;return t.type||(t.type="input"),t}function Bge(e,t){return e?(Object.keys(t||{}).forEach(function(n){t[n]&&(e[n]=Ou(e[n]||{},t[n]))}),e):t}function ij(e,t){Object.defineProperties(e,Object.keys(t).reduce(function(n,r){return n[r]={get:function(){return t[r]()}},n},{}))}function yu(e){return e.__fc__||(e.__origin__?e.__origin__.__fc__:null)}function tr(e,t){try{t=e()}catch(n){Sge(n)}return t}function G1(){var e={},t=function(r){return r||"default"};return{setSlot:function(r,o){r=t(r),!(!o||Array.isArray(o)&&o.length)&&(e[r]||(e[r]=[]),e[r].push(o))},getSlot:function(r,o){r=t(r);var i=[];return(e[r]||[]).forEach(function(a){if(Array.isArray(a))i.push.apply(i,nr(a));else if(Ft.Function(a)){var l=a.apply(void 0,nr(o||[]));Array.isArray(l)?i.push.apply(i,nr(l)):i.push(l)}else Ft.Undef(a)||i.push(a)}),i},getSlots:function(){var r=this,o={};return Object.keys(e).forEach(function(i){o[i]=function(){for(var a=arguments.length,l=new Array(a),s=0;s0&&(n=Xl(n));var r="on".concat(Gz(n));Array.isArray(t[r])?t[r]=[].concat(nr(t[r]),[e.on[n]]):t[r]?t[r]=[t[r],e.on[n]]:t[r]=e.on[n]}),t.key=e.key,t.ref=e.ref,t.class=e.class,t.id=e.id,t.style=e.style,t.slot&&delete t.slot,t}function q1(e,t){return Object.setPrototypeOf(e,t),e}var mO=function(t,n){return typeof t=="string"?String(n):typeof t=="number"?Number(n):n},gf={"==":function(t,n){return JSON.stringify(t)===JSON.stringify(mO(t,n))},"!=":function(t,n){return!gf["=="](t,n)},">":function(t,n){return t>n},">=":function(t,n){return t>=n},"<":function(t,n){return t-1},notOn:function(t,n){return!gf.on(t,n)},in:function(t,n){return n&&n.indexOf&&n.indexOf(t)>-1},notIn:function(t,n){return!gf.in(t,n)},between:function(t,n){return t>n[0]&&tn[1]},empty:function(t){return Ft.empty(t)},notEmpty:function(t){return!Ft.empty(t)},pattern:function(t,n){return new RegExp(n,"g").test(t)}};function Hi(e,t){return(Array.isArray(t)?t:(t||"").split(".")).forEach(function(n){e!=null&&(e=e[n])}),e}function $ge(e){for(var t=/{{\s*(.*?)\s*}}/g,n,r={};(n=t.exec(e))!==null;)n[1]&&(r[n[1]]=!0);return Object.keys(r)}function gO(e){var t=e.split("."),n=[],r="";return t.forEach(function(o,i){i===0?r=o:r+="."+o,n.push(r)}),n.join(" && ")}function aj(e){for(var t=[],n="",r=0,o=0;o-1&&T.splice(N,1)}else I.subForm()},x=null;Ag(function(){ma(function(){var D="",I=o.option&&o.option.globalClass||{};Object.keys(I).forEach(function(T){var N="";I[T].style&&Object.keys(I[T].style).forEach(function(L){N+=Wm(L)+":"+I[T].style[L]+";"}),I[T].content&&(N+=I[T].content+";"),N&&(D+=".".concat(T,"{").concat(N,"}"))}),o.option&&o.option.style&&(D+=o.option.style),x||(x=document.createElement("style"),x.type="text/css",document.head.appendChild(x)),x.innerHTML=D||""})});var C=jc(function(){v.bus.$emit("$loadData.$topForm")},100),E=jc(function(){v.bus.$emit("$loadData.$scopeForm")},100),A=jc(function(){v.bus.$emit("$loadData.$form")},100),M=function(I){v.bus.$emit("change-$form."+I)};return en(function(){a&&(m.top.bus.$on("$loadData.$form",C),m.top.bus.$on("change",M)),m!==m.scope&&m.scope.bus.$on("$loadData.$scopeForm",E),v.mounted()}),Ar(function(){a&&(m.top.bus.$off("$loadData.$form",C),m.top.bus.$off("change",M)),m!==m.scope&&m.scope.bus.$off("$loadData.$scopeForm",E),b(),h.destroyed=!0,v.unmount(),x&&(x.parentNode||x.parentElement)&&document.head.removeChild(x)}),Gs(function(){v.updated()}),g(),Ye(function(){return o.option},function(){v.initOptions(),m.refresh()},{deep:!0,flush:"sync"}),Ye(function(){return nr(u.value)},function(D){v.$handle.isBreakWatch()||D.length===h.renderRule.length&&D.every(function(I){return h.renderRule.indexOf(I)>-1})||(v.$handle.updateAppendData(),v.$handle.reloadRule(u.value),i.setupState.renderRule())}),Ye(function(){return[o.disabled,o.preview]},function(){m.refresh()}),Ye(c,function(D){Um(D||{})!==h.updateValue&&(m.config.forceCoverValue?m.coverValue(D||{}):m.setValue(D||{}))},{deep:!0,flush:"post"}),Ye(function(){return o.index},function(){m.coverValue({}),v.$handle.updateAppendData(),vt(function(){vt(function(){m.clearValidateState()})})},{flush:"sync"}),nn(nn({fc:Yr(v),parent:a&&Yr(a),top:Yr(l),fapi:Yr(m)},$r(h)),{},{getGroupInject:function(){return ay(i,a)},refresh:function(){++h.unique},renderRule:function(){h.renderRule=nr(u.value||[])},updateValue:function(I){if(!h.destroyed){var T=Um(I);h.updateValue!==T&&(h.updateValue=T,i.emit("update:modelValue",I),vt(function(){A(),a?d.value||E():(C(),E())}))}}})},created:function(){var o=Kt();o.emit("update:api",o.setupState.fapi),o.setupState.fc.init()}})}function lj(){return{props:{},on:{},options:[],children:[],hidden:!1,display:!0,value:void 0}}function Mi(e,t){return function(n,r,o){var i=arguments.length>3&&arguments[3]!==void 0?arguments[3]:{},a=new NA(e,n,r,o,i);return t&&(Ft.Function(t)?t(a):a.props(t)),a}}function NA(e,t,n,r,o){this._data=Nn(lj(),{type:e,title:t,field:n,value:r,props:o||{}}),this.event=this.on}Nn(NA.prototype,{getRule:function(){return this._data},setProp:function(t,n){return rl(this._data,t,n),this},modelField:function(t){return this._data.modelField=t,this},_clone:function(){var t=new this.constructor;return t._data=rj(this._data),t}});function sj(e){e.forEach(function(t){NA.prototype[t]=function(n){return Ou(this._data,go({},t,arguments.length<2?n:go({},n,arguments[1]))),this}})}sj(tj());var jge=Mi("");function Vge(e,t,n){var r=jge("",t);return r._data.type=e,r._data.title=n,r}function Hge(){return{create:Vge,factory:Mi}}function Wge(e,t,n){var r="fail to ".concat(e," ").concat(n.status,"'"),o=new Error(r);return o.status=n.status,o.url=e,o}function yO(e){var t=e.responseText||e.response;if(!t)return t;try{return JSON.parse(t)}catch{return t}}function uj(e){if(!(typeof XMLHttpRequest>"u")){var t=new XMLHttpRequest,n=e.action||"";if(t.upload&&e.onProgress&&t.upload.addEventListener("progress",function(l){l.percent=l.total>0?l.loaded/l.total*100:0,e.onProgress(l)}),e.query){var r=Ft.String(e.query)?e.query:Object.keys(e.query).reduce(function(l,s){return l[s]=e.query[s]===null||e.query[s]===void 0?"":e.query[s],l},{}),o=new URLSearchParams(r).toString();o&&(n.includes("?")?n+="&".concat(o):n+="?".concat(o))}t.onerror=function(s){e.onError(s)},t.onload=function(){if(t.status<200||t.status>=300)return e.onError(Wge(n,e,t),yO(t));e.onSuccess(yO(t))},t.open(e.method||"get",n,!0);var i;(e.data||e.file)&&(e.file||(e.dataType||"").toLowerCase()!=="json"?(i=new FormData,Object.keys(e.data||{}).map(function(l){i.append(l,e.data[l])})):(i=JSON.stringify(e.data||{}),t.setRequestHeader("content-type","application/json"))),e.file&&i.append(e.filename,e.file,e.file.name),e.withCredentials&&"withCredentials"in t&&(t.withCredentials=!0);var a=e.headers||{};Object.keys(a).forEach(function(l){a[l]!=null&&t.setRequestHeader(l,a[l])}),t.send(i)}}function _O(e,t,n){return new Promise(function(r,o){(t||uj)(nn(nn({},e),{},{onSuccess:function(a){var l=function(c){return c},s=Hs(e.parse);Ft.Function(s)?l=s:s&&Ft.String(s)&&(l=function(c){return Hi(c,s)}),p_(l(a,e.targetRule,n)).then(function(u){r(u)})},onError:function(a){o(a)}}))})}function g0(e){return ts(e)}function Uge(e){function t(i){return Ft.Undef(i)?i=e.fields():Array.isArray(i)||(i=[i]),i}function n(i,a,l){Ft.Undef(i)?i=Object.keys(nn(nn({},e.fieldCtx),e.nameCtx)):Array.isArray(i)||(i=[i]),i.forEach(function(s){var u=e.fieldCtx[s]||e.nameCtx[s];u&&u.forEach(function(c){rl(c.rule,a,l),e.$render.clearCache(c)})})}function r(){var i=e.subForm;return Object.keys(i).reduce(function(a,l){var s=i[l];return s&&(Array.isArray(s)?a.push.apply(a,nr(s)):a.push(s)),a},[])}var o={get isScope(){return e.vm.props.subForm===!1},get isPreview(){return e.preview},get config(){return e.options},set config(i){e.fc.options.value=i},get options(){return e.options},set options(i){e.fc.options.value=i},get form(){return e.form},get rule(){return e.rules},get parent(){return e.vm.setupState.parent&&e.vm.setupState.parent.setupState.fapi},get top(){return o.parent?o.parent.top:o},get scope(){for(var i=e.vm;i&&i.setupState.fapi;)if(i.props.subForm)i=i.setupState.parent;else return i.setupState.fapi;return o.top},get children(){return r()},get siblings(){var i=e.vm.setupState.getGroupInject();if(i){var a=i.getSubForm();if(Array.isArray(a))return nr(a)}},get index(){var i=o.siblings;if(i){var a=i.indexOf(o);return a>-1?a:void 0}},get formulas(){return nn({},e.fc.formulas)},formData:function(a){if(a==null||typeof a=="boolean"){var l={};return Object.keys(e.form).forEach(function(s){(a===!0||e.ignoreFields.indexOf(s)===-1)&&(l[s]=g0(e.form[s]))}),l}else return t(a).reduce(function(s,u){return s[u]=o.getValue(u),s},{})},getValue:function(a){var l=e.getFieldCtx(a);return l?g0(l.rule.value):e.options.appendValue!==!1&&no(e.appendData,a)?g0(e.appendData[a]):void 0},coverValue:function(a){var l=nn({},a||{});e.deferSyncValue(function(){e.appendData={},o.fields().forEach(function(s){var u=e.fieldCtx[s];if(u){var c=no(a,s);u.forEach(function(d){d.rule.value=c?a[s]:void 0}),delete l[s]}}),Nn(e.appendData,l)},!0)},setValue:function(a){var l=a;arguments.length>=2&&(l=go({},a,arguments[1])),e.deferSyncValue(function(){Object.keys(l).forEach(function(s){var u=e.fieldCtx[s];if(!u)return e.appendData[s]=l[s];u.forEach(function(c){c.rule.value=l[s]})})},!0)},removeField:function(a){var l=e.getCtx(a);return e.deferSyncValue(function(){e.getCtxs(a).forEach(function(s){s.rm()})},!0),l?l.origin:void 0},removeRule:function(a){var l=a&&yu(a);if(l)return l.rm(),l.origin},fields:function(){return e.fields()},append:function(a,l,s){var u=e.sort.length-1,c,d=e.getCtx(l);if(d)if(s){if(c=d.getPending("children",d.rule.children),!Array.isArray(c))return;u=d.rule.children.length-1}else u=d.root.indexOf(d.origin),c=d.root;else c=e.rules;c.splice(u+1,0,a)},prepend:function(a,l,s){var u=0,c,d=e.getCtx(l);if(d)if(s){if(c=d.getPending("children",d.rule.children),!Array.isArray(c))return}else u=d.root.indexOf(d.origin),c=d.root;else c=e.rules;c.splice(u,0,a)},hidden:function(a,l){n(l,"hidden",!!a),e.refresh()},hiddenStatus:function(a){var l=e.getCtx(a);if(l)return!!l.rule.hidden},display:function(a,l){n(l,"display",!!a),e.refresh()},displayStatus:function(a){var l=e.getCtx(a);if(l)return!!l.rule.display},disabled:function(a,l){t(l).forEach(function(s){e.getCtxs(s).forEach(function(u){rl(u.rule.props,"disabled",!!a)})}),e.refresh()},all:function(a){return Object.keys(e.ctxs).map(function(l){var s=e.ctxs[l];return a?s.origin:s.rule})},model:function(a){return e.fields().reduce(function(l,s){var u=e.fieldCtx[s][0];return l[s]=a?u.origin:u.rule,l},{})},component:function(a){return Object.keys(e.nameCtx).reduce(function(l,s){var u=e.nameCtx[s].map(function(c){return a?c.origin:c.rule});return l[s]=u.length===1?u[0]:u,l},{})},bind:function(){return o.form},reload:function(a){e.reloadRule(a)},updateOptions:function(a){e.fc.updateOptions(a),o.refresh()},onSubmit:function(a){o.updateOptions({onSubmit:a})},sync:function(a){if(Array.isArray(a)){a.forEach(function(s){return o.sync(s)});return}var l=Ft.Object(a)?yu(a):e.getCtxs(a);l&&(l=Array.isArray(l)?l:[l],l.forEach(function(s){if(!s.deleted){var u=e.subForm[s.id];u&&(Array.isArray(u)?u.forEach(function(c){c.refresh()}):u&&u.refresh()),e.$render.clearCache(s)}}),e.refresh())},refresh:function(){r().forEach(function(a){a.refresh()}),e.$render.clearCacheAll(),e.refresh()},refreshOptions:function(){e.$manager.updateOptions(e.options),o.refresh()},hideForm:function(a){e.vm.setupState.isShow=!a},changeStatus:function(){return e.changeStatus},clearChangeStatus:function(){e.changeStatus=!1},updateRule:function(a,l){e.getCtxs(a).forEach(function(s){Nn(s.rule,l)})},updateRules:function(a){Object.keys(a).forEach(function(l){o.updateRule(l,a[l])})},mergeRule:function(a,l){e.getCtxs(a).forEach(function(s){Ou(s.rule,l)})},mergeRules:function(a){Object.keys(a).forEach(function(l){o.mergeRule(l,a[l])})},getRule:function(a,l){var s=e.getCtx(a);if(s)return l?s.origin:s.rule},findType:function(a,l){var s=void 0;return Object.keys(e.ctxs).forEach(function(u){var c=e.ctxs[u];c.rule.type===a&&(s=l?c.origin:c.rule)}),s},findTypes:function(a,l){var s=[];return Object.keys(e.ctxs).forEach(function(u){var c=e.ctxs[u];c.rule.type===a&&s.push(l?c.origin:c.rule)}),s},getRenderRule:function(a){var l=e.getCtx(a);if(l)return l.prop},getRefRule:function(a){var l=e.getCtxs(a);if(l){var s=l.map(function(u){return u.rule});return s.length===1?s[0]:s}},setEffect:function(a,l,s){var u=e.getCtx(a);u&&l&&(l[0]==="$"&&(l=l.substr(1)),no(u.rule,"$"+l)&&rl(u.rule,"$"+l,s),no(u.rule,"effect")||(u.rule.effect={}),rl(u.rule.effect,l,s))},clearEffectData:function(a,l){var s=e.getCtx(a);s&&(l&&l[0]==="$"&&(l=l.substr(1)),s.clearEffectData(l),o.sync(a))},updateValidate:function(a,l,s){s?o.mergeRule(a,{validate:l}):n(a,"validate",l)},updateValidates:function(a,l){Object.keys(a).forEach(function(s){o.updateValidate(s,a[s],l)})},refreshValidate:function(){o.refresh()},resetFields:function(a){t(a).forEach(function(l){e.getCtxs(l).forEach(function(s){e.$render.clearCache(s),s.rule.value=g0(s.defaultValue)})}),vt(function(){vt(function(){vt(function(){o.clearValidateState(a)})})}),a==null&&(Ft.Function(e.options.onReset)&&tr(function(){return e.options.onReset(o)}),e.vm.emit("reset",o))},method:function(a,l){var s=o.el(a);if(!s||!s[l])throw new Error(qz("err","".concat(l," 方法不存在")));return function(){return s[l].apply(s,arguments)}},exec:function(a,l){for(var s=arguments.length,u=new Array(s>2?s-2:0),c=2;c2?u-2:0),d=2;d1?s-1:0),c=1;c-1&&n.indexOf("}}")>-1){var a=n,l=$ge(n),s=function(f){var h=!1,v;if(o&&f.indexOf("$form.")===0){var m=f.split(".");m.shift(),no(o.value,m[0])&&(h=!0,v=r?r({id:"$form."+m[0]+"_"+o.rule.__fc__.id,getValue:function(){return Hi(o.value,m)}}):Hi(o.value,m))}return h||(v=r?r(f):i.fc.getLoadData(f)),v},u=function d(f){var h=[];f.forEach(function(m){m.key?h.push(m.key):m.children&&h.push(d(m.children))});var v=!1;return h.forEach(function(m,y){m!=null&&(m.indexOf("'")===0||m.indexOf('"')===0)&&(h[y]=m.slice(1,-1),v=!0)}),h.length===1&&(v||!isNaN(Number(h[0])))?h[0]:s(h.join("."))},c;if(l.forEach(function(d){var f=d.split("||"),h=f[0].trim();if(h){var v=(f[1]||"").trim(),m=aj(h),y=tr(function(){return u(m)});(y==null||y==="")&&f.length>1&&(y=v),c=y,n=n.replaceAll("{{".concat(d,"}}"),y??"")}}),l.length===1&&a==="{{".concat(l[0],"}}"))return c}return n},loadFetchVar:function(n,r,o){var i=this,a;o&&o.__fc__&&(a=o.__fc__.getParentGroup());var l=function(u){return i.loadStrVar(u,r,a?{rule:o,value:i.subRuleData[a.id]||{}}:null)};return n.action=l(n.action||""),["headers","data","query"].forEach(function(s){if(n[s]){var u=Array.isArray(n[s])?[]:{};Object.keys(n[s]).forEach(function(c){u[l(c)]=l(n[s][c])}),n[s]=u}}),n}})}var bO=["hook:updated","hook:mounted"];function Xge(e){Nn(e.prototype,{usePage:function(){var n=this,r=this.options.page;if(r){var o=25,i=Qge(this.rules);Ft.Object(r)&&(r.first&&(o=parseInt(r.first,10)||o),r.limit&&(i=parseInt(r.limit,10)||i)),Nn(this,{first:o,limit:i,pageEnd:this.rules.length<=o}),this.bus.$on("page-end",function(){return n.vm.emit("page-end",n.api)}),this.pageLoad()}},pageLoad:function(){var n=this,r=function o(){n.pageEnd?(n.bus.$off(bO,o),n.bus.$emit("page-end")):(n.first+=n.limit,n.pageEnd=n.rules.length<=n.first,n.loadRule(),n.refresh())};this.bus.$on(bO,r)}})}function Qge(e){return e.length<31?31:Math.ceil(e.length/3)}function Jge(e){Nn(e.prototype,{clearNextTick:function(){this.nextTick&&clearTimeout(this.nextTick),this.nextTick=null},bindNextTick:function(n){var r=this;this.clearNextTick(),this.nextTick=setTimeout(function(){n(),r.nextTick=null},10)},render:function(){return++this.loadedId,this.vm.setupState.unique>0?this.$render.render():(this.vm.setupState.unique=1,[])}})}function Zge(e){Object.defineProperties(e.origin,{__fc__:W5(Yr(e))}),e.rule!==e.origin&&Object.defineProperties(e.rule,{__fc__:W5(Yr(e))})}function cj(e,t,n){var r=b4(),o=!!t.field;Nn(this,{id:r,ref:r,wrapRef:r+"fi",rule:t,origin:t.__origin__||t,name:t.name,pending:{},none:!1,watch:[],linkOn:[],root:[],ctrlRule:[],children:[],parent:null,group:t.subRule?this:null,cacheConfig:null,prop:nn({},t),computed:{},payload:{},refRule:{},input:o,el:void 0,exportEl:void 0,defaultValue:o?ts(n):void 0,field:t.field||void 0}),this.updateKey(),Zge(this),this.update(e,!0)}Nn(cj.prototype,{getParentGroup:function(){for(var t=this.parent;t;){if(t.group)return t;t=t.parent}},loadChildrenPending:function(){var t=this,n=this.rule.children||[];return Array.isArray(n)?n:this.loadPending({key:"children",origin:n,def:[],onLoad:function(o){t.$handle&&t.$handle.loadChildren(o,t)},onUpdate:function(o,i){t.$handle&&(o===i?t.$handle.loadChildren(o,t):t.$handle.updateChildren(t,o,i))},onReload:function(o){t.$handle?t.$handle.updateChildren(t,[],o):delete t.pending.children}})},loadPending:function(t){var n=this,r=t.key,o=t.origin,i=t.def,a=t.onLoad,l=t.onReload,s=t.onUpdate;if(this.pending[r]&&this.pending[r].origin===o)return this.getPending(r,i);delete this.pending[r];var u=o;if(Ft.Function(o)){var c=tr(function(){return o({rule:n.rule,api:n.$api,update:function(f){var h=f||i,v=n.getPending(r,i);n.setPending(r,o,h),s&&s(h,v)},reload:function(){var f=n.getPending(r,i);delete n.pending[r],l&&l(f),n.$api&&n.$api.sync(n.rule)}})});c&&Ft.Function(c.then)?(c.then(function(d){var f=d||i;n.setPending(r,o,f),a&&a(f),n.$api&&n.$api.sync(n.rule)}).catch(function(d){console.error(d)}),u=i,this.setPending(r,o,u)):(u=c||i,this.setPending(r,o,u),a&&a(u))}return u},getPending:function(t,n){return this.pending[t]&&this.pending[t].value||n},setPending:function(t,n,r){this.pending[t]={origin:n,value:mn(r)}},effectData:function(t){return this.payload[t]||(this.payload[t]={}),this.payload[t]},clearEffectData:function(t){t===void 0?this.payload={}:delete this.payload[t]},updateKey:function(t){this.key=b4(),t&&this.parent&&this.parent.updateKey(t)},updateType:function(){this.originType=this.rule.type,this.type=Xl(this.rule.type),this.trueType=this.$handle.getType(this.originType)},setParser:function(t){this.parser=t,t.init(this)},initProp:function(){var t=this,n,r,o=nn({},this.rule);delete o.children,delete o.validate,this.prop=Ou({},[o].concat(nr(Object.keys(this.payload).map(function(i){return t.payload[i]})),[this.computed])),this.prop.validate=[].concat(nr(((n=this.refRule)===null||n===void 0||(r=n.__$validate)===null||r===void 0?void 0:r.value)||[]),nr(this.prop.validate||[]))},initNone:function(){this.none=!(Ft.Undef(this.prop.display)||this.prop.display)},hasHidden:function(){return!!this.rule.hidden||(this.parent?this.parent.hasHidden():!1)},injectValidate:function(){return this.prop.validate},check:function(t){return this.vm===t.vm},unwatch:function(){var t=this;this.watch.forEach(function(n){return n()}),this.watch=[],Object.keys(this.refRule).forEach(function(n){n.indexOf("__$")!==0&&delete t.refRule[n]})},unlink:function(){this.linkOn.forEach(function(t){return t()}),this.linkOn=[]},link:function(){this.unlink(),this.$handle.appendLink(this)},watchTo:function(){this.$handle.watchCtx(this)},delete:function(){this.unwatch(),this.unlink(),this.rmCtrl(),this.parent&&this.parent.children.splice(this.parent.children.indexOf(this)>>>0,1),Nn(this,{deleted:!0,computed:{},parent:null,children:[],cacheConfig:null,none:!1})},rmCtrl:function(){this.ctrlRule.forEach(function(t){return t.__fc__&&t.__fc__.rm()}),this.ctrlRule=[]},rm:function(){var t=this,n=function(){var o=t.root.indexOf(t.origin);o>-1&&(t.root.splice(o,1),t.$handle&&t.$handle.refresh())};if(this.deleted){n();return}this.$handle.noWatch(function(){t.$handle.deferSyncValue(function(){t.rmCtrl(),n(),t.$handle.rmCtx(t),Nn(t,{root:[]})},t.input)})},update:function(t,n){Nn(this,{deleted:!1,$handle:t,$render:t.$render,$api:t.api,vm:t.vm,vNode:t.$render.vNode,updated:!1,cacheValue:this.rule.value}),!n&&this.unwatch(),this.watchTo(),this.link(),this.updateType()}});function e1e(e){Nn(e.prototype,{nextRefresh:function(n){var r=this,o=this.loadedId;vt(function(){o===r.loadedId&&(n?n():r.refresh())})},parseRule:function(n){var r=this,o=hO(n);return Object.defineProperties(o,{__origin__:W5(n)}),t1e(o),this.appendValue(o),[o,o.prefix,o.suffix].forEach(function(i){i&&r.loadFn(i,o)}),this.loadCtrl(o),o.update&&(o.update=Hs(o.update)),o},loadFn:function(n,r){var o=this;["on","props","deep"].forEach(function(i){n[i]&&o.parseInjectEvent(r,n[i])})},loadCtrl:function(n){n.control&&n.control.forEach(function(r){r.handle&&(r.handle=Hs(r.handle))})},syncProp:function(n){var r=this,o=n.rule;Ft.trueArray(o.sync)&&Nc([{on:o.sync.reduce(function(i,a){return i[dr(a)==="object"&&a.event||"update:".concat(a)]=function(l){o.props[dr(a)==="object"&&a.prop||a]=l,r.vm.emit("sync",a,l,o,r.fapi)},i},{})}],n.computed)},loadRule:function(){var n=this;this.cycleLoad=!1,this.loading=!0,this.pageEnd&&this.bus.$emit("load-start"),this.deferSyncValue(function(){if(n._loadRule(n.rules),n.loading=!1,n.cycleLoad&&n.pageEnd)return n.loadRule();n.syncForm(),n.pageEnd&&n.bus.$emit("load-end"),n.vm.setupState.renderRule()})},loadChildren:function(n,r){if(this.cycleLoad=!1,this.loading=!0,this.bus.$emit("load-start"),this._loadRule(n,r),this.loading=!1,this.cycleLoad)return this.loadRule();this.syncForm(),this.bus.$emit("load-end"),this.$render.clearCache(r)},_loadRule:function(n,r){var o=this,i=function s(u){var c=n[u-1];if(!c||!c.__fc__)return u>0?s(u-1):-1;var d=o.sort.indexOf(c.__fc__.id);return d>-1?d:s(u-1)},a=function(u,c){Ft.trueArray(u)&&o._loadRule(u,c)},l=n.map(function(s,u){if(!(r&&!Ft.Object(s))&&!(!o.pageEnd&&!r&&u>=o.first)){if(s.__fc__&&s.__fc__.root===n&&o.ctxs[s.__fc__.id])return a(s.__fc__.loadChildrenPending(),s.__fc__),s.__fc__;var c=hO(s),d=function(){return!!(c.field&&o.fieldCtx[c.field]&&o.fieldCtx[c.field][0]!==s.__fc__)};o.fc.targetFormDriver("loadRule",{rule:c,api:o.api},o.fc),o.ruleEffect(c,"init",{repeat:d()}),d()&&o.vm.emit("repeat-field",s,o.api);var f,h=!1,v=!!s.__fc__,m=c.value;if(v){if(f=s.__fc__,m=f.defaultValue,f.deleted){if(wO(f))return;f.update(o)}else if(!f.check(o)){if(wO(f))return;n[u]=s=s._clone?s._clone():Xz(Um(s)),f=null,h=!0}}if(f)f.originType!==f.rule.type&&f.updateType(),o.bindParser(f),o.appendValue(f.rule),f.parent&&f.parent!==r&&o.rmSubRuleData(f);else{var y=o.parseRule(s);f=new cj(o,y,m),o.bindParser(f)}o.parseEmit(f),o.syncProp(f),f.parent=r||null,f.root=n,o.setCtx(f),!h&&!v&&(o.effect(f,"load"),o.targetHook(f,"load")),o.effect(f,"created");var g=f.loadChildrenPending();if(f.parser.loadChildren===!1||a(g,f),!r){var b=i(u);b>-1||!u?o.sort.splice(b+1,0,f.id):o.sort.push(f.id)}var x=f.rule;return f.updated||(f.updated=!0,Ft.Function(x.update)&&o.bus.$once("load-end",function(){o.refreshUpdate(f,x.value,"init")}),o.effect(f,"loaded")),o.refreshControl(f)&&(o.cycleLoad=!0),f}}).filter(function(s){return!!s});r&&(r.children=l)},refreshControl:function(n){return n.input&&n.rule.control&&this.useCtrl(n)},useCtrl:function(n){var r=this,o=n1e(n),i=[],a=this.api;if(!o.length)return!1;for(var l=function(h){var v=o[h],m=v.handle||function(g){return(gf[v.condition||"=="]||gf["=="])(g,v.value)};if(!Ft.trueArray(v.rule))return"continue";var y=nn(nn({},v),{},{valid:tr(function(){return m(n.rule.value,a)}),ctrl:r1e(n,v.rule),isHidden:Ft.String(v.rule[0])});if(y.valid&&y.ctrl||!y.valid&&!y.ctrl&&!y.isHidden)return"continue";i.push(y)},s=0;s>>0,1),c[v?"push":"unshift"](function(){C==="disabled"||C==="enabled"?r.api.disabled(!v,m):C==="display"||C==="show"?r.api.display(v,m):C==="required"?(m.forEach(function(M){r.api.setEffect(M,"required",v)}),v||r.api.clearValidateState(m)):r.api.hidden(!v,m)});return}if(v){d=!0;var E={type:"fragment",native:!0,__ctrl:!0,children:m};n.ctrlRule.push(E),r.bus.$once("load-start",function(){y?a.prepend(E,y,b):g||b?a.append(E,g||n.id,b):n.root.splice(n.root.indexOf(n.origin)+1,0,E)})}else{n.ctrlRule.splice(n.ctrlRule.indexOf(x),1);var A=yu(x);A&&A.rm()}})}),c.length&&(this.loading?c.length&&this.bus.$once("load-end",function(){c.forEach(function(f){return f()})}):c.length&&vt(function(){c.forEach(function(f){return f()})})),this.vm.emit("control",n.origin,this.api),this.effect(n,"control"),d},reloadRule:function(n){return this._reloadRule(n)},_reloadRule:function(n){var r=this;n||(n=this.rules);var o=nn({},this.ctxs);this.clearNextTick(),this.initData(n),this.fc.rules=n,this.deferSyncValue(function(){r.bus.$once("load-end",function(){Object.keys(o).filter(function(i){return r.ctxs[i]===void 0}).forEach(function(i){return r.rmCtx(o[i])}),r.$render.clearCacheAll()}),r.reloading=!0,r.loadRule(),r.reloading=!1,r.refresh(),r.bus.$emit("reloading",r.api)}),this.bus.$off("next-tick",this.nextReload),this.bus.$once("next-tick",this.nextReload),this.bus.$emit("update",this.api)},refresh:function(){this.vm.setupState.refresh()}})}function t1e(e){var t=lj();return Object.keys(t).forEach(function(n){no(e,n)||(e[n]=t[n])}),e}function n1e(e){var t=e.rule.control||[];return Ft.Object(t)?[t]:t}function r1e(e,t){for(var n=0;n>>0,1).length>0;return a.length||delete this[i][r],l},getFieldCtx:function(n){return(this.fieldCtx[n]||[])[0]},getNameCtx:function(n){return this.nameCtx[n]||[]},setCtx:function(n){var r=n.id,o=n.field,i=n.name,a=n.rule;this.ctxs[r]=n,i&&this.setIdCtx(n,i,"name"),n.input&&(this.setIdCtx(n,o,"field"),this.setFormData(n,n.parser.toFormValue(a.value,n)),this.isMounted&&!this.reloading&&this.vm.emit("change",n.field,a.value,n.origin,this.api))},getParser:function(n){var r=this.fc.parsers,o=this.fc.renderDriver;if(o){var i=o.parsers||{},a=i[n.originType]||i[Xl(n.type)]||i[n.trueType];if(a)return a}return r[n.originType]||r[Xl(n.type)]||r[n.trueType]||ly},bindParser:function(n){n.setParser(this.getParser(n))},getType:function(n){var r=this.fc.CreateNode.aliasMap,o=r[n]||r[Xl(n)]||n;return Xl(o)},noWatch:function(n){this.noWatchFn||(this.noWatchFn=n),tr(n),this.noWatchFn===n&&(this.noWatchFn=null)},watchCtx:function(n){var r=this,o=tj();if(o.filter(function(a){return a[0]!=="_"&&a[0]!=="$"&&i1e.indexOf(a)===-1}).forEach(function(a){var l=In(n.rule,a),s=a==="children";n.refRule[a]=l,n.watch.push(Ye(s?function(){return Ft.Function(l.value)?l.value:nr(l.value||[])}:function(){return l.value},function(u,c){var d=l.value;if(!r.isBreakWatch()){if(s&&n.parser.loadChildren===!1){r.$render.clearCache(n),r.nextRefresh();return}if(r.watching=!0,vt(function(){r.targetHook(n,"watch",{key:a,oldValue:c,newValue:d})}),a==="hidden"&&!!d!=!!c&&(r.$render.clearCacheAll(),vt(function(){r.targetHook(n,"hidden",{value:d})})),a==="ignore"&&n.input||a==="hidden"&&n.input&&(n.rule.ignore==="hidden"||r.options.ignoreHiddenFields))r.syncForm();else if(a==="link"){n.link();return}else["props","on","deep"].indexOf(a)>-1?(r.parseInjectEvent(n.rule,d||{}),a==="props"&&n.input&&r.setFormData(n,n.parser.toFormValue(n.rule.value,n))):a==="emit"?r.parseEmit(n):["prefix","suffix"].indexOf(a)>-1?d&&r.loadFn(d,n.rule):a==="type"?(n.updateType(),r.bindParser(n)):s&&(Ft.Function(c)&&(c=n.getPending("children",[])),Ft.Function(d)&&(d=n.loadChildrenPending()),r.updateChildren(n,d,c));r.$render.clearCache(n),r.refresh(),r.watching=!1}},{deep:!s}))}),n.refRule.__$title=G(function(){var a=(dr(n.rule.title)==="object"?n.rule.title.title:n.rule.title)||"";if(a){var l=a.match(/^\{\{\s*\$t\.(.+)\s*\}\}$/);l&&(a=r.api.t(l[1]))}return a}),n.refRule.__$info=G(function(){var a=(dr(n.rule.info)==="object"?n.rule.info.info:n.rule.info)||"";if(a){var l=a.match(/^\{\{\s*\$t\.(.+)\s*\}\}$/);l&&(a=r.api.t(l[1]))}return a}),n.refRule.__$validate=G(function(){var a=function(s){var u=s.match(/^\{\{\s*\$t\.(.+)\s*\}\}$/);if(u){var c,d;return r.api.t(u[1],{title:(c=n.refRule)===null||c===void 0||(d=c.__$title)===null||d===void 0?void 0:d.value})}return s};return va(n.rule.validate).map(function(l){var s=nn({},l);if(s.message&&(s.message=a(s.message)),Ft.Function(s.validator)){var u=n;s.validator=function(){for(var d,f=arguments.length,h=new Array(f),v=0;v-1:s.value!==Hi(n.rule,l))&&u(s.value),n.watch.push(Ye(s,function(c){c!==y0&&setTimeout(function(){u(c)})},{deep:!0}))}))}),this.watchEffect(n)},adapterValidate:function(n,r){var o=this,i=function(l,s){var u=n.beforeValidate&&tr(function(){return n.beforeValidate({value:l,api:o.api,validate:n,rule:r.rule})});if(u===!1)s();else{var c=o.validator(r,l,n);if(c){var f="";dr(n.error)==="object"&&(f=n.error[c]||n.error.default),!f&&typeof n.message=="string"&&(f=n.message),f||(f=o.getValidateMessage(r,{key:c,rule:n[c]})),s(f)}else if(n.validator){var d=n.validator&&tr(function(){return n.validator(l,s)});d&&Ft.Function(d.then)&&d.then(function(){return s()}).catch(function(h){return s(h)})}else s()}};return this.$manager.adapterValidate({required:n.required,message:n.message,target:n.trigger},i)},getValidateMessage:function(n,r){var o,i,a,l=Array.isArray(r.rule)?r.rule.join(","):""+r.rule;return this.api.t(r.key==="required"?r.key:"validate."+r.key,(a={},go(a,r.key,l),go(a,"title",(o=n.refRule)===null||o===void 0||(i=o.__$title)===null||i===void 0?void 0:i.value),a))},validator:function(n,r,o){var i=this,a=Ft.empty(r);if(a)return o.required?"required":void 0;for(var l=function(){var f=Ome(u[s],2),h=f[0],v=f[1];switch(h){case"len":case"maxLen":case"minLen":var m=function(M){return h==="len"?M===v:h==="maxLen"?M<=v:M>=v};if(Array.isArray(r)){if(!m(r.length))return{v:h}}else{if(dr(r)==="object")return{v:h};if(!m((""+r).length))return{v:h}}break;case"pattern":var y=typeof v=="string"?new RegExp(v):v;if(!y.test(""+r))return{v:h};break;case"uppercase":if(v&&(typeof r!="string"||!/^[A-Z]*$/.test(r)))return{v:h};break;case"lowercase":if(v&&(typeof r!="string"||!/^[a-z]*$/.test(r)))return{v:h};break;case"min":case"max":case"positive":case"negative":case"integer":case"number":var g=Number(r);if(Number.isNaN(g))return{v:h};if(h==="min"&&gv)return{v:h};if(h==="positive"&&g<=0)return{v:h};if(h==="negative"&&g>=0)return{v:h};if(h==="integer"&&!Number.isInteger(g))return{v:h};break;case"equal":if(r!==v)return{v:h};break;case"enum":if(Array.isArray(v)&&!v.includes(r))return{v:h};break;case"hasKeys":if(dr(r)!=="object"||Array.isArray(v)&&v.some(function(A){return!(A in r)}))return{v:h};break;case"email":var b=/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;if(!b.test(""+r))return{v:h};break;case"url":var x=new RegExp("^(?!mailto:)(?:(?:http|https|ftp)://)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$","i");if(!x.test(""+r))return{v:h};break;case"ip":var C=/^(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})(\.(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})){3}$/;if(!C.test(""+r))return{v:h};break;case"phone":var E=/^(?:(?:\+|00)86)?1[3-9]\d{9}$/;if(!E.test(""+r))return{v:h};break;case"computed":if(!i.compute(n,v))return{v:h};break}},s=0,u=Object.entries(o);s-1&&this.sort.splice(u,1),this.$render.clearCache(n),n.delete(),this.effect(n,"deleted"),this.targetHook(n,"deleted"),a&&!this.fieldCtx[i]&&this.vm.emit("remove-field",i,n.rule,this.api),n.rule.__ctrl||this.vm.emit("remove-rule",n.rule,this.api),n}}})}function l1e(e){Nn(e.prototype,{mounted:function(){var n=this,r=function(){n.isMounted=!0,n.lifecycle("mounted")};this.pageEnd?r():this.bus.$once("page-end",r)},lifecycle:function(n){this.fc.targetFormDriver(n,this.api,this.fc),this.vm.emit(n,this.api),this.emitEvent(n,this.api)},emitEvent:function(n){for(var r,o=arguments.length,i=new Array(o>1?o-1:0),a=1;a-1||(i.push(l),o.bus.$on("p:".concat(r||n.name,":").concat(l,":").concat(n.input?1:0),function(s,u){n[s]&&n[s].apply(n,nr(u))}))}),n._used=i},watchEffect:function(n){var r=this,o={required:function(){var a,l;return(no(n.rule,"$required")?n.rule.$required:(a=n.rule)===null||a===void 0||(l=a.effect)===null||l===void 0?void 0:l.required)||!1}};Object.keys(n.rule.effect||{}).forEach(function(i){o[i]=function(){return n.rule.effect[i]}}),Object.keys(n.rule).forEach(function(i){i[0]==="$"&&(o[i.substr(1)]=function(){return n.rule[i]})}),Object.keys(o).forEach(function(i){n.watch.push(Ye(o[i],function(a){r.effect(n,"watch",go({},i,a))},{deep:!0}))})},ruleEffect:function(n,r,o){this.emitEffect({rule:n,input:!!n.field,type:this.getType(n.type)},r,o)},effect:function(n,r,o){this.emitEffect({rule:n.rule,input:n.input,type:n.trueType,ctx:n,custom:o},r)},getEffect:function(n,r){if(no(n,"$"+r))return n["$"+r];if(no(n,"effect")&&no(n.effect,r))return n.effect[r]},emitEffect:function(n,r,o){var i=this,a=n.ctx,l=n.rule,s=n.input,u=n.type,c=n.custom;if(!(!u||["fcFragment","fragment"].indexOf(u)>-1)){var d=c||Object.keys(l).reduce(function(f,h){return h[0]==="$"&&(f[h.substr(1)]=l[h]),f},nn({},l.effect||{}));Object.keys(d).forEach(function(f){var h=i.providers[f];if(!(!h||h.input&&!s)){var v;if(!h._c)v="*";else if(h._used.indexOf(u)>-1)v=u;else return;var m=nn({value:d[f],getValue:function(){return i.getEffect(l,f)}},o||{});a&&(m.getProp=function(){return a.effectData(f)},m.clearProp=function(){return a.clearEffectData(f)},m.mergeProp=function(y){return Ou(m.getProp(),[y])},m.id=a.id),i.bus.$emit("p:".concat(f,":").concat(v,":").concat(h.input?1:0),r,[m,l,i.api])}})}}})}function u1e(e){return e.filter(function(t,n,r){return r.indexOf(t,0)===n})}function c1e(e){var t=e.components;if(Array.isArray(t)){var n=u1e(t.filter(function(r){return r!=="*"}));return n.length?n:!1}else return Ft.String(t)?[t]:!1}function tu(e){var t=this;ij(this,{options:function(){return e.options.value||{}},bus:function(){return e.bus},preview:function(){return e.vm.props.preview!=null?e.vm.props.preview:e.vm.setupState.parent&&e.vm.setupState.parent.props.preview!=null?e.vm.setupState.parent.props.preview:e.options.value.preview||!1}}),Nn(this,{fc:e,vm:e.vm,watching:!1,loading:!1,reloading:!1,noWatchFn:null,deferSyncFn:null,isMounted:!1,formData:mn({}),subRuleData:mn({}),subForm:{},form:mn({}),appendData:{},ignoreFields:[],providers:{},cycleLoad:null,loadedId:1,nextTick:null,changeStatus:!1,pageEnd:!0,nextReload:function(){t.lifecycle("reload")}}),this.initData(e.rules),this.$manager=new e.manager(this),this.$render=new FA(this),this.api=e.extendApiFn.reduce(function(n,r){var o=tr(function(){return r(n,t)});return o&&o!==n&&Nn(n,o),n},Uge(this))}Nn(tu.prototype,{initData:function(t){Nn(this,{ctxs:{},fieldCtx:{},nameCtx:{},sort:[],rules:t})},init:function(){this.updateAppendData(),this.useProvider(),this.usePage(),this.loadRule(),this.$manager.__init(),this.lifecycle("created")},updateAppendData:function(){this.appendData=nn(nn(nn({},this.options.formData||{}),this.fc.vm.props.modelValue||{}),this.appendData)},isBreakWatch:function(){return this.loading||this.noWatchFn||this.reloading},globalBeforeFetch:function(t){var n=this;return new Promise(function(r,o){var i=n.options.beforeFetch&&tr(function(){return n.options.beforeFetch(t,{api:n.api})});i&&Ft.Function(i.then)?i.then(r).catch(o):r()})},beforeFetch:function(t){var n=this;return new Promise(function(r,o){var i=t&&t.beforeFetch&&tr(function(){return t.beforeFetch(t,{api:n.api})});i&&Ft.Function(i.then)?i.then(r).catch(o):i===!1?o():r()}).then(function(){return n.globalBeforeFetch(t)})},beforeSubmit:function(t){var n=this;return new Promise(function(r,o){var i=n.options.beforeSubmit&&tr(function(){return n.options.beforeSubmit(t,{api:n.api})});i&&Ft.Function(i.then)?i.then(r).catch(o):i===!1?o():r()})}});qge(tu);Xge(tu);Jge(tu);e1e(tu);o1e(tu);a1e(tu);l1e(tu);s1e(tu);var d1e="fcFragment",b3=ee({name:d1e,inheritAttrs:!1,props:["vnode"],render:function(){return this.vnode}});function f1e(e){return Object.keys(e).map(function(t){var n=e[t],r=Kc(t);if(r)return[r,n.value,n.arg,n.modifiers]}).filter(function(t){return!!t})}function CO(e,t){var n=e.directives;return n?(Array.isArray(n)||(n=[n]),It(t,n.reduce(function(r,o){return r.concat(f1e(o))},[]))):t}function p1e(){var e={};function t(n){this.vm=n.vm,this.handle=n}return Nn(t.prototype,{make:function(r,o,i){return CO(o,this.h(r,vO(o),i))},makeComponent:function(r,o,i){try{return CO(o,Y(r,vO(o),i))}catch(a){return console.error(a),Y("")}},h:function(r,o,i){var a=this.vm||Kt(),l=a.appContext.config.isNativeTag(r),s=this.handle.fc.prop.components[r];!s&&l&&delete o.formCreateInject;try{return Y(s||(l?r:we(r)),o,i)}catch(u){return console.error(u),Y("")}},aliasMap:e}),Nn(t,{aliasMap:e,alias:function(r,o){e[r]=o},use:function(r){Object.keys(r).forEach(function(o){var i=Wm(o),a=Yz(o).toLocaleLowerCase(),l=r[o];t.alias(o,l),[o,i,a].forEach(function(s){t.prototype[s]=function(u,c){return this.make(e[o]||s,u,c)}})})}}),t}function h1e(e){var t=function(n){Ame(o,n);var r=Tme(o);function o(){return Eme(this,o),r.apply(this,arguments)}return o}(dj);return Object.assign(t.prototype,e),t}function dj(e){Nn(this,{$handle:e,vm:e.vm,options:{},ref:"fcForm",mergeOptionsRule:{normal:["form","row","info","submitBtn","resetBtn"]}}),this.updateKey(),this.init()}Nn(dj.prototype,{__init:function(){var t=this;this.$render=this.$handle.$render,this.$r=function(){var n;return(n=t.$render).renderRule.apply(n,arguments)}},updateKey:function(){this.key=b4()},init:function(){},update:function(){},beforeRender:function(){},form:function(){return this.vm.refs[this.ref]},adapterValidate:function(t,n){return t.validator=function(r,o,i){return n(o,i)},t},getSlot:function(t){var n=function r(o){if(o){var i=o.slots[t];return i||r(o.setupState.parent)}};return n(this.vm)},mergeOptions:function(t,n){var r=this;return Nc(t.map(function(o){return r.tidyOptions(o)}),n,this.mergeOptionsRule)},updateOptions:function(t){this.$handle.fc.targetFormDriver("updateOptions",t,{handle:this.$handle,api:this.$handle.api}),this.options=this.mergeOptions([t],this.getDefaultOptions()),this.update()},tidyOptions:function(t){return t},tidyRule:function(t){},mergeProp:function(t){},getDefaultOptions:function(){return{}},fieldChange:function(t,n,r,o){},render:function(t){}});var v1e=function(t){var n={name:"loadData",_fn:[],loaded:function(o,i,a){var l=this;this.deleted(o),vt(function(){var s=va(o.getValue()),u=[];s.forEach(function(c){if(c&&(c.attr||c.template)){var d=function(m){var y;i&&i.__fc__&&(y=i.__fc__.getParentGroup());var g;c.template?g=t.$handle.loadStrVar(c.template,m,y?{rule:i,value:t.$handle.subRuleData[y.id]||{}}:null):c.handler&&Ft.Function(c.handler)?g=c.handler(m,i,a):g=t.$handle.loadStrVar("{{".concat(c.attr,"}}"),m,y?{rule:i,value:t.$handle.subRuleData[y.id]||{}}:null),(g==null||g==="")&&c.default!=null&&(g=c.default),c.copy!==!1&&(g=ts(g));var b=c.modify?i:o.getProp();c.to==="child"?b.children?b.children[0]=g:b.children=[g]:td(b,c.to||"options",g),a.sync(i)},f=function(m){return d(m)},h=t.watchLoadData(f);d=jc(d,c.wait||300),c.watch!==!1?u.push(h):h()}}),l._fn[o.id]=u})},deleted:function(o){this._fn[o.id]&&(this._fn[o.id].forEach(function(i){i()}),delete this._fn[o.id]),o.clearProp()}};return n.watch=n.loaded,n},m1e=function(t){var n={name:"t",_fn:[],loaded:function(o,i,a){this.deleted(o);var l=o.getValue()||{},s=[];Object.keys(l).forEach(function(u){var c=l[u];if(c){var d=dr(c)==="object",f=function(y){var g=t.t(d?c.attr:c,d?c.params:null,y),b=d&&c.modify?i:o.getProp();u==="child"?b.children?b.children[0]=g:b.children=[g]:td(b,u,g),a.sync(i)},h=function(y){return f(y)},v=t.watchLoadData(h);f=jc(f,c.wait||300),c.watch!==!1?s.push(v):v()}}),this._fn[o.id]=s},deleted:function(o){this._fn[o.id]&&(this._fn[o.id].forEach(function(i){i()}),delete this._fn[o.id]),o.clearProp()}};return n.watch=n.loaded,n},U5={name:"componentValidate",load:function(t,n,r){var o=t.getValue();if(!o||o.method===!1)t.clearProp(),r.clearValidateState([n.field]);else{Ft.Object(o)||(o={method:o});var i=o.method,a=nn(nn({},o),{},{validator:function(){var s=yu(n);if(s){for(var u=arguments.length,c=new Array(u),d=0;d1?ye-1:0),Be=1;Be1&&arguments[1]!==void 0?arguments[1]:{};return L(qe,Te)};fe(Ne),J.config.globalProperties.$formCreate=Ne;var Be=D();J.component(Be.name,Be),a.forEach(function(Ge){tr(function(){return Ge(ae,J)})})}}})}if(fe(y),Q(y),B("$cookie",E1e),B("$localStorage",A1e),B("$sessionStorage",S1e),v.use({fragment:"fcFragment"}),e.install&&L.use(e),b(function(ae,le){le.mixin({props:["formCreateInject"]})}),A(b1e),e.inherit){var ie=e.inherit;ie.components&&Nn(t,ie.components),ie.parsers&&Nn(n,ie.parsers),ie.directives&&Nn(r,ie.directives),ie.modelFields&&Nn(o,ie.modelFields),ie.providers&&Nn(u,ie.providers),ie.useApps&&Nn(a,ie.useApps),ie.maker&&Nn(c,ie.maker),ie.loadData&&Nn(h,ie.loadData),ie.formulas&&Nn(m,ie.formulas)}var he=D();return q1(he,y),Object.defineProperties(he,{fetch:{get:function(){return y.fetch},set:function(le){y.fetch=le}}}),he.util=y,he}var AO={date:"YYYY-MM-DD",month:"YYYY-MM",week:"YYYY-wo",datetime:"YYYY-MM-DD HH:mm:ss",timerange:"HH:mm:ss",daterange:"YYYY-MM-DD",monthrange:"YYYY-MM",datetimerange:"YYYY-MM-DD HH:mm:ss",year:"YYYY"},SO="datePicker",D1e={name:SO,maker:function(){return["year","month","date","dates","week","datetime","datetimeRange","dateRange","monthRange"].reduce(function(e,t){return e[t]=Mi(SO,{type:t.toLowerCase()}),e},{})}(),mergeProp:function(t){var n=t.prop.props;n.valueFormat||(n.valueFormat=AO[n.type]||AO.date)}},x3="hidden",N1e={name:x3,maker:go({},x3,function(e,t){return Mi(x3)("",e,t)}),render:function(){return[]}},C3="input",F1e={name:C3,maker:function(){var e=["password","url","email","text","textarea"].reduce(function(t,n){return t[n]=Mi(C3,{type:n}),t},{});return e.idate=Mi(C3,{type:"date"}),e}(),mergeProp:function(t){var n=t.prop.props;n&&n.autosize&&n.autosize.minRows&&(n.rows=n.autosize.minRows||2)}},R1e="textarea",P1e={name:R1e,mergeProp:function(t){var n=t.prop.props;n.type="textarea",n&&n.autosize&&n.autosize.minRows&&(n.rows=n.autosize.minRows||2)}},kO="slider",I1e={name:kO,maker:{sliderRange:Mi(kO,{range:!0})},toFormValue:function(t,n){var r=Array.isArray(t),o=n.prop.props,i=o.min||0,a;return o.range===!0?a=r?t:[i,parseFloat(t)||i]:a=r?parseFloat(t[0])||i:parseFloat(t),a}},E3="timePicker",L1e={name:E3,maker:{time:Mi(E3,function(e){return e.props.isRange=!1}),timeRange:Mi(E3,function(e){return e.props.isRange=!0})},mergeProp:function(t){var n=t.prop.props;n.valueFormat||(n.valueFormat="HH:mm:ss")}},B1e={name:"FcRow",render:function(t,n){return n.vNode.col({props:{span:24}},{default:function(){return[n.vNode.row(n.prop,t)]}})}},$1e="select",z1e={name:$1e,toFormValue:function(t,n){return n.prop.props.multiple&&!Array.isArray(t)?va(t):t}},j1e=[D1e,N1e,F1e,P1e,I1e,L1e,B1e,z1e],io="el",V1e={button:io+"-button",icon:io+"-icon",slider:io+"-slider",rate:io+"-rate",upload:"fc-upload",cascader:io+"-cascader",popover:io+"-popover",tooltip:io+"-tooltip",colorPicker:io+"-colorPicker",timePicker:io+"-time-picker",timeSelect:io+"-time-select",datePicker:io+"-date-picker",switch:io+"-switch",select:"fc-select",checkbox:"fc-checkbox",radio:"fc-radio",inputNumber:io+"-input-number",number:io+"-input-number",input:io+"-input",textarea:io+"-input",formItem:io+"-form-item",form:io+"-form",frame:"fc-frame",col:io+"-col",row:io+"-row",tree:"fc-tree",autoComplete:io+"-autocomplete",auto:io+"-autocomplete",group:"fc-group",array:"fc-group",object:"fc-sub-form",subForm:"fc-sub-form"};function H1e(){return{form:{inline:!1,labelPosition:"right",labelWidth:"125px",disabled:!1,size:void 0},row:{show:!0,gutter:0},submitBtn:{type:"primary",loading:!1,disabled:!1,innerText:"",show:!0,col:void 0,click:void 0},resetBtn:{type:"default",loading:!1,disabled:!1,innerText:"",show:!1,col:void 0,click:void 0}}}function W1e(e){return e.type==="tooltip"}function MO(e,t){if(no(e,t)&&Ft.String(e[t])){var n;e[t]=(n={},go(n,t,e[t]),go(n,"show",!0),n)}}function pu(e){return e===!1}function U1e(e,t){no(e,t)&&!Ft.Object(e[t])&&(e[t]={show:!!e[t]})}function A3(e){var t=nn({},e);return delete t.children,t}var K1e={validate:function(){var t=this.form();return t?t.validate():new Promise(function(n){return n()})},validateField:function(t){var n=this;return new Promise(function(r,o){var i=n.form();i?i.validateField(t,function(a,l){l?o(l):r(a)}):r()})},clearValidateState:function(t){var n=this.vm.refs[t.wrapRef];n&&n.clearValidate()},fieldChange:function(t,n,r,o){if(!o){var i,a;(i=this.vm.refs[t.wrapRef])===null||i===void 0||(a=i.validate("change"))===null||a===void 0||a.catch(function(){})}},tidyOptions:function(t){return["submitBtn","resetBtn","row","info","wrap","col","title"].forEach(function(n){U1e(t,n)}),t},tidyRule:function(t){var n=t.prop;return MO(n,"title"),MO(n,"info"),n},mergeProp:function(t){var n=this,r={info:{trigger:"hover",placement:"top-start",icon:!0},title:{},col:{span:24},wrap:{}};["info","wrap","col","title"].forEach(function(o){t.prop[o]=Nc([n.options[o]||{},t.prop[o]||{}],r[o])})},getDefaultOptions:function(){return H1e()},update:function(){var t=this.options.form;this.rule={props:nn({},t),on:{submit:function(r){r.preventDefault()}},style:t.style,type:"form"}},beforeRender:function(){var t=this.key,n=this.ref,r=this.$handle,o=this.options.form;Nn(this.rule,{key:t,ref:n,class:[o.className,o.class,"form-create",this.$handle.preview?"is-preview":""]}),Nn(this.rule.props,{model:r.formData})},render:function(t){var n=this;return t.slotLen()&&!this.$handle.preview&&t.setSlot(void 0,function(){return n.makeFormBtn()}),this.$r(this.rule,pu(this.options.row.show)?t.getSlots():[this.makeRow(t)])},makeWrap:function(t,n){var r=this,o=t.prop,i="".concat(this.key).concat(t.key),a=o.col,l=this.isTitle(o)&&o.wrap.title!==!1,s=!a.labelWidth&&!l?0:a.labelWidth,u=this.rule.props,c=u.inline,d=u.col,f=o.wrap.class;delete o.wrap.class,delete o.wrap.title;var h=pu(o.wrap.show)?n:this.$r(Nc([o.wrap,{props:nn(nn({labelWidth:s===void 0?s:Yz(s),label:l?o.title.title:void 0},A3(o.wrap||{})),{},{prop:t.id,rules:t.injectValidate()}),class:this.$render.mergeClass(f||o.className,"fc-form-item"),key:"".concat(i,"fi"),ref:t.wrapRef,type:"formItem"}]),nn({default:function(){return n}},l?{label:function(){return r.makeInfo(o,i,t)}}:{}));return c===!0||pu(d)||pu(a.show)?h:this.makeCol(o,i,[h])},isTitle:function(t){if(this.options.form.title===!1)return!1;var n=t.title;return!(!n.title&&!n.native||pu(n.show))},makeInfo:function(t,n,r){var o,i,a,l,s=this,u=nn({},t.title),c=nn({},t.info),d=W1e(c),f=this.options.form,h=this.getSlot("title"),v=[h?h({title:(o=r.refRule)===null||o===void 0||(i=o.__$title)===null||i===void 0?void 0:i.value,rule:r.rule,options:this.options}):((a=r.refRule)===null||a===void 0||(l=a.__$title)===null||l===void 0?void 0:l.value)+(f.labelSuffix||f["label-suffix"]||"")];if(!pu(c.show)&&(c.info||c.native)&&!pu(c.icon)){var m={type:c.type||"popover",props:A3(c),key:"".concat(n,"pop")};delete m.props.icon,delete m.props.show,delete m.props.info,delete m.props.align,delete m.props.native;var y="content";if(c.info&&!no(m.props,y)){var g,b;m.props[y]=(g=r.refRule)===null||g===void 0||(b=g.__$info)===null||b===void 0?void 0:b.value}v[c.align!=="left"?"unshift":"push"](this.$r(Nc([c,m]),go({},u.slot||(d?"default":"reference"),function(){return s.$r({type:"ElIcon",style:"top:2px",key:"".concat(n,"i")},{default:function(){return s.$r({type:c.icon===!0?"icon-warning":c.icon})}},!0)})))}var x=Nc([u,{props:A3(u),key:"".concat(n,"tit"),class:"fc-form-title",type:u.type||"span"}]);return delete x.props.show,delete x.props.title,delete x.props.native,this.$r(x,v)},makeCol:function(t,n,r){var o=t.col;return this.$r({class:this.$render.mergeClass(o.class,"fc-form-col"),type:"col",props:o||{span:24},key:"".concat(n,"col")},r)},makeRow:function(t){var n=this.options.row||{};return this.$r({type:"row",props:n,class:this.$render.mergeClass(n.class,"fc-form-row"),key:"".concat(this.key,"row")},t)},makeFormBtn:function(){var t=[];if(pu(this.options.submitBtn.show)||t.push(this.makeSubmitBtn()),pu(this.options.resetBtn.show)||t.push(this.makeResetBtn()),!!t.length){var n=this.$r({type:"formItem",class:"fc-form-item",key:"".concat(this.key,"fb")},t);return this.rule.props.inline===!0?n:this.$r({type:"col",class:"fc-form-col fc-form-footer",props:{span:24},key:"".concat(this.key,"fc")},[n])}},makeResetBtn:function(){var t=this,n=nn({},this.options.resetBtn),r=n.innerText||this.$handle.api.t("reset")||"重置";return delete n.innerText,delete n.click,delete n.col,delete n.show,this.$r({type:"button",props:n,class:"fc-reset-btn",style:{width:n.width},on:{click:function(){var i=t.$handle.api;t.options.resetBtn.click?t.options.resetBtn.click(i):i.resetFields()}},key:"".concat(this.key,"b2")},[r])},makeSubmitBtn:function(){var t=this,n=nn({},this.options.submitBtn),r=n.innerText||this.$handle.api.t("submit")||"提交";return delete n.innerText,delete n.click,delete n.col,delete n.show,this.$r({type:"button",props:n,class:"fc-submit-btn",style:{width:n.width},on:{click:function(){var i=t.$handle.api;t.options.submitBtn.click?t.options.submitBtn.click(i):i.submit().catch(function(){})}},key:"".concat(this.key,"b1")},[r])}},Ff={};Y1e(Ff);G1e(Ff);q1e(Ff);X1e(Ff);Q1e(Ff);function Y1e(e){["group","tree","switch","upload","autoComplete","checkbox","cascader","colorPicker","datePicker","frame","inputNumber","radio","rate"].forEach(function(t){e[t]=Mi(t)}),e.auto=e.autoComplete,e.number=e.inputNumber,e.color=e.colorPicker}function G1e(e){var t="select",n="multiple";e.selectMultiple=Mi(t,go({},n,!0)),e.selectOne=Mi(t,go({},n,!1))}function q1e(e){var t="tree",n={treeSelected:"selected",treeChecked:"checked"};Object.keys(n).reduce(function(r,o){return r[o]=Mi(t,{type:n[o]}),r},e)}function X1e(e){var t="upload",n={image:["image",0],file:["file",0],uploadFileOne:["file",1],uploadImageOne:["image",1]};Object.keys(n).reduce(function(r,o){return r[o]=Mi(t,function(i){return i.props({uploadType:n[o][0],maxLength:n[o][1]})}),r},e),e.uploadImage=e.image,e.uploadFile=e.file}function Q1e(e){var t={frameInputs:["input",0],frameFiles:["file",0],frameImages:["image",0],frameInputOne:["input",1],frameFileOne:["file",1],frameImageOne:["image",1]};return Object.keys(t).reduce(function(n,r){return n[r]=Mi("frame",function(o){return o.props({type:t[r][0],maxLength:t[r][1]})}),n},e),e.frameInput=e.frameInputs,e.frameFile=e.frameFiles,e.frameImage=e.frameImages,e}var J1e=".form-create .form-create .el-form-item{margin-bottom:22px}.form-create{width:100%}.form-create .fc-none,.form-create.is-preview .el-form-item.is-required>.el-form-item__label-wrap>.el-form-item__label:before,.form-create.is-preview .el-form-item.is-required>.el-form-item__label:before,.form-create.is-preview .fc-clock{display:none!important}.fc-wrap-right>.el-form-item__label{display:flex;justify-content:flex-end}.fc-wrap-left>.el-form-item__label{display:flex;justify-content:flex-start}.fc-wrap-top.el-form-item{display:block}.fc-wrap-top.el-form-item>.el-form-item__label{display:block;height:auto;line-height:22px;margin-bottom:8px;text-align:left}.el-form--large .fc-wrap-top.el-form-item>.el-form-item__label{line-height:22px;margin-bottom:12px}.el-form--default .fc-wrap-top.el-form-item>.el-form-item__label{line-height:22px;margin-bottom:8px}.el-form--small .fc-wrap-top.el-form-item>.el-form-item__label{line-height:20px;margin-bottom:4px}.fc-form-footer{margin-top:12px}";_4(J1e);function TO(e,t){return Ft.Boolean(e)?e={show:e}:!Ft.Undef(e)&&!Ft.Object(e)&&(e={show:t}),e}function Z1e(e,t){return{formEl:function(){return t.$manager.form()},wrapEl:function(r){var o=t.getFieldCtx(r);if(o)return t.vm.refs[o.wrapRef]},validate:function(r){return new Promise(function(o,i){var a=e.children,l=[t.$manager.validate()];a.filter(function(s){return!s.isScope}).forEach(function(s){l.push(s.validate())}),Promise.all(l).then(function(){o(!0),r&&r(!0)}).catch(function(s){i(s),r&&r(s),t.vm.emit("validate-fail",s,{api:e}),t.emitEvent("validate-fail",s,{api:e})})})},validateField:function(r,o){return new Promise(function(i,a){var l=t.getFieldCtx(r);if(l){var s=t.subForm[l.id],u=[t.$manager.validateField(l.id)];va(s).filter(function(c){return!c.isScope}).forEach(function(c){u.push(c.validate())}),Promise.all(u).then(function(){i(null),o&&o(null)}).catch(function(c){a(c),o&&o(c),t.vm.emit("validate-field-fail",c,{field:r,api:e})})}})},clearValidateState:function(r){var o=this,i=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!0;e.helper.tidyFields(r).forEach(function(a){i&&o.clearSubValidateState(a),t.getCtxs(a).forEach(function(l){t.$manager.clearValidateState(l)})})},clearSubValidateState:function(r){e.helper.tidyFields(r).forEach(function(o){t.getCtxs(o).forEach(function(i){var a=t.subForm[i.id];a&&(Array.isArray(a)?a.forEach(function(l){l.clearValidateState()}):a&&a.clearValidateState())})})},btn:{loading:function(){var r=arguments.length>0&&arguments[0]!==void 0?arguments[0]:!0;e.submitBtnProps({loading:!!r})},disabled:function(){var r=arguments.length>0&&arguments[0]!==void 0?arguments[0]:!0;e.submitBtnProps({disabled:!!r})},show:function(){var r=arguments.length>0&&arguments[0]!==void 0?arguments[0]:!0;e.submitBtnProps({show:!!r})}},resetBtn:{loading:function(){var r=arguments.length>0&&arguments[0]!==void 0?arguments[0]:!0;e.resetBtnProps({loading:!!r})},disabled:function(){var r=arguments.length>0&&arguments[0]!==void 0?arguments[0]:!0;e.resetBtnProps({disabled:!!r})},show:function(){var r=arguments.length>0&&arguments[0]!==void 0?arguments[0]:!0;e.resetBtnProps({show:!!r})}},submitBtnProps:function(){var r=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},o=TO(t.options.submitBtn,!0);Nn(o,r),t.options.submitBtn=o,e.refreshOptions()},resetBtnProps:function(){var r=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},o=TO(t.options.resetBtn,!1);Nn(o,r),t.options.resetBtn=o,e.refreshOptions()},submit:function(r,o){return new Promise(function(i,a){var l=t.options.validateOnSubmit===!1?Promise.resolve():e.validate();l.then(function(){var s=e.formData();t.beforeSubmit(s).then(function(){Ft.Function(r)&&tr(function(){return r(s,e)}),Ft.Function(t.options.onSubmit)&&tr(function(){return t.options.onSubmit(s,e)}),t.vm.emit("submit",s,e),i(s)}).catch(function(u){})}).catch(function(){for(var s=arguments.length,u=new Array(s),c=0;cspan::selection,.cm-fat-cursor .CodeMirror-line>span>span::selection{background:transparent}.cm-fat-cursor .CodeMirror-line::-moz-selection,.cm-fat-cursor .CodeMirror-line>span::-moz-selection,.cm-fat-cursor .CodeMirror-line>span>span::-moz-selection{background:transparent}.cm-fat-cursor{caret-color:transparent}.cm-tab{display:inline-block;text-decoration:inherit}.CodeMirror-rulers{bottom:0;left:0;overflow:hidden;position:absolute;right:0;top:-50px}.CodeMirror-ruler{border-left:1px solid #ccc;bottom:0;position:absolute;top:0}.cm-s-default .cm-header{color:#00f}.cm-s-default .cm-quote{color:#090}.cm-negative{color:#d44}.cm-positive{color:#292}.cm-header,.cm-strong{font-weight:700}.cm-em{font-style:italic}.cm-link{text-decoration:underline}.cm-strikethrough{text-decoration:line-through}.cm-s-default .cm-keyword{color:#708}.cm-s-default .cm-atom{color:#219}.cm-s-default .cm-number{color:#164}.cm-s-default .cm-def{color:#00f}.cm-s-default .cm-variable-2{color:#05a}.cm-s-default .cm-type,.cm-s-default .cm-variable-3{color:#085}.cm-s-default .cm-comment{color:#a50}.cm-s-default .cm-string{color:#a11}.cm-s-default .cm-string-2{color:#f50}.cm-s-default .cm-meta,.cm-s-default .cm-qualifier{color:#555}.cm-s-default .cm-builtin{color:#30a}.cm-s-default .cm-bracket{color:#997}.cm-s-default .cm-tag{color:#170}.cm-s-default .cm-attribute{color:#00c}.cm-s-default .cm-hr{color:#999}.cm-s-default .cm-link{color:#00c}.cm-invalidchar,.cm-s-default .cm-error{color:red}.CodeMirror-composing{border-bottom:2px solid}div.CodeMirror span.CodeMirror-matchingbracket{color:#0b0}div.CodeMirror span.CodeMirror-nonmatchingbracket{color:#a22}.CodeMirror-matchingtag{background:rgba(255,150,0,.3)}.CodeMirror-activeline-background{background:#e8f2ff}.CodeMirror{background:#fff;overflow:hidden;position:relative}.CodeMirror-scroll{height:100%;margin-bottom:-50px;margin-right:-50px;outline:none;overflow:scroll!important;padding-bottom:50px;position:relative;z-index:0}.CodeMirror-sizer{border-right:50px solid transparent;position:relative}.CodeMirror-gutter-filler,.CodeMirror-hscrollbar,.CodeMirror-scrollbar-filler,.CodeMirror-vscrollbar{display:none;outline:none;position:absolute;z-index:5}.CodeMirror-vscrollbar{overflow-x:hidden;overflow-y:scroll;right:0;top:0}.CodeMirror-hscrollbar{bottom:0;left:0;overflow-x:scroll;overflow-y:hidden}.CodeMirror-scrollbar-filler{bottom:0;right:0}.CodeMirror-gutter-filler{bottom:0;left:0}.CodeMirror-gutters{left:0;min-height:100%;position:absolute;top:0;z-index:3}.CodeMirror-gutter{display:inline-block;height:100%;margin-bottom:-50px;vertical-align:top;white-space:normal}.CodeMirror-gutter-wrapper{background:none!important;border:none!important;position:absolute;z-index:4}.CodeMirror-gutter-background{bottom:0;position:absolute;top:0;z-index:4}.CodeMirror-gutter-elt{cursor:default;position:absolute;z-index:4}.CodeMirror-gutter-wrapper ::selection{background-color:transparent}.CodeMirror-gutter-wrapper ::-moz-selection{background-color:transparent}.CodeMirror-lines{cursor:text;min-height:1px}.CodeMirror pre.CodeMirror-line,.CodeMirror pre.CodeMirror-line-like{word-wrap:normal;-webkit-tap-highlight-color:transparent;background:transparent;border-radius:0;border-width:0;color:inherit;font-family:inherit;font-size:inherit;font-variant-ligatures:contextual;line-height:inherit;margin:0;overflow:visible;position:relative;white-space:pre;z-index:2}.CodeMirror-wrap pre.CodeMirror-line,.CodeMirror-wrap pre.CodeMirror-line-like{word-wrap:break-word;white-space:pre-wrap;word-break:normal}.CodeMirror-linebackground{bottom:0;left:0;position:absolute;right:0;top:0;z-index:0}.CodeMirror-linewidget{padding:.1px;position:relative;z-index:2}.CodeMirror-rtl pre{direction:rtl}.CodeMirror-code{outline:none}.CodeMirror-gutter,.CodeMirror-gutters,.CodeMirror-linenumber,.CodeMirror-scroll,.CodeMirror-sizer{box-sizing:content-box}.CodeMirror-measure{height:0;overflow:hidden;position:absolute;visibility:hidden;width:100%}.CodeMirror-cursor{pointer-events:none;position:absolute}.CodeMirror-measure pre{position:static}div.CodeMirror-cursors{position:relative;visibility:hidden;z-index:3}.CodeMirror-focused div.CodeMirror-cursors,div.CodeMirror-dragcursors{visibility:visible}.CodeMirror-selected{background:#d9d9d9}.CodeMirror-focused .CodeMirror-selected{background:#d7d4f0}.CodeMirror-crosshair{cursor:crosshair}.CodeMirror-line::selection,.CodeMirror-line>span::selection,.CodeMirror-line>span>span::selection{background:#d7d4f0}.CodeMirror-line::-moz-selection,.CodeMirror-line>span::-moz-selection,.CodeMirror-line>span>span::-moz-selection{background:#d7d4f0}.cm-searching{background-color:#ffa;background-color:#ff06}.cm-force-border{padding-right:.1px}@media print{.CodeMirror div.CodeMirror-cursors{visibility:hidden}}.cm-tab-wrap-hack:after{content:""}span.CodeMirror-selectedtext{background:none}.CodeMirror-hints{background:#fff;border:1px solid silver;border-radius:3px;box-shadow:2px 3px 5px #0003;font-family:monospace;font-size:90%;list-style:none;margin:0;max-height:20em;overflow:hidden;overflow-y:auto;padding:2px;position:absolute;z-index:1}.CodeMirror-hint{border-radius:2px;color:#000;cursor:pointer;margin:0;padding:0 4px;white-space:pre}li.CodeMirror-hint-active{background:#08f;color:#fff}._fd-fn{display:flex;flex-direction:column;height:100%;position:relative;width:100%}._fd-fn .el-button{bottom:3px;box-shadow:0 6px 16px #00000014,0 3px 6px -4px #0000001f,0 9px 28px 8px #0000000d;position:absolute;right:5px}._fd-fn-editor{display:flex;flex:1;overflow:auto;width:100%}._fd-fn-editor .CodeMirror{height:100%;width:100%}._fd-fn-tip{color:#000;direction:ltr;font-family:monospace}._fd-fn-tip .cm-keyword{color:#708;line-height:24px;overflow-x:auto;white-space:nowrap}._fd-fn-tip .cm-keyword::-webkit-scrollbar{background-color:transparent;height:0;width:0}._fd-fn-ind{background-color:#f7f7f7;border-right:1px solid #ddd;display:inline-block;float:left;height:24px;margin-right:4px;width:29px}._fd-fn-arg{cursor:pointer;text-decoration:underline}._fd-fn-arg i{color:#3073ff;font-size:12px}._fd-event .el-button{border-color:#2e73ff;color:#2e73ff;font-weight:400;width:100%}._fd-event .el-badge{width:100%}._fd-event-dialog .el-dialog__body{padding:10px 20px}._fd-event-con .el-main{padding:0}._fd-event-l,._fd-event-r{border:1px solid #ececec;display:flex;flex:1;flex-direction:column;height:100%}._fd-event-dropdown .el-dropdown-menu{max-height:500px;overflow:auto}._fd-event-head{align-items:center;background:#f8f9ff;border-bottom:1px solid #eee;display:flex;padding:5px 15px}._fd-event-head .el-button.is-link{color:#2f73ff}._fd-event-r{border-left:0}._fd-event-r ._fd-event-head{justify-content:space-between}._fd-event-l>.el-main,._fd-event-r>.el-main{box-sizing:border-box;display:flex;flex:1;flex-basis:auto;flex-direction:row;min-width:0;width:100%}._fd-event-item,._fd-event-r>.el-main{flex-direction:column}._fd-event-item{display:flex;font-size:14px;justify-content:center;max-width:250px;overflow:hidden;white-space:pre-wrap}._fd-event-item ._fd-label{color:#aaa;font-size:12px}._fd-event-l .el-menu{border-right:0;border-top:0;overflow:auto;padding:0 10px 5px;width:100%}._fd-event-l .el-menu-item.is-active{background:#e4e7ed;color:#303133}._fd-event-l .el-menu-item{border:1px solid #ececec;border-radius:5px;height:auto;line-height:1em;margin-top:5px;padding:0}._fd-event-method{color:#9d238c;display:flex;flex-direction:column;font-family:monospace;font-size:14px;justify-content:center;overflow:hidden;white-space:pre-wrap;width:225px}._fd-event-method ._fd-label{color:#aaa;font-size:12px;margin-top:4px}._fd-event-method>span:first-child,._fd-fn-list-method>span:first-child{color:#9d238c}._fd-event-method>span:first-child>span,._fd-fn-list-method>span:first-child>span{color:#000;margin-left:10px}._fd-event-title{align-items:center;display:flex;justify-content:space-between;padding:10px 0;width:100%}._fd-event-title .fc-icon{color:#282828;font-size:18px;margin-right:6px}._fd-event-title .el-input{width:200px}._fd-event-title .el-input__wrapper{box-shadow:none}._fd-event-title .el-menu-item.is-active i{color:#282828}._fd-event-con .CodeMirror{height:100%;width:100%}._fd-event-con .CodeMirror-wrap pre.CodeMirror-line{padding-left:20px}._fd-type-select{cursor:pointer}._fd-type-select.is-disabled{cursor:default}._fd-type-select .fc-icon{font-size:14px}._fd-type-select-pop{max-height:500px;overflow:auto}._fd-type-select-pop .fc-icon{font-size:14px}._fd-struct,._fd-struct .el-badge{width:100%}._fd-struct .el-button{border-color:#2e73ff;color:#2e73ff;font-weight:400;width:100%}._fd-struct-con .CodeMirror{height:500px}._fd-struct-con .el-dialog__body{padding:0}._fd-props-input{display:inline-block;width:16px}._fd-props-input .fc-icon{cursor:pointer}._fd-language-config{height:100%;overflow:auto}._fd-lc-body,._fd-lc-header{padding:0 12px}._fd-lc-body{overflow:auto}._fd-lc-header{display:flex;justify-content:flex-end;margin-bottom:12px}._fd-language-config .el-table__cell{height:34px}._fd-language-config ._fc-l-info{margin-bottom:12px}._fd-lc-handle{align-items:center;cursor:pointer;display:flex;justify-content:space-between}._fd-struct-editor{flex:1;width:100%}._fd-struct-editor>div{height:100%}._fc-json-preview{color:#262626;display:flex;width:100%}._fc-json-preview .CodeMirror{font-size:12px;height:100%}._fd-warning-pop{max-width:400px}._fd-warning-text{cursor:help;text-decoration:underline;text-decoration-style:dashed}._fd-drag-tool{word-wrap:break-word;box-sizing:border-box;display:block;min-height:20px;outline:1px dashed var(--fc-tool-border-color);overflow:hidden;padding:2px;position:relative;word-break:break-all;z-index:0}._fd-drag-tool ._fd-drag-tool{height:calc(100% - 6px);margin:3px}._fd-drag-tool+._fd-drag-tool{margin-top:5px}._fd-drag-tool.active{outline:1px solid #2e73ff}._fd-drag-tool.active>div>._fd-drag-btn{display:flex}._fd-drag-tool:not(.active):hover>div>._fd-drag-btn{display:flex!important;opacity:.7}._fd-drag-tool._fd-drop-hover ._fd-drag-box{padding-bottom:15px!important;padding-top:15px!important}._fd-drag-tool ._fd-drag-btn{display:none}._fd-drag-r{right:2px;top:calc(100% - 20px)}._fd-drag-l,._fd-drag-r{position:absolute;z-index:3}._fd-drag-l{left:0;top:0}._fd-drag-btn{background-color:#2e73ff;color:#fff;cursor:pointer;float:left;height:18px;justify-content:center;line-height:20px;padding-bottom:1px;text-align:center;width:18px}._fd-drag-btn+._fd-drag-btn{margin-left:2px}._fd-drag-danger{background-color:#ff2e2e}._fd-drag-btn i{font-size:14px}._fd-drag-mask{bottom:0;left:0;position:absolute;right:0;top:0;z-index:2}._fd-drag-tool:hover{outline-color:#2e73ff80;outline-style:solid;z-index:1}._fd-drag-tool:has(._fd-drag-tool:not(.active):hover,._fd-drag-tool.active:hover)>div>._fd-drag-btn{display:none!important}._fd-row,._fd-html-editor{width:100%}._fd-html-editor .el-button{border-color:#2e73ff;color:#2e73ff;font-weight:400;width:100%}._fd-html-editor-con .CodeMirror{height:450px}._fd-html-editor-con .CodeMirror-line{font-size:13px!important;line-height:16px!important}._fd-html-editor-con .CodeMirror-lint-tooltip{z-index:1!important}._fd-html-editor-con .el-dialog__body{padding:0 20px}._fd-fn-input,._fd-fn-input .el-badge{width:100%}._fd-fn-input .el-button{border-color:#2e73ff;color:#2e73ff;font-weight:400;width:100%}._fd-fn-input-dialog .CodeMirror-lint-tooltip{z-index:1!important}._fd-fn-input-dialog .el-dialog__body{height:500px;padding:0}._fd-gfc,._fd-gfc .el-badge{width:100%}._fd-gfc .el-button{border-color:#2e73ff;color:#2e73ff;font-weight:400;width:100%}._fd-gfc-dialog .el-tabs__header{margin-bottom:0}._fd-gfc-dialog .form-create{margin-top:15px}._fd-gfc-con .CodeMirror{height:100%;width:100%}._fd-gfc-con .CodeMirror-wrap pre.CodeMirror-line{padding-left:20px}._fd-config-item{color:#666;display:flex;flex-direction:column;font-size:12px;margin-bottom:10px;width:100%}._fd-ci-head{justify-content:space-between}._fd-ci-head,._fd-ci-label{align-items:center;display:flex}._fd-ci-label{color:#262626;font-size:12px}._fd-ci-con{align-items:center;display:flex;justify-content:flex-end;min-width:150px}._fd-ci-label.is-arrow{cursor:pointer}._fd-ci-append{background:#f5f5f5;display:flex;flex-direction:column;margin:5px 3px 3px;padding:4px}._fd-ci-label i{font-size:12px;font-weight:600}._fd-ci-label i.down{transform:rotate(-180deg)}._fd-ci-info{font-size:12px;padding-right:5px}._fd-field-input{width:100%}._fd-field-input>.fc-icon{color:#a8abb2;cursor:pointer;height:24px;position:absolute;right:28px;text-align:center;top:1px;width:24px;z-index:1}._fd-field-input>.fc-icon:hover{color:#2e73ff}._fd-field-input .el-input-group__append{color:#606266;cursor:pointer;margin:0;padding:0;width:25px}._fd-fn-list,._fd-fn-list .el-badge{width:100%}._fd-fn-list .el-button{border-color:#2e73ff;color:#2e73ff;font-weight:400;width:100%}._fd-fn-list-dialog .el-dialog__body{padding:10px 20px}._fd-fn-list-con .el-main{padding:0}._fd-fn-list-l,._fd-fn-list-r{border:1px solid #ececec;display:flex;flex:1;flex-direction:column;height:100%}._fd-fn-list-head{align-items:center;background:#f8f9ff;border-bottom:1px solid #eee;display:flex;padding:5px 15px}._fd-fn-list-head .el-button.is-link{color:#2f73ff}._fd-fn-list-r{border-left:0}._fd-fn-list-r ._fd-fn-list-head{justify-content:flex-end}._fd-fn-list-l>.el-main,._fd-fn-list-r>.el-main{box-sizing:border-box;display:flex;flex:1;flex-basis:auto;flex-direction:row;min-width:0;width:100%}._fd-fn-list-r>.el-main{flex-direction:column}._fd-fn-list-l .el-menu{border-right:0;border-top:0;overflow:auto;padding:0 10px 5px;width:100%}._fd-fn-list-l .el-menu-item.is-active{background:#e4e7ed;color:#303133}._fd-fn-list-l .el-menu-item{border:1px solid #ececec;border-radius:5px;height:auto;line-height:1em;margin-top:5px;padding:0}._fd-fn-list-method{box-sizing:border-box;display:flex;flex-direction:column;font-family:monospace;font-size:14px;justify-content:center;line-height:1em;overflow:hidden;padding:10px 20px 10px 0;position:relative;white-space:pre-wrap;width:100%}._fd-fn-list-method ._fd-label{color:#aaa;font-size:12px;margin-top:4px}._fd-fn-list-method ._fd-dot{background:#00c050;border-radius:15px;display:block;height:6px;margin-top:-3px;position:absolute;right:16px;top:50%;width:6px}._fd-fn-list-method-info>span:first-child,._fd-fn-list-method>span:first-child{color:#9d238c}._fd-fn-list-method-info>span:first-child>span,._fd-fn-list-method>span:first-child>span{color:#000;margin-left:10px}._fd-fn-list-con .CodeMirror{height:100%;width:100%}._fd-fn-list-con .CodeMirror-wrap pre.CodeMirror-line{padding-left:20px}._fd-table-view{overflow:auto}._fd-table-view-cell{height:100%;min-height:50px;position:relative}._fd-table-row-handle{bottom:-4px;cursor:row-resize;height:8px;left:0;width:100%}._fd-table-col-handle,._fd-table-row-handle{background-color:transparent;position:absolute;z-index:1}._fd-table-col-handle{cursor:col-resize;height:100%;right:-4px;top:0;width:8px}._fd-table-view-cell.selected>._fd-drag-tool{background-color:#2e73ff0d;outline:1px solid #2e73ff}._fd-table-view-cell>._fd-drag-tool{border:0;height:100%;margin:0;max-height:100%;max-width:100%;min-width:unset;outline:0;width:100%}._fd-table-view-cell>._fd-drag-tool>._fd-tableCell-drag{min-height:80px}._fd-table-view-btn{flex-direction:column;padding:0}._fd-table-view-btn .fc-icon{color:#fff;font-size:16px;width:18px}._fd-table-view-icon{color:#fff;display:flex;height:100%;justify-content:center;margin-top:1px;width:100%}._fd-table-view>table{border-bottom:1px solid #ebeef5;border-collapse:collapse;border-right:1px solid #ebeef5;border-color:#ebeef5 currentcolor currentcolor #ebeef5;border-style:solid none none solid;border-width:1px 0 0 1px;height:100%;overflow:hidden;table-layout:fixed;width:100%}._fd-table-view tr{min-height:50px}._fd-table-view td{border:1px solid #ebeef5;box-sizing:border-box;min-height:50px;min-width:80px;overflow-wrap:break-word;padding:0;position:relative;white-space:nowrap}._fd-tableCell-drag{height:100%}._fd-table-view.is-mini td{min-height:12px;padding:0}._fd-table-view.is-mini .el-form-item{margin:0;padding:0}._fd-table-context-menu{background-color:#fff;border:1px solid #ddd;border-radius:6px;box-shadow:0 1px 10px #0000001a;color:#666;display:block;font-size:14px;min-width:120px;padding:6px 0;position:fixed;z-index:2}._fd-table-context-menuitem{cursor:pointer;line-height:22px;padding:5px 16px}._fd-table-context-menuitem:hover{background-color:#f0f0f0}._fd-table-context-menu-separator{background-color:#eee;height:1px;margin:6px 0}._fc-table{overflow:auto}._fc-table>table{border-bottom:1px solid #ebeef5;border-collapse:collapse;border-right:1px solid #ebeef5;border-color:#ebeef5 currentcolor currentcolor #ebeef5;border-style:solid none none solid;border-width:1px 0 0 1px;height:100%;overflow:hidden;table-layout:fixed;width:100%}._fc-table td,._fc-table tr{min-height:50px}._fc-table td{border-bottom:0;border-right:0;border-color:currentcolor #ebeef5 #ebeef5 currentcolor;border-style:none solid solid none;border-width:0 1px 1px 0;box-sizing:border-box;min-width:80px;overflow:hidden;overflow-wrap:break-word;padding:5px;position:relative}._fc-table.is-mini td{min-height:12px;padding:0}._fc-table.is-mini .el-form-item{margin:0;padding:0}._fd-language-list{max-height:320px;overflow:auto;padding-top:70px}._fd-language-input .el-input-group__append{color:#aaa;cursor:pointer;margin:0;padding:0;width:25px}._fd-language-input.is-variable input{color:#2e73ff}._fd-language-header,._fd-language-item{border-bottom:1px solid #ececec;display:flex;padding:0 12px}._fd-language-header{background-color:#fff;color:#262626;flex-direction:column;font-weight:500;left:0;overflow:auto;padding-top:10px;position:absolute;right:0;top:0}._fd-language-item>div,._fd-language-name>div{flex:1;font-size:12px;min-width:70px;padding:5px}._fd-language-title{margin:6px 0}._fd-language-title .fc-icon{color:#2e73ff;cursor:pointer;font-size:14px}._fd-language-name{display:flex}._fd-language-name>div{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}._fd-language-item{cursor:pointer}._fd-language-item:hover{background-color:#ccdfff;color:#2e73ff}._fd-language-popover{padding:0!important}._fd-validate{display:flex;flex-direction:column;width:100%}._fd-validate-btn{border-color:#2e73ff;color:#2e73ff;font-weight:400;width:100%}._fd-validate-pop .el-dropdown-menu__item{width:248px}._fd-validate-item{border-bottom:1px dashed #ececec;margin-bottom:10px}._fd-validate-item .el-col-12:first-child{padding-right:5px}._fd-validate-item .el-col-12+.el-col-12{padding-left:5px}._fd-validate-item .el-input-number{width:100%}._fd-validate-title{display:flex;flex-direction:row;justify-content:space-between;margin-bottom:10px}._fd-validate-title>div{align-items:center;display:flex}._fd-validate-title>div>span{background:#ececec;border-radius:15px;font-size:12px;height:16px;line-height:16px;margin-right:5px;text-align:center;width:16px}._fd-validate-title i{cursor:pointer}._fd-validate-title i:hover{color:#ff2e2e}._fd-validate .append-msg{cursor:pointer}._fd-validate .el-input-group__append{padding:0 10px}._fd-required{align-items:center;display:flex;width:100%}._fd-required .el-input{margin-left:15px}._fd-required .el-switch{height:28px}._fd-value-input .el-input__validateIcon{display:none}._fd-value-input .el-select,._fd-value-input .el-select__wrapper{height:100%}._td-table-opt[data-v-91131f26]{width:100%}._td-table-opt .el-table[data-v-91131f26]{z-index:1}._td-table-opt-handle[data-v-91131f26]{align-items:center;display:flex;justify-content:space-between;padding-right:5px}._fd-tree-opt ._fd-tree-opt-btn{background-color:#2f73ff;color:#fff;cursor:pointer;float:left;height:19px;justify-content:center;line-height:20px;padding-bottom:1px;text-align:center;width:18px}._fd-tree-opt-node{align-items:center;display:flex}._fd-tree-opt-first{margin-right:5px;width:60px}._fd-tree-opt-last{width:165px}._fd-tree-opt-last._label{width:175px}._fd-tree-opt-last._label .el-input-group__append{width:65px}._fd-tree-opt ._fd-tree-opt-danger{background-color:#ff2d2e;border-radius:0 2px 2px 0}._fd-tree-opt .el-tree-node__content{height:28px;margin-bottom:3px}._fd-tree-opt .el-input__inner{border-right:0}._fd-tree-opt .el-input-group__append{background:#fff;padding-left:1px;padding-right:2px;width:90px}._fd-table-form{background:#fff;border:1px solid #ececec;width:100%}._fc-child-empty,._fd-table-form{min-height:130px}._fd-tf-wrap{display:flex;overflow:auto}._fd-tf-wrap>._fd-drag-tool{display:flex;flex-shrink:0;height:auto;margin:2px;overflow:auto}._fc-table-form{color:#666;overflow:auto}._fc-table-form .form-create td .el-form-item{margin-bottom:1px}._fc-table-form .form-create td .el-form-item.is-error{margin-bottom:22px}._fc-table-form .el-form-item__label,._fc-table-form .van-field__label{display:none!important}._fc-table-form .el-form-item__content{display:flex;margin-left:0!important;width:100%!important}._fc-tf-head-idx,._fc-tf-idx{font-weight:500;min-width:40px;text-align:center;width:40px}._fc-tf-btn,._fc-tf-edit{min-width:70px;text-align:center;width:70px}._fc-tf-btn .fc-icon{cursor:pointer}._fc-table-form._fc-disabled ._fc-tf-btn .fc-icon,._fc-table-form._fc-disabled>.el-button{cursor:not-allowed}._fc-tf-table{border:1px solid #ebeef5;border-bottom:0;height:100%;overflow:hidden;table-layout:fixed;width:100%}._fc-table-form ._fc-tf-table>thead>tr>th{border:0;border-bottom:1px solid #ebeef5;box-sizing:border-box;font-weight:500;height:40px;padding:0 5px}._fc-table-form ._fc-tf-table>thead>tr>th+th{border-left:1px solid #ebeef5}._fc-table-form tr{min-height:50px}._fc-table-form ._fc-read-view{text-align:center;width:100%}._fc-table-form td{border:0;border-bottom:1px solid #ebeef5;box-sizing:border-box;min-height:50px;min-width:80px;overflow:hidden;overflow-wrap:break-word;padding:5px;position:relative}._fc-table-form td+td{border-left:1px solid #ebeef5}._fc-tf-table .el-cascader,._fc-tf-table .el-date-editor,._fc-tf-table .el-input-number,._fc-tf-table .el-select,._fc-tf-table .el-slider{width:100%}._fc-tf-head-required:before{color:#f56c6c;content:"*";margin-right:4px}._fc-signature{width:100%}._fc-signature-btn,._fc-signature-preview{background:#fff;border:1px dashed #d4d7e0;border-radius:4px;box-sizing:border-box;color:#c9ccd8;font-size:14px;height:88px;line-height:88px;min-width:160px;position:relative;text-align:center;width:100%}._fc-signature-btn{cursor:pointer}._fc-signature.disabled ._fc-signature-btn{cursor:not-allowed}._fc-signature-preview>img{display:inline-block;height:88px}._fc-signature-preview .icon-delete2{cursor:pointer;display:inline-block;font-size:14px;line-height:14px;position:absolute;right:9px;top:9px}._fc-signature-btn i{font-size:14px}._fc-signature-dialog .el-dialog__body{text-align:center}._fc-signature-pad{background-image:linear-gradient(#fff 14px,transparent 0),linear-gradient(90deg,#fff 14px,#d4d7e0 0);background-size:15px 15px;border:1px dashed #d4d7e0;border-radius:4px}._fc-title{font-size:16px;font-weight:600;margin-bottom:16px;margin-top:1em;width:100%}._fc-title.h1,._fc-title.h2{border-bottom:1px solid #eee;padding-bottom:.3em}._fc-title.h1{font-size:32px;line-height:1.2}._fc-title.h2{font-size:24px;line-height:1.225}._fc-title.h3{font-size:20px;line-height:1.43}._fc-title.h4{font-size:16px}._fc-title.h5{font-size:14px}._fc-title.h6{font-size:12px}._fd-tf-col ._fd-tf-con .el-form-item{margin-bottom:1px!important}._fd-tf-col{display:flex;flex-direction:column;flex-shrink:0;flex-wrap:wrap;width:180px}._fd-tf-con .el-form-item__label,._fd-tf-con .van-field__label{display:none!important}._fd-tf-con{display:flex;flex:1;width:100%}._fd-tf-con .el-form-item__content{display:flex;margin-left:0!important;width:100%!important}._fd-tf-title{border-bottom:1px solid #ebeef5;box-sizing:border-box;height:40px;margin-bottom:0;padding:0 5px}._fd-tf-required{color:#f56c6c;margin-right:4px}._fd-tf-con ._fc-l-item{display:flex;flex-shrink:0;margin-top:4px;width:100%}._fd-tf-con ._fc-l-item>*{display:none!important}._fd-tf-con .el-cascader,._fd-tf-con .el-date-editor,._fd-tf-con .el-input-number,._fd-tf-con .el-select,._fd-tf-con .el-slider{width:100%}._fd-tf-col:has(._fd-tf-col){width:auto!important}._fd-size-input{align-items:center;display:flex}._fd-size-input .el-input-number--small{width:122px}._fd-size-input .el-button{font-size:12px;margin-left:3px;padding:5px;width:25px}._fd-color-input{width:150px}._fd-color-input .el-input .el-color-picker{margin:0}._fd-color-input .el-input .el-input-group__append{padding:0;width:24px}._fd-color-input .el-input .el-color-picker__trigger{border-left:0;border-radius:0 3px 3px 0}._fd-box-size-input .el-form{grid-column-gap:10px;display:grid;grid-template-columns:repeat(2,1fr);width:100%}._fd-box-size-input .el-radio-group{width:100%}._fd-box-size-input .el-radio-button__inner{padding:4px;width:100%}._fd-box-size-input .el-radio-button{flex:1}._fd-box-size-input ._fd-size-input .el-input-number--small{width:100%}._fd-box-space-input{color:#000}._fd-box-space-input ._margin,._fd-box-space-input ._padding{background-color:#f2cea5;box-sizing:border-box;height:180px;padding:40px 55px;position:relative;width:100%}._fd-box-space-input ._margin,html.dark ._fd-box-space-input ._padding{background-color:#a9855c}._fd-box-space-input ._margin{background-color:#c6cf92;height:100px;width:100%}._fd-box-space-input ._fd-input{background-color:unset;border:0;display:inline-block;font-size:12px;height:20px;margin:0;max-width:40px;outline:0 none;padding:0;text-align:center;text-decoration:underline;width:30%}._fd-box-space-input ._fd-input:focus,._fd-box-space-input ._fd-input:hover{background-color:#ececec;color:#262626;opacity:.9}._fd-box-space-input ._fd-left,._fd-box-space-input ._fd-right{left:7px;margin-top:-10px;position:absolute;top:50%}._fd-box-space-input ._fd-bottom,._fd-box-space-input ._fd-top{left:50%;margin-left:-20px;position:absolute;top:5px}._fd-box-space-input ._fd-bottom{bottom:15px;top:unset}._fd-box-space-input ._fd-right{left:unset;right:2px}._fd-box-space-input ._box{align-items:center;background-color:#94b5c0;display:flex;height:100%;justify-content:center;width:100%}._fd-box-space-input ._margin-title,._fd-box-space-input ._padding-title{left:4px;position:absolute;top:2px}._fd-box-space-input ._fd-help{align-items:center;color:#aaa;display:flex;position:absolute;right:5px;top:5px}._fd-box-space-input ._padding .fc-icon{color:#262626;cursor:pointer;font-size:12px}._fd-box-space-input ._padding .fc-icon+.fc-icon{margin-left:2px}._fd-box-space-input .fc-icon.active{color:#2e73ff}._fd-box-space-input ._fd-x{margin:0 5px}._fd-border-input{display:flex;height:110px;justify-content:center;width:100%}._fd-border-input ._fd-bi-left{align-items:center;display:flex;flex-direction:column;height:115px;justify-content:center;width:115px}._fd-border-input ._fd-bi-right{display:flex;flex-direction:column;justify-content:space-around;padding:5px;width:140px}._fd-border-input ._fd-bi-right ._fd-color-input{width:140px}._fd-bi-opt{align-items:center;display:flex;height:100%;width:100%}._fd-bi-opt ._line{width:100%}._fd-bi-opt .solid{border:1px solid #000}._fd-bi-opt .dashed{border:1px dashed #000}._fd-bi-opt .dotted{border:1px dotted #000}._fd-bi-opt .double{border:1px double #000}._fd-border-input ._fd-bil-row{display:flex;height:38px;justify-content:center}._fd-border-input ._fd-bil-col{align-items:center;cursor:pointer;display:flex;font-size:16px;height:22px;justify-content:center;margin:8px;width:22px}._fd-border-input ._fd-bil-col.active{color:#2e73ff;outline:1px dashed #2e73ff}.line-box{box-sizing:border-box;height:20px;padding:1px;width:150px}.line-box-con{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAD5JREFUOE9jZGBg+M+AChjR+HjlQYqHgQFoXibNS+gBBjKMpDAZHAaQ5GQGBgYUV4+mA7QAgaYokgJ14NMBAK1TIAlUJpxYAAAAAElFTkSuQmCC);height:100%;opacity:.3;width:100%}._fd-radius-input{display:flex;flex-direction:column;width:100%}._fd-radius-con{display:flex;flex-wrap:wrap}._fd-radius-item{box-sizing:border-box;padding:5px 0;width:50%}._fd-radius-item,._fd-radius-item ._fd-radius-icon{align-items:center;display:flex;justify-content:center}._fd-radius-item ._fd-radius-icon{height:24px;width:24px}._fd-radius-item ._fd-size-input .el-input-number--small{width:70px}._fd-font-input{display:flex;justify-content:center;padding:0 5px}._fd-fi-box{overflow:hidden;width:150px}._fd-font-input .el-form{grid-column-gap:10px;display:grid;grid-template-columns:repeat(2,1fr);width:100%}._fd-font-input .el-form--inline .el-form-item{margin:0;padding:0}._fd-font-input ._fd-size-input .el-input-number--small{width:100%}._fd-shadow-content{align-items:center;display:flex;flex-direction:column;justify-content:space-between;padding:0 5px}._fd-sc-form .fc-icon{font-size:12px}._fd-shadow-content .el-form{grid-column-gap:10px;display:grid;grid-template-columns:repeat(2,1fr);width:100%}._fd-shadow-content .el-form-item{display:grid!important;margin:0!important}._fd-shadow-content .el-input__wrapper{flex:1}._fd-shadow-content ._fd-sc-box{border:1px solid #ccc;border-radius:5px;cursor:pointer;height:250px;overflow:hidden;position:relative;width:250px}._fd-shadow-content ._fd-sc-box .spot{border-radius:100%;height:0;position:absolute;width:0}._fd-shadow-content ._fd-sc-box .spot-id{background:#1989fa;border-radius:100%;height:10px;left:-5px;position:absolute;top:-5px;width:10px;z-index:1}._fd-shadow-content ._fd-sc-box.down .spot-id{box-shadow:1px 1px 10px 2px #1989fa}._fd-shadow-content ._fd-sc-box .center-spot{background:#1989fa;border-radius:100%;height:0;left:125px;position:absolute;top:125px;width:0}._fd-shadow-content ._fd-sc-box .x-hr{background:#ccc;height:1px;position:absolute;top:125px;width:100%}._fd-shadow-content ._fd-sc-box .y-hr{background:#ccc;height:100%;left:125px;position:absolute;width:1px}._fd-shadow-content .el-select__placeholder{text-align:center}._fd-shadow-content .el-input-group__append{padding:0;width:55px}._fd-shadow-content ._fd-color-input,._fd-shadow-content .el-input{width:100%}._fd-shadow-content ._fd-sc-right{margin-top:10px}._fd-shadow-content ._fd-sc-radio{width:100%}._fd-shadow-content ._fd-sc-radio .el-radio-button{display:flex;flex:1}._fd-shadow-content ._fd-sc-radio .el-radio-button__inner{width:100%}._fd-shadow-input ._fd-ci-con{width:150px}._fd-shadow-input :focus-visible{outline:0 none}._fd-si-input .el-input-group__append{display:inline-flex;padding:0;width:24px}._fd-si-input .el-input__wrapper{flex:1}._fd-shadow-input ._fd-ci-con .fc-icon{cursor:pointer}._fd-pos-input{display:flex;flex-direction:column;width:100%}._fd-pos-con{display:flex;flex-wrap:wrap}._fd-pos-item{box-sizing:border-box;padding:5px 0;width:50%}._fd-pos-item,._fd-pos-item ._fd-pos-icon{align-items:center;display:flex;justify-content:center}._fd-pos-item ._fd-pos-icon{height:24px;width:24px}._fd-pos-item ._fd-size-input .el-input-number--small{width:70px}._fd-style-config{display:flex;flex-direction:column;width:100%}._fd-opacity-input ._fd-ci-con{align-items:center;display:flex;justify-content:space-between;width:150px}._fd-opacity-input ._fd-ci-con>span{width:32px}._fd-opacity-input .el-slider{flex:1;margin-right:15px}._fc-designer{--fc-drag-empty:"\\62d6\\62fd\\5de6\\4fa7\\5217\\8868\\4e2d\\7684\\7ec4\\4ef6\\5230\\6b64\\5904";--fc-child-empty:"\\70b9\\51fb\\53f3\\4e0b\\89d2 \\e789 \\6309\\94ae\\6dfb\\52a0\\4e00\\5217";--fc-tool-border-color:#2e73ff;background-color:#fff;cursor:default;height:100%;min-height:500px;overflow:hidden;position:relative}._fc-designer>.el-main{bottom:0;left:0;padding:0;position:absolute;right:0;top:0}._fc-l-menu{border-right:1px solid #ececec;border-top:1px solid #ececec;flex-direction:column}._fc-l-menu,._fc-l-menu-item{align-items:center;display:flex}._fc-l-menu-item{box-sizing:border-box;cursor:pointer;height:40px;justify-content:center;width:100%}._fc-l-menu-item.active{color:#2e73ff}._fc-l-menu-form{border-bottom:1px solid #ececec}._fc-l-menu-item i{font-size:22px}._fc-l-menu-item i:hover{color:#2e73ff}._fc-l-menu-item .el-badge__content{--el-badge-size:15px;--el-badge-padding:4px;background-color:#2e73ff}._fc-l-label{color:#262626;font-size:14px;font-weight:500;line-height:17px;margin-top:5px;padding:12px}._fc-l-info{color:#aaa;font-size:12px;font-style:normal;font-weight:400;line-height:17px;padding:0 12px;text-align:left}._fc-l>.el-container{height:100%}._fc-m .form-create ._fc-l-item{align-items:center;background:#f5f5f5;border:1px dashed #000;border-radius:4px;color:#000;display:flex!important;height:30px;justify-content:center;margin:5px 0;overflow:hidden;padding-bottom:0;transition:all .3s ease;width:100%}._fc-m .form-create ._fc-l-item ._fc-l-icon{display:inline-block!important;padding:0 4px}._fc-m .form-create ._fc-l-item ._fc-l-name{display:inline-block!important;font-size:12px}._fc-l,._fc-m,._fc-r{background-color:#fff;border-top:1px solid #ececec;box-sizing:border-box;overflow:unset;position:relative}._fc-l-close,._fc-l-open,._fc-r-close,._fc-r-open{align-items:center;background:#fff;border-radius:0 5px 5px 0;cursor:pointer;display:flex;height:46px;justify-content:center;position:absolute;right:-12px;top:50%;width:12px;z-index:1}._fc-l-open{left:0;right:unset}._fc-r-close{left:-12px;right:unset}._fc-r-close,._fc-r-open{border-radius:5px 0 0 5px}._fc-r-open{right:0}._fc-l-close>i,._fc-r-open>i{display:block;font-size:9px;transform:rotate(-90deg)}._fc-l-open>i,._fc-r-close>i{display:block;font-size:9px;transform:rotate(90deg)}._fc-r{--el-color-primary:#2e73ff}._fc-r-tab-props{padding:0 20px;position:relative}._fc-r-tools-close{color:#666;cursor:pointer;position:absolute;right:24px;top:12px;transform:rotate(45deg)}._fc-r-title{color:#333;font-size:12px;margin:15px 0 5px}._fc-r-sub ._fc-r-title{align-items:center;display:flex;justify-content:space-between}._fc-r-sub .fc-icon{cursor:pointer}._fc-r-sub ._fd-config-item+._fd-config-item{margin-top:8px}._fc-r-sub>._fd-config-item>._fd-ci-head{padding-left:8px;position:relative}._fc-r-sub>._fd-config-item>._fd-ci-head:before{background-color:#333;border-radius:25px;content:" ";height:5px;left:0;position:absolute;width:5px}._fc-r-config{display:grid;grid-template-areas:"a" "b" "c" "d" "e";grid-template-columns:280px}._fc-r-name-input .el-input-group__append{color:#606266;cursor:pointer;margin:0;padding:0;width:25px}._fc-r-name-input .icon-group{cursor:pointer}._fc-r-name-input .icon-group:hover{color:#2e73ff}._fc-r .el-main{padding-bottom:100px}._fc-l .el-main{padding:0}._fc-l .el-tree-node__label{color:#333;font-weight:400;padding:3px}._fc-l .el-tree-node__content{height:30px;margin-top:5px}._fc-l .el-tree-node__content>.el-tree-node__expand-icon{color:#333}._fc-l .el-tree-node__expand-icon.is-leaf{color:transparent}@keyframes a{0%{transform:rotate(0)}to{transform:rotate(1turn)}}._fc-loading{animation:a 2s linear infinite}._fc-tree-node{align-items:center;display:flex;height:26px;justify-content:space-between;line-height:26px;padding-right:5px;width:100%}._fc-tree-node.active,._fc-tree-node.active .icon-more{color:#2e73ff}._fc-tree-label{align-items:center;display:flex}._fc-tree-label>i{font-weight:700;margin-right:5px}._fc-tree-more{align-items:center;display:flex;font-weight:700;padding:0 15px}._fc-l-tabs{border-bottom:1px solid #ececec;display:flex;padding:0}._fc-l-tab{box-sizing:border-box;color:#303133;display:inline-block;flex:1;font-size:14px;font-weight:600;height:40px;line-height:40px;list-style:none;position:relative;text-align:center}._fc-l ._fc-l-tab.active{border-bottom:2px solid #409eff;color:#409eff}._fc-l-group{border:1px solid #eee;margin:12px;padding:0;user-select:none}._fc-l-group ._fc-l-list{align-items:stretch;display:grid;grid-template-columns:repeat(3,1fr)}._fc-l-title{align-items:center;cursor:pointer;display:flex;font-weight:600;justify-content:space-between;margin:0;padding:12px}._fc-l-title,._fc-l-title i{font-size:14px}._fc-l-title i.down{transform:rotate(90deg)}._fc-l-item{background:#fff;color:#000;cursor:pointer;display:inline-block;line-height:1;padding-bottom:10px;text-align:center;transition:all .2s ease}._fc-l-item i{display:inline-block;font-size:21px}._fc-l-item ._fc-l-name{font-size:12px}._fc-l-item ._fc-l-icon{padding:10px 5px 12px}._fc-l-item:hover{background:#2e73ff;color:#fff}._fc-m-tools{border:1px solid #ececec;border-top:0;height:40px;justify-content:space-between;white-space:nowrap}._fc-m-tools,._fc-m-tools-l,._fc-m-tools-r{align-items:center;display:flex}._fc-m-tools-r{overflow:auto}._fc-m-tools-l .devices .fc-icon{cursor:pointer;width:18px}._fc-m-tools-l .devices .fc-icon.active{color:#2e73ff}._fc-m-tools-l .devices .fc-icon+.fc-icon{margin-left:5px}._fc-m-tools .line{background:#d8d8d8;height:24px;margin:0 10px;width:1px}._fc-m-tools .el-button{align-items:center;border-radius:5px;display:flex;padding:5px 10px}._fc-m-tools .el-button>span{align-items:center;display:inline-flex;justify-content:center}._fc-m-tools .el-button+.el-button,._fc-m-tools .el-dropdown{margin-left:10px}._fc-m-tools ._fd-m-extend{background-color:#f1f1f1;border-color:#ccc;border-radius:5px;color:#666;padding:5px}._fc-m-tools ._fd-m-extend .fc-icon{margin-right:0}._fc-m-tools ._fd-input-btn{align-items:center;display:flex;font-size:12px;justify-content:space-between}._fc-m-tools ._fd-input-btn .el-switch__action{height:18px;left:2px;width:18px}._fc-m-tools ._fd-input-btn .is-checked .el-switch__action{--el-color-white:#2e73ff;left:calc(100% - 20px)}._fc-m-tools ._fd-input-btn .el-switch__core,._fc-m-tools ._fd-input-btn .is-checked .el-switch__core{--el-switch-on-color:var(--el-switch-off-color);border-radius:16px;height:24px;width:46px}._fc-m-tools-r .fc-icon{font-size:14px}._fc-m-tools-l .fc-icon{cursor:pointer;font-size:18px}._fc-m-tools-l .fc-icon+.fc-icon{margin-left:10px}._fc-m-tools-l .fc-icon.disabled{color:#999;cursor:not-allowed}._fc-r .el-tabs__nav-wrap:after{background-color:#ececec;height:1px}._fc-r ._fc-r-tabs{border-bottom:1px solid #ececec;display:flex;padding:0}._fc-r .el-button,._fc-r .el-radio-button__inner,._fc-r .el-table__cell .cell{font-weight:400}._fc-r ._fc-r-tab{box-sizing:border-box;color:#303133;display:inline-block;flex:1;font-size:14px;font-weight:600;height:40px;line-height:40px;list-style:none;position:relative;text-align:center}._fc-r ._fc-r-tab.active{border-bottom:2px solid #409eff;color:#409eff}._fc-m-con{background:#f5f5f5;padding:20px 20px 36px;position:relative}._fc-m-drag{box-sizing:border-box;margin:0 auto;overflow:auto;padding:2px}._fc-m-input{padding:5px 5px 80px}._fc-m-input-handle{background:#fff;bottom:16px;box-shadow:0 2px 10px #0000000d;left:0;margin:20px;padding:12px;position:absolute;right:0;text-align:center;z-index:3}._fc-m-drag.mobile{width:400px}._fc-m-drag.pad{width:770px}._fc-m-drag,.draggable-drag{background:#fff;height:100%;position:relative}._fc-m-drag>form,._fc-m-drag>form>.el-row{height:100%}._fc-m-drag .el-tree{width:100%}._fd-drag-box{height:100%;min-height:80px;transition:padding-bottom,padding-top .3s ease;width:100%}._fd-drag-box>div[data-draggable]{margin-bottom:1px}._fc-r ._fc-group-container+._fc-group-container{margin-top:20px}._fc-r ._fc-group-container{margin:0;padding:10px}._fc-r ._fc-group-handle{right:15px}._fc-r .el-form-item{margin-bottom:10px!important}._fc-r .el-form-item__label{color:#333}._fc-upload-preview{border-radius:5px;display:inline-block;height:120px;overflow:hidden;width:120px}._fc-tabs .el-tabs__item{font-weight:400}._fc-tabs .el-tabs__content{overflow:auto}._fc-tabs .el-tabs__nav-scroll{padding:0 15px}._fc-tabs .el-tab-pane{margin-right:15px}._fc-tabs .el-tabs__nav-wrap:after{height:1px}.form-create .fc-none{display:none}._fd-draggable-drag.drag-holder,._fd-elCard-drag.drag-holder,._fd-elCollapseItem-drag.drag-holder,._fd-elTabPane-drag.drag-holder,._fd-group-drag.drag-holder,._fd-subForm-drag.drag-holder,._fd-tableFormColumn-drag.drag-holder{background:#f5f5f5;background-size:0;min-height:90px;position:relative}._fc-child-empty:after,._fd-draggable-drag.drag-holder:after,._fd-elCard-drag.drag-holder:after,._fd-elCollapseItem-drag.drag-holder:after,._fd-elTabPane-drag.drag-holder:after,._fd-group-drag.drag-holder:after,._fd-subForm-drag.drag-holder:after,._fd-tableFormColumn-drag.drag-holder:after{align-items:center;bottom:0;color:#aaa;content:var(--fc-drag-empty);display:flex;font-size:12px;justify-content:center;left:0;position:absolute;right:0;top:0}._fc-designer ._fc-m-drag ._fd-draggable-drag{overflow:auto;padding:2px 2px 100px}._fc-m-drag._fd-drop-hover ._fd-draggable-drag{padding-top:20px}._fd-draggable-drag.drag-holder{background-color:#fff}._fd-draggable-drag.drag-holder:after{font-size:16px}._fc-child-empty:after{content:var(--fc-child-empty);font-family:fc-icon!important}.fc-configured{color:#999;margin-left:5px}._fc-manage-text{color:#2f73ff;cursor:pointer;font-size:12px;margin-left:4px}._fc-manage-text i{font-size:12px}._fc-message-error{background-color:#fff;border-color:#fff;border-radius:8px;box-shadow:0 6px 16px #00000014,0 3px 6px -4px #0000001f,0 9px 28px 8px #0000000d;padding:9px 13px;top:16px;z-index:4}._fc-message-error>.el-icon{font-size:18px}._fc-message-error .el-message__content{color:#000000e0;font-size:14px}._fd-preview-copy{align-items:center;background:#2e73ff33;border-radius:10px;color:#2e73ff;cursor:pointer;display:flex;height:28px;justify-content:center;position:absolute;right:35px;top:65px;width:28px}._fd-preview-dialog{border-radius:6px;min-height:40%;padding-top:0}._fd-preview-dialog .el-dialog__header{float:right;position:absolute;right:0;top:0;z-index:2}._fd-preview-code{margin-top:0;max-height:510px;overflow:auto}._fd-preview-tabs .el-tabs__nav-wrap:after{height:1px}._fd-preview-tabs .el-tabs__item{height:46px}._fd-preview-code>code{white-space:pre-wrap}._fd-row-line{background:#d8d8d8;height:1px;margin:10px 0;width:100%}.CodeMirror-hints{z-index:5}@font-face{font-family:fc-icon;src:url(data:font/woff;base64,d09GRgABAAAAAFOoAAsAAAAApWAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADsAAABUIIslek9TLzIAAAFEAAAARAAAAGA8FUuJY21hcAAAAYgAAAQeAAAKdIfzZw1nbHlmAAAFqAAARj8AAIvcpZc9X2hlYWQAAEvoAAAALwAAADYs4uLZaGhlYQAATBgAAAAeAAAAJAghBRFobXR4AABMOAAAAGQAAAKQqEAAAGxvY2EAAEycAAABSgAAAUr05NDIbWF4cAAATegAAAAfAAAAIAHIAPxuYW1lAABOCAAAAUUAAAJb1Yvg7HBvc3QAAE9QAAAEVQAABukvWosjeJxjYGRgYOBiMGCwY2BycfMJYeDLSSzJY5BiYGGAAJA8MpsxJzM9kYEDxgPKsYBpDiBmg4gCACY7BUgAeJxjYGFRY5zAwMrAwNTJdIaBgaEfQjO+ZjBi5ACKMrAyM2AFAWmuKQwHnjG8mMzc8L+BgYH5DgOQZGBEUcQEAIDyDRt4nO3WZXOVVxiF4TtAoQXq1N3d3VuguLu7u7u7W3CXugs1atTd3YWZNfwLujarH/ojmsyVyXtmMsmcPPezN3AEUNWutGpQZT8V/o6KfX614vDrVal5+PVqFSv8fAxHUoXqQrVUXw114GDloUNw+LmuGqhxnv/zUeGfKZ8nUKeikZ+P8itjGcd4JjCRSUymFlOYyjSmM4OZzGI2c5jLPOazgIUsYjFLWMoylrOClaziUlZzGWuoZC3rWM8GNrKJS7icK9jMFv/NF7OVq9jGdnawk13sZg83chM3cwsXcCu3cTW3cwd3chd3cw/3cg33UZd61Od+GtCQa2lEY5rQlGY0pwUtaUVr2tCWdrSnAx25jk505nq60JVudKcHPelFb/rQl370ZwADGcRghjCUYQxnBDcwklGMZgwXcSFncw7nch7ncxZncCZHcxqn+306luOozfF+v06kDidxMqdwqt/1mlT3/6Ka/2NVqOG3tTr/f9QuX2rs/fdpT5nD8JR5IqPMseqGJw/VC88gqh+eRtQgPJeoYXhCUePwrKIm4alFTYPye5qFJxk1D880ahGebtQyPOeoVXjiUevw7KM24QpQ23APqF24DNQ+3AjqEK4FdQx3gzqFC0Kdwy2hLuGqUNdwX6hbuDTUPdwc6hGuD/UMd4h6hYtEvcNtoj7hSlHfcK+oX7hc1D/cMBoQrhkNDHeNBoULR4PDraMh4erR0HD/aFh4E6Dh4Z2ARoS3AxoZ3hNoVHhjoNHh3YHGhLcIGhtlN2pceLOg8eEdgyaEtw2aGN47aFJ4A6HJ4V2EpoS3Epoa3k9oWnhToelxeJ5nhLcXmhneY2hWeKOh2eHdhuaEtxyaG953aF5486H54R2IFoS3IVoY3otoUXhDosXhXYmWhLcmWhren2hZeJOi5eGdilaEtytaGZQWVwWlxdVBaXFNeAujyqB0uTYoXa4L72i0Pryt0Yag9LoxKL1uCkqvm4PS65ag9Lo1KL1uC0qv24PS646g9LozKL3uCkqvu4PS656g9PpAUHp9MCi9PhQ+TdDDQWn3kaC0+2j4rEGPBaXjx4PS8RNB6fjJoHT8VFA6fjooHT8TlI6fDUrHzwWl4+eD0vHeoHT8QlA6fjEoHb8UlI5fDkrHrwSl431B6fjVoHT8WlA6fj0oHb8RlI7fDJ+jaH9Qmn4rKE2/HZSm3wlK0++GT170XvgMRu+HT2P0QfhcRh+GT2j0UfisRh+HT230Sfj8Rp+GT3L0WfhMR5+HT3f0RficR1+GT3z0VfjsR1+HbwHom/B9AH0b5cTTd+E7Avo+fFtAP4TvDejH8A0C/RS+S6Cfw7cK9Ev4foF+Dd800G/hOwf6PcodUH+E7yHozyh3R/0V5f6ov6PcK3UgfF/hYGVQ4x/aBF6zAAB4nL29B4AcZd04PM8zOzvb++zs3m2f292re7f9cj3JHcml3QVISCGdllBTJKE3RUWaQhBeJYBgAJUmBKICShH/Ypf64serFBHUV1BBEbPD9/s9M3u3e8klp3+/7+6ZZ56+M0/71ec3nIGDP/4S+k8uyWW5AW6c45KiUUlk6CAplcEVCxmixBMOIrl9EZKP5wZIPxGNJJ7z+4yQkSqUxEKpl2C0jSRSyXQqIRKjkxj9WgsFumStSbT5nQY+JLiIx+EN58LqeCQbDmcj5H6I7HGpD5IdVpfLql4JPty/Z9U8utXqyXvU/zXTEG83b7CZiddzYphafQHbWtlFvA5xBdQHV3lTu391j4u86/K7wH3dir5rldUDDZjsG8x2PkywgTy+soXjPn6fv5L+leM5kbNyTs7LyVwjF+UULs1xRFKKsiLlef2e1O8kLylxuJJ4xaU4Pf/DR84++2fo0Q3oq7uHhtT3hoZobGio8hu6vnIr/evf/z60U73/ww9nn/0IeuQH519//fVfHeKM8AwfGebSF+B5bFyAa+bauS6OK0MnS1kf9G0xWyBS3K0kUkV3oZQXcn4YBNbLLC4r7nwaHmRHPzm9v1+9rp//fKXbE2xqCtJX0P8req/97Gev/fWvO/r7+vrphrHKA5DZFGx6RGlQlwcVJUi+1qCMqee1ngPPAHPh41f4R/kI5+LCXIRrhX7IRYjPQRLpVDpVKJfKJRlmAswDmAUwL7zlbClK/GLW2ElS5Lsrblyx4sYHFi9o7+hoX7B45bydRx2186pdRx21a3UwEPgoEAgE/xHkIytuuv/GFR1tCxc9uAhKjmCRkZFdV+08Sm1jxZjHnuV9/gn6OoyLwjVhv+CzwPs7cDpmCPTAAMkL8YQR0vzQZSVISQkwESEOXQQxPtE22to62lb5c9toGzjqrPw5WSwmqRP8yo+j62KxdVH6GpRQx7US5H6oMVxMquNYjtyfLI7F1HNjMXJ5DPqHsrXyBVrhPPBMnJn4fTDTYQWUzSRDBqhfqE2BhZGjObLMalW/bov5raRHfUY0Gh0+n3q5lmqNySzVKsfoexjFknIM0kiP6PM5BcE0JRVqaOMEz3ElfZL1TQ83NsO+iZJBkpfdopIuOonSSSQIucvJnD8MyX5YsQkYRljKxUKJ/r15pBmc6tHu5E+qR8lmFfIn8NVh8dijxQ6TqUssis1wdZlM5EKHJ0AMfIkItER53u+lTzQPN6t7wQNHVoE3lFXUvayVVUp2mfD44/B+T5tMd5xrMJx7BzTRZ4IV/Q7hYUnQudT4jsPLcXZtnfI2zgez4ChuHncMdyy3ntvAnc6dwZ3Lncd9esrb00O/fToXoWGiFAspRc/phUWuF6d5L06cLJs42UKxNlLOZ9lyzGKkl0wX4VsaMo2NmQZ1rq8p5IQweUyd64RRJI/BkN+OiUM+pzoXIo5czptsdLkak95HY+/E0N2C7vp4LBY/PgZ/qzFEd0Ir6lzwsCR5DBqfaIE85vS9gMm5HEbxl4acoSYfeQxbrvwMG/kMenfGav70Na71Z3Xf47ywj+E/cSvsfwcfOPA2XnTDJXddQpeht0FL4QPVvfMV/go+BTtlkStxgzADJ0eFjUYYd402UsznSv1E63JI0rscAAiOkAyDUYLh8eOwxIni8+dKhVTCKGvbHMIWbduLu+NJGBFoAEYE6kq1ET4WzoZC2bCiuCTJVfke+oqipZ08HPWTa/3RYfVqsrcQaY9G2yNOfzTqV89Cn1wC6dvUpwIvByYcXRPKht6Ga9jvfNvpZ96wnjT4HNZ67in1T3CHxk6Ehp5mDfX5oyPqmy8HhAC4E7QbbBATfS0DdO2EPayP6+dGuQXccoQw0Ccim6BSfGIaQt9RnMlUibvxvbW5K9bOR2naCOXUl+xeCj3for5UnWH06GpI3UtWqW+RBpgw/xM7JTbFkTeG3Hb1lw7fbDbbWnDCkhYMV+7FFvjA7MoWeLCjHb5rcC59ecJjc+pl/my+GaCYxBVgjXKkFkyIiCQgfsAnHFTyyX74R1gy+Q9IBuVTCGSKMDNwNftEo2ikYbYtL2d7M2wcR8HeMXLhhsLKoZPyKadHCjXGI0ooJjfYnYUNFw0XsvNai/PO+8K580st87voGW3zj8P9/7j5bX/NjeTAkXuK6y8a+cxla8Zb/Gab0yeHIkqyrS2bK5Wh2WJFVDLDo+vWL7xwwYILF65fNzqc4RomxhAxBB8Xg1EswKyfzS3llsE4buQ2cWdyZ3Hncxdwn+Wu4G7gvsjdzt3B3c89wD2Ga6F+LiMOka6dvskjFThyC7Ugr5dOZtSGxdoaddW9tZHaKuRrvnDYpx6QwmHpg7feusFSsICjyzCuHsA8SN1tyVvAqe8fqiyJWdot4K6r3jos4K6zfM8C7jrLdy3g6vN4W7VtYpDCs6sNff6Qqepvqj9JDL7wUPVZPm9R77FYyDEW7Qc09wWt/S9ov6a+aSlbJhzDw7QxruIaPdzwDGEqAgc3AwHuQlxDltqIGzEzbe4fFpBWKhH4I7bSqhK4a69dtiw5lEwO8ba6CuDNBsg5Ue1TEfV+qDUeKa4+bXWRXNmn/jE5uGgwWbPfeBkW26zP1Dnckhm+DWxFMwR3h3+vc+nLleZVSfg7D73z0TvCW907tD9Z88fVwSp8n+6ZjokoG/1RIqbSooOmM3SA+GXRGIF7uZQhxsM++M/KljSgh4VYrzXTF0nlhOb5ixpuFMzBRCIgGBsLmSO8xA5HathKyo72V8MtDpOlb8ncqCs8x50Ims1Bd0PG0VClO3jcKZNcistxeYCfQ9wi2Em2HQqToQe9H5kWVamLEIbwsPkITVVBbhUOa3CZXg9IBcMyqkACsZYqguFTPxF7O4ZuD3Nr0VE3FoVqzyCEaOxofOYZhyQ5noFKUxGWOpwHG1QfRLzmvFg8HjsPIQdpx4LPQCOIuzwDFWf7HNXmuJq+qq7LLMDPAYCfx3Gr/431ySZwNSJDDnQFRKAjZL/PyfB2IFlzApBUR1i24Ugk/HYkHI7cjqGcyTRoCrjRUz99zz0zXb7V++7wo5HIo2GTKWgaMrkD5kGzr6+K4/NJRhvGYK5MoQtLuahGbcCDa8S6mYhlsjVGfxWLVZIx8k+bYv+Uzaaeb7bdavE5jSRILeR29VrvOeTMUex9+jOb7XK7YlMvMPFxanR4rXc5zGRYvf6SIDkDf16A/v8L/zneziWg1xfDLnIcYIsIj8qlKC0DNCrnNegEz9BJRGMqjSCKTEJ5WIdTQXwNgE9PoHz/Zffe6XAY/vlTl02wey2md12uj37giPBe8om2pf1xv08JNTWG/HKoIRlSvHJ8YLy93DLUNrpiQdtQS/eCtoH29oE28obXrj68CtCGGzYTq81DPQ71NbNTOMdkvuE0k9MtkIvtXtKa6F/aVi5m2hIx2et0euVYor2jWIafSaitqdzYMR2L2tsXdRwzlvtOtA0b1vpBm4c4Fgqs2EVTR0NH2w63bIUBIvtz5QESoYDaJNKwF4kOoBvfi5HfxGIqjMcsfWVpCJeGvFXuxcWDWJev8n96rx7JrS4ZnC67kQrzbhxOdBdCoiwZi7ii6EsMyZsLDUxSArM12kCjQv6yiWycP2oUqdnpMm7YkI1GuqxGewDoAI2efFOHG22Aq+a58kxXmFvxTrkOv3r+cfbZN4+P78xmx3K5zIIF9E2kE7fodOINSCfmEuoWVv6GRG6tuoNcWX8dDBvS8LwzhnCC5EPWVAGI3cNDg8oqpHnIQMB6i8VyizVwpIU9++IX7XbTqcaA8VSTXV8/r/B7gU7ywfrNcb3cfHxKXBcMvT2Yq4LhAqaKOt8lg9u2nDBqC4Uv+WUlQ4BuUAYI5bZvXNfd09O9buNb1cD2dXesXXvHd48e7+zq6hw/ej3jfyxnTJB0d/eS7u70yY/0Ls1fccMV+aW9j/CpusosMLL2jsfvWIuVv4etdLeNLkPsG7yh8lgZnPorsv7Y7r6+7mPXcxM8kX76GFsbnBdXhZutCnfh6T2kb88e9ek99FH0McbG7XV6Eh/hkD6ycH7okUnKr3a3pqHRtr42cA34MvSFCIbbnu1Sg11d5HddUJ+w/l0H/dvA+pX4HbSTwHaEqDKuLYr8R8rJLtLiJJE218+Bavu5JRTyiSYL0DZ8yq2+5GqLEOcvKjcjHUc3/cJiEn2hEOS6tfZ/ycaPzS6gUzXmG7I7M8TBA05By0DDDkKe6OAB1eD9MsT59bhm/we9K/JX33T5sVLXadf81yXjotsuOCRiWvtf3773sv7ACbft/8agxKe0oiFfzyzr4ou/eN3pBc/Rly7mbT5C/M41e04K9F36wCNfWu+Ye9SVABR51t+30//DmaC/XfBcfL4cl/O8wnvzXuLOu8ll/9zeS7y92z86t/1kw8nEcRr9QWXbHV+l16qnk6j6GvlCZRu9mtPGC9raTr/PwnYuCP2YYmupABjJbNjxOW8tceGuQy/+jZyrOo/vBKfsUMAR//Qx+v1OdV0mQ27rrDyqwB8dPnycw544wrss4VYB1nDy4d7pP5tzVWcG/jqVEQXcDzMQ68wcMpLphKgW+Vdf/KCOME3bD/XvHa+N1GGU8fp3+F0nOGif3dV1yscKOHKbdp983PrH0MbEcNCz1O8P7loQelUn/UxnZ2VXp0JvVZTK+oO6ojpft9FfHX6+TlCw7jo6+t/IuSa7KZfblNV84shtzE04iFUzwKevZH+dBadepd3J9sPHGWyAd3mdPszgVwfgtcfNEII5iCiVylLenZwqDgC4kB9AQQwCFxF206KCRA/AjWWp2Slwld2p2cnk7BQ9tbIbttMIPRX8ytuulD/dkCH3BuLxwGtyPC5/It1Jmom/TXJZ/a32ILlrp2mBPdBB90Fd9V6tDXI03Oe2RdR7sSVydKTtRJds7LDKr5OG7risvoUNkQY5viDfEg57mz1WVwgQFMWTdTQMtuhz43f6+49zx88U2wBUU/LlIVZQECzmc2XZqCQQkDIWk5IoYhR24ZwsAkTx6RxIftVhukAdSnkJNbkiZxTmLyl3Son8rFi/nNv0yaVDMakh3NCUGbC1OIOkwRfwHKETvnqyycpbM5GTZp8x5GpslOXMJ49ffNJIJNgaaioHXGFvW9ptcLgZrf44/0d+LtCBLdwIt4xbw23lzucu4j6n8walCd6g3hfxif7JFrXeULJpTezmzlfFbwibUGAE84HXIBZyjqAHkHMk5st5OV9WpHypPECLhVSa8Sh0dAOmTBsppmWlnMtr0bRSpF9WV7B3u0tdofUgPVV7d0z1I4e0qQn9G52S5GxtRX8MJ5L6KE6AdemjUs5RV/Pq+QMJi12yeV02h9VtcclBp8liMnoTu4PZH3oFs8VmttsDdJ/el3OrfVvt67la7/5cikoVB3jg6F/AU38Xl1+Dn5Nfl+NLHm5tffhhQojTb3cGDYIU8NooPVmwuW2EdPXNHSNWF4RIS5WH/b/8NbyXawS6cgAw+hO4M2AEzoP+v4ZBexnQRDnO1piDpuNGhteXGAmNbFsgQkgZ5Qpx6EBAwkUFxSlFcOV4lIhKJ1GcREkPkniOoXtOgtSQtjbbCJRCRp4f6aJUWmRjkmO4n3fKqCHGSu70Rnh4i3zrLETtK3cgZd3T8tOf2jyQTE+weqnLpn4vkeVjgtAndMAlkB7DYiFrsI/ahU5hsUAMbR6LlTdQg9FgMFucIad7oyBsyHZZzTbpf6WWH7Z1WS1Gk+1Ktyy71RfRJ43q+8TGe0PSp+EHZrWquxhPmOAj0PUtPXPctsqXrV6eQK9eBSX4nPKUkBLuEcClBP5l0dDSYhBf5oPOVoeBJ7xBEBwt7oCRF14WLuskBqPbbu4bOi4lGPyQ/YDsXucKBFzr3HJ5NtvrAbd7AnCvg3lZYwDHT/+Pc7TqJEDJAQKDyFDIQQKkWhlliciFRlkiotP1ssQx3nzg7+ub0+nmT6abm9OXYWgsdlE0elFM/b25wXLjjaLbaeVF4+rVZoPAp9rmt1XeAw8cdYE3UkhW3ksWCknqSha+NfI0NlK91DOjsVh0uYU3nXuewIt2t9M4Oir6rRzSbTU00FxuGPbRpbCPzBSOMOLCLzOYgT4jOVJpBegOnJM1wFmqjRxeNrjNHI1JUixqni/KDbI4vz56R4ysB2L31lj1fgSa6qdOm6kx0WC2O212u81pNzckGk02p9XhsJ6BPIwrdLGWjhd/lb7DhbhZTL6C71RlyLI3imvUVB4WLmyNss6tVeJHzlS0zF4CVJfqjpjHTE6z2WkaM0cSEVPld1qMBk2R5z1hMylocfXH5rASwZvDDA6SI7+CujSmZVd+g7XN87TYPHOEvuN2mp1wOTweBws4gTxxO01OD1zuiYD68kT2RAVtLrzC/+Sg9fIfWCW8klnS0bEkoxozizPgiFr5uHnWrGZKwK8cw/MHKusnZv3M5zan893v1mWRJqCSJS6OOKJbIXhNi69+IZulv83lKvdo2Bdp13Ay3qbLLS/LqjuyWXJltnpndOrHH/Hfox/C/EDOBm7GEeKgqUmCUef/yYTtvAOaGDNerIaQy0W9gtV0hVEQ7G6POOYLSWOWsM9yhcWpXubwmV0el5k+ZXZ5nRag9OiHhIpXiB4ICsZx9dOMCXrOuMUXtnzWSiq8zwlFzeBuu027P2UHkk+fy9Ave3V+U2gK1iww8WAyLeQAhLuvuZ6suv56de/1vBwtj6bVswj5vNQcdfO23ZAImbufUXekR8tRspK4o80SuVLri1f478Fc6dRmSD1HojipzsN4eIx9x4RzMB9Q3QchlAydRzoJk+WWaMO2TRoTYdNvN67vnuVznIlvu9BcKhx97HeWLGptN2Y7x5audUiS0eHbbbdZGux3WR2+KQyI7YwMXrtoQXOLrZAbP/rRsSUdGV/Y9zWf4zclIdhoE/p5r/VDZA2zPnqF/wW8Q4BLwN7HeTV0KT8FXwqSNJM9wzimGEI0sSdIOOz9ZGJtw2s/291MrB+oBm2+k49gsu+Fud5Nn5zV6/DtsOAwWXbAM+7QRmyHj7yjXpMsjFTnOs79d8m2DwvJVauSBXWLz7EDZ4VlB3YH1IcJsoM9vj73H9PHGGmZMM796ed8sroqXDo5cr42+dVgLkffggWxc+qk362tBm28P+Cf5gn8So7jkvkSsr9SCSdxI9dFcqfB9wFyIkyZ+SUNFxEV6jdaxSssvpBlTAr5xi0hnPTqF5w+i9OLcx66xGUX/kxNV2DWGNmFPHz1M2NY4wrLAZ/jKa3vbr0VeZFP2XyMbeSdgF92TuYygIUNcgsBE1vBreRO5D7JfZ67mdvDfY37OvcIt597GvFiJhln4owWouFnpSJfFXToOVXmKwo6cIdD9F+oBWB1FG55QggAGyCvkRDpFHKzgdLQEOkcbhZp3BoSmIobB3SfTn4MwipIJWcmoOevnJSn05cqW9x2utzhSxyB7XtrkKwKBtW9QfJYcFMQ3KZgIBBsiqbSEU/Q7SS83SoGPNFUKuoJON1Gg9selEwOoyhaATrYbbdbbTYWEu3qPcF7AoF7grU+GXqn+rPvvMO0B+i+OqmMzkauimVODwTx54OBXHBTILApqF7Y3NfitNoJtYkGZ3N/MwAnweAwuzIW3iiYRDNx+hx2v91itxosC4PwtxBrL0AP5oBV42fxSS7CRbk2rp3rBvg9wt3Gvcq9xb1b1SY5WG9HX+T1aMq0PZ8sIfKN/HeY+wNsCBkhCThChMgS7H6SA1IcMKmgHA49TIEUqjIanfpGB+Ofwnu6mCqUiwME20F9qbIsSn7YTMSsiIsoy2QbElNwwI01Wy7ijlrOlpHjnIXnhtrprL7ZyjjhcJJN8Hjp11qQA66o2xEH2rdPw4zcMbIY8KUHY7FfMIeqazGy1Ra2AEJvtVuNBmoJ25odNoFQj506JcrzVHJSu4cSweZotoetRBBsdhsQBtawPYUFjR73ZEG3x4gF5zYZUiFCKQmlDHFiFOW0LIqKIRnGtHDSoBhNUtoviqVoe3t/e3vE4nBIDgff1Dzc8oMfKDl4bJeSnY2o3HvwGvehuO9+RNGYd4XIw+MaTCaDgZp5oyxGnFQw2n2USg7eHYen8VFCJLtgpM6IKIsGeGYsLRCLQfRjaVcY6Dd4WpfigkeHcNjJOyOmiI9YbaE2A7EF7MTQGrZZiZcYBbdbEL3VnKANSKAQ5hgFp1MwjrQPoHgn55CcTsmB+zHDH39Xj4sQfdeN106zOr4VST73HJGee049RqG/TCQqGaV6p1+pPEqHK48mFCVBP6skEsqE7t4v6fc5s65PAr+BRKYCxGVyIkTqfmLzFin6XJS0b/bDTQ0neFMiceDDBE1F/eo1ZKs/ugfI4veoS4rCr8HfBHz8ni57KM9U7sDEimXkpOjqgIV62F7u6SmvRzHDunWTYgb1GUvIsu01RIU+suCGPwPBgvqQxbLtdUSG/mGBGqGqTEd7ZsRhUQo1M+yVZ2wNKwEfyDQNCk9goJO4Z0G9+56xlsdbxu7ZNo+fdwRE9WK1b9s28rT6+2eeYbyC9/gH+CEms2/hWrkS9Ohcbh23k7t4ZjJ7xLC9tRLneG2Er+fcwS6ULyoM7Sqk8hqYKZXzEgNNip8xv1JidefgPTpl+m6spyMQ6OiJUXfl3VQBthK3RptaD3zwLE4/ksGp+G52FuxikXLIZ4sVXE7nL260ZDvckfKzHX2FDjlto8loxtxhW5Ef6Ck2JuKz4nHeCs2rChC+gfbeKHk12tseHCkmVYUpyb6aLF40QgaULYnEFkXz1U8O5pOphnzA5k963C3+lmAs6okvyfeumuXzCu197cesGR0ZTzX1KoneJOPFwIS9kLccxNedj5LWOu60PLMIYTQt8SM9a9U38DIqOtLFkfej0fcjg5F90ei+yLzIGdHoGZE5mm6guscANBZPvIJZloW//9PgsxpsZuFMcq56OX0qEo1G9qHCzkMTIXq0yWIxVZbZZLPFSk+u3GSxUCrIsMn0T8zn7+jz+UTuqhnqr7CV6qAIKgDLYIiIKOUZRwpwch4BFTLtkGCXcTYMEhHBEorjUQ+g5M0xuTuEcRlDY5If6lVxGiRyEMGBmROa1I8+iKfxWpMs8cTocAF9abHbrDZqFIpWF3XbhofEjpBBKZp4myyJot1tchj65lphV7YtdfJef6DL7IZ3MjmNdq8502mmxGs3uszJJjNLybabKPEcYfkNNAbTJqeFmBwmW8DvaxEliTisgs2jHginCOUzCZtktEpWn8v6ASEOi2Bzk1mW2aGY+rzZ1N5u5wnxNJpLZZPV6PDwjq4uPaUnz1I4zqPrV8SPKC86hduhU2CHxB/5aSFD3Xqvm8TlusVfh6fU5myNkwsB6F8ap6tjF8XjF8U+jpEL43H10lj1/msEq5XvQxYUuBUjt8bvisXuihOmRkyfrmr+qh/oPioKbIvtj4FTb8K04ycikHV8/JFY7JG4Pnff47/EWzmXrqd0qLMAuF0xTY8aCFLHKnJs2X/qqftfXLOqUCoVVq05bfXiRXg6YNHifdWA+maczIFHexy2mVP3v7B/S6m4cs3Lx68qlmZ3tC9csm/xwvZMpn3h4n1LFrZ3qM/jU07qIOPaqo5fskY/iKt7ijocMVmz+oQ4MnLZuiMTcmcYgXxthJyQIOFEQn0j8UGCFwHw/iNBX4f5qj6FM7egujsTJtIP1N6bCHqvQo9P4UZ7NQZJ5df6HI+PqE1Kni8kyTZsrNoo3rkq/+bbde+TY9T5jN5E1rYQbQPJswjTvmX6YhsVElIU9U3lA0V7AYW+gTv607Ds/wYEWdjnu0gn5XyHfvT7fA71DcbACDN18zc0+hdjup7Dy/xd+rOHAG+aNVXaeIiTK24CxGW8yu2GrQ2xDdyzdhd/VARHv4Ei+WfR+87Xv1Z5ldLPiGazWNllE75oMHzRYOJTRfXkQoHcVAQ06GQpGpXITaiH/WlyTuUNnl9gMSZF8wJR4E/nrfxpPNVlo4/Txw7SJ/vXz5b00kOH+eh0fGKAyJUHCkX4KzxXd6OPtuqDAXfSp59AeZoB175kcbCoflgsElNx6p3xyN+nf2A8BNSlQHwSz+0g9y/L9E1qdCuEiRA/wzSybkF7f1tbfzu5cgH4EP7wiAn0iQgjDKLaTb0Vdaja2qLaDaIa2aDduAl8uHZMZigHrOGIHanPya+gz5Iz7mcdbv8S5rMLsL1RbjPDnjWRGOptFdjeZ6xufrDQStXtL0NQX7tasFhmTPXaMiX5CPm0bfvG9Vu3rt/4203rtm1bt2nb4iuLzbLPJzcXr/wSBP2S5IcgKTl8eiFCpUZPTb7PMX0Wn2oIHnfKccEG/TbSIHskTwQuuaEmqD7hcxS0UgXB7qa+0GSJsO/tw2VqOkEfvw97sw160oy8ViDfiSymy/Sc+9X2++nJ9624bwWG275JT7r/uPuOm6BX7oJV2gqYP9IryGBU8srkuPeTAYpToyjVz42knPWLxJjOpsqkRF6xe4jHTp5BYpS8p2QJsshNKGRwtaCKWQs9SZZlFT2ZfMlP3yfeBo9Gt2aVH5icJrPLRH+gyyOQiHXoxQGd/JKs46r76O8PiTfU4wnuaSN15GS5FtrcHKMvAUhviU3cySKAjg/FyM9jRITQP2JPMyD+ewb6T0cKm4xWwfwnY+qHUMMUm7LXtXEZpmO8DHGZmawvoogKkFMK4IyCUsxrIkwR5qrIxMJMWFmECa8gidIG9EpO0MTuZcYukxXkFuMS8THUIG887BpVg8e4cktLSwuhUIQ4Tzmz1W21Gq1uj8XZ6PTM6naanE6rLWAOOnLdTuJqSOe6Y6G2WY1O0sWb7UGbX3TGsnZR6DQeYYmrP8m88cbylSuXvxFwttjgVRqbXQGBP6HfYJLcoqHNGDHndwUGu3tj4ZTkd1idVou0OOU2eZJsfr7E74A9oQ0ovxVMgoD/g+x8hqZwAch1mvHJO4kbeUx+XNGoUYwIPOTyjAeF61x0s4I87AHIXkJ2lMZeShlFGvEo7QPzmpIes5UIDnfD2od4h2Pf+qDbYSTEZnLHm48a6ErajC6Tdda8oflWYo2aj77pC0dnnMQ6f2heT/Nyg02QDcSw02Az/BLufsFm4GPupqBiEwmxmtSzDd5AOHLmAmowLDwzEg74DORKk4UQItqUhmabcbklGwoYifrNSMpy/NyB5V0thIwRIRjKqp84GRr0QsNXC8Twe7hDXYOmiwfzbT+dBzCI87LTkLKZaMquBDa7S8xGwe2XTWQFWWHy+11Go0m9wq7YyD9NftllFE3qXZBhNLr8fsyw6zxyaPM7NArYkQUgG0K1xQjTRMWb92pz0107Nx2kbnIeZm4mJiYmsZ8lzTlt/qkjyVSanPzZ68oBB9A7smw3R9ySvGih1+bzWW22hGfuQi/ZkijOXtjW1LNI8ZFBo8U93Nw622MWB0ykeYBk1F9u27lzG8nEvCW3VTQlC36nz2D41LhgCclmoUdssc69NXrs6FhbMhcKSTZv+aS83yZndRj4Cv0NF+A6uB52firHgILPWOXXaFCH4dwO6otoqsR0UnljUle05uAVLRyzdLR/1tzhWQOj43eMjw7MGp47q3906TGDc/pP7us/pW/O+bP7T+nvO7l/ziCT7R2/ONOxJNOIBxlL6NHfNMW6i+edfG6pO64o8e7SuSefV+yONY00Niw/YfkJxzWEQg3HQWB5Q+M2Vpt5I3pt8PR3+z7s6yHASEa1lYNsVo12hVeSjSJjuSM8LJSiRMtEjgeUY4xeeHOtOySfmEZFcoKbCwSTpZysSfHSgDuSZG54ObzO7IHu3nIu2zNn/ujR46P9PcPr/jvrkeClAq2OrsDcZMeSjuhnjx1f0NczJ5NthI6YM+Tx9XapBywhCz1HF+5VPmOhV+bazym2bYK3lKVULtMQbopDZ+QHdx/b5WgNQIOSJxuKQsel5v6d9VOhf35XJt2O3UF77AGLpfJpo8djFwSR7rKEdRn1d9g4pwFeHG6UUYV+4vxc7QBTSR/Tnn4Y06XzYUzn9PQtGD82MdDZoI3Bko7qCDZMO4KPNHQOJDKHGLCJdXclzej4vHbmjavTk1Mm1WF15SIpTw6V9qXsKVlwV+HxrVPwsNbKBQvoagycgkkY68h+mAUX9n2IKeANvUIaamMTesb306c5J9Mj1ISUsJ9qB0iFCdh1Y9Dzrse2gufJck/wwDtBD33CEyR/u90TtK+gQU+fol7tbmhwk20cwkjAU84+xPmSmeOghxY91NHAr2l4hzqgq7g/qQ4w9YYnAUVRyzGyEKD6vljs2Xj8WYDdQSDsf3cktYgzEQV4QGOsawRxVZf31xNjFoe9JAO7yQJuCbeW2zhFNzJ+iLHyTqsVOdPRvqpzQyazoZPc4W1s9G5E79ijjvoCpEC6+sfMxgy4T2LyBl9jo++Yo46iRQxs0EuSkzQ105D3bUx52xvq/zEx6yqo52h3lswK9P2YmGpjOt7+dcZvi8Lb9x56HEmcsVTjSOWkmRSvV19o5WoOi/F2jfxVo9qdnKY+z2jgdqCZN72MwZcZHXyxegtL38BiB3O2Kj9GEjvse9QXkpgMGAKatkCVj/EUH2NzMF+jxXM6d8FM+YVTB6L+kJdQOzXrNJSTR6jJNx0GaTst3tkZ3xPPZOJ79pjsdtMes92uxpQVyoQjr8Y6O2N7WDnMNWM5PoY98je9eyw64+9vjPFnSRa2ZuJFrFWMZ3ps5iI2XDTbbKgArH6I/jyWx0r1aHlYSqcffqnzWufOrOfQugeDQRjLsPPqksKYWpqxEz5/GAMSD/+v02czCQaz5HVi0Czwouw9Al+z8qTNJgpWk0JaFa/VDkirVVTUFxVP9bzI+/zTuq0DPCl+NHsPOiOOsZQhyBkGGAKvUCz55QGa1LWY6gSMfCra0xGYTkaybe5Ab2fLVTdfEIotaFqWaU4vvLE/uOFi9a2G9mCwvSHd3Z22e70NXi9vC7b3xNSUbjjjFfBQJpBiuPYryeIbwUD7aXPnOdYvOK47FLbN88Q7557YfX1De7492L2kexDbaPDqZ2S+WMP3Ch9RC2KouKcIjoxpdzU5MkIfLVTeKxSoq1C9bznwEMdVz+Dsrm9/ek34NLT/YAGbLZBXtOavnLZ1bb+5Fto269ZjgP6rcrpQaqhxu6qaG3/Zg7ysPeTRPRhQJWh3D1yfifrVVf4ojkLUT/b6o3sOPMQvwvaRjn6TfwD2hjDbyxjDX8xQJpKOoGaiXD3lwhBtTAbcqMyyAB/FlDTpVI7fuCyUdffNG27YHvFLvePj4dWdq9u2nrUsHG4h21u3bz82EmvdUVNsR1iSesfGIqs7yYtK3Jxr7oo2m+XgjvD4eI8kRY7vPL41HDl269ZWsr0tFj12+/aWHXXFxsZ7fT4oNgG3v0O+UnM+qAoZz2ymB5qbK4ZmchtTCPOw/vwZGys754D+DDFpW5nr5ka4o5iNiQ0AyfC8PeyNXmUCQwKkVzhMjPx/UPKXY1KyNSkxj+yti4z7m9qa/MyrfOU/WYy+JOnJku6mBtXr/+9L6Pjfz8nP68bhX+jv56Z/m9rIxUd8kiqOdiuzodEMe+Ji7iRuJ/c57kvazoh6P3TygHMvEaeAMCVZewx6wvJIfaGDW6KHbokcsaWbUEca9Wbu1Y9H30fuRc2Z+xiG8BXdNsy92rHre+9FdSj01CV19TBQX1GvMLUeb0Ndnfsm6mhHqFmd+3RTHvfBQ+Bz1Gepe1j5quoPa6G2pl4Dfg/r1+Rwmk2eV4CmQ90Chetk5xqXsXV5PvdZGJk7uQen0TfwitqhYKaGk045ia4WDwWSyCHRktNlRdIzoFJSkXRxJpZStFSW49W06mUpDzkCRvJFJuXES5Tyep24xpJJ6KUB4KfFieYHJ+5QikYnjlT+tqr0ULYSh8/roHaf5CTW3k9bk3GT0NXj4iUbsfM2n8HV0yWIiaaffpcXeUe+0yYMWivq47YBwZ7JOyCNkMcJFXj7vHl2XqAP2jrzDoOR/y6xDqo3QAJ5nIoUMp8VBmxQ0GiwlbrIYvxNO6/9ps/rS6fvc83KGuBnLLxN+r4B8qxNCRGe41CqFpWLCF9KKQaSTHUbiCnI2z0GIekTTK0ZsznTahJ8KYPBY/+JJ59zG9yNJnHsBKPxhDHR1AjRXN7jbW32EouZwp3ImBjo6PCLm8ZmY4Yr02GnZvMCcckmkz/TIfOecM2P8UTcKfiSAjTP40+ZUjmz/vOazuMr/Jkwb6q2VJBGOchGCtq9IdOyTvkyniupvIM+hTtcD+RpPJ+v/DpfvfMpKNHESvw/cry70sT78rlcvhLM5fM57Tlu5Rfx6/+vnsOJ5tPUGDOi9hs1Rn5TebNE/lgqqb5S9c6vV4IH9mMJfn5Q6Tuwn/9kCf4OfAP9Kv7wJMNZk4CzbpiQseraqfgYg0Q/1cPUMfEBRYWRXmUd5lcVAyjDDktJxOWrJxcAA0FBfy9luLCRfFGT270KhAhqKJtwG3lVU9488A+H70nRjdwK41P3MEXAjwW7W+IlTNe0mJ/8BqQTnxMyXD4+xTSdX2X7E4fHVQ88BEQOi38McUqGfY57nhQFwe7xGJ/Es68+6rMRzuF7ui7DZmQZH3O4ueCZ26f5/+b7GQ7vz8uouiDlGWeIKSWhyq6Uz+mxqsUyUfJpenqaCDEMOwG8O2JBtImQ8wWjuOIPduEGg+EGwf6HFZSnBv58g8UAGaYVf6CENxt2C8Juo/33x5mMwvm8hZ9FbjDa/rBChJhWDhogWE5r448EG+EJP5kLzfNE161+kvGJR5kUAwasXopRwIGT0nItTk/zAhpzqBnLSUVcUsqR3zo8Pjt5rkp8WaDnzYRUSTNUy6FftVjUXZMDx8S55DOWEJ8iUqOXVKmQ5yxQ0+w20+eq2D+SAxZLGGq72Bh57R9r2rrfgOqcxtv9WOUfpB8BBOaqqGac7e9IPjLiEY1EykypEoeknCqTAoyJC/r3THLu8gXzWtsDscDg6bPVK06zUDNvi1vvtVNqp380CYJTksQz21vnLVj+Op47en326YOnWiJWwWqF1/HaNb6ZgaP7AM+2c259pcaraxR1q/jNQeXA+0qQXl85jV5/4EX+eweG6D4lWDlADbDy1O7WPk3GpPJf4ykXAag1pJ0RQaShirM7AOgDAq1MSKm1YUPNItkNGXFWxF0q40kQJia5pfkoOTAv/TWUNj0Qy9sGbfnYNzHy9a9bnDaz4euzOiCF3Ps1MVawthQeFMw2J0+botGmQlPlL00FmopEUrTQRB1NhQGntfIXs5MqNAppkFN5LcZTp5k6rE59r3gc5pUH8LHMpEafpJQyFB98gAoSkq8wGpqORjHvY1Zv8g6aLCKhC6NF5e2oxDdCjjr/5i9f1LQwtvNL6vu33u+PkK9uQ8394aPO2/PlC5sWQTqxQnpUgy/bl157QkrZMDq2a/byu4Yj/v0VC8LGbZCcTGycP7ZzzvK7RqL+/UyfV9fLF5lGO+rLdXBZrsjNAuxtNseV8SHhcVCspJkGySPVrUlQRLS7WdavpH7Fp1zPDm5pIH2bd27dsL5/wM6P8MQ9f/7p2y5VPx7BP37RSGXjZpIAt0Xzd60cKW7eM8KXi6uPP2vzZsdFW07uG+DJcVu27N+8+VubNxe3bBkbmfir0iyv0l9yfsR7cRXKEeqgIur3QYcOUC8qz1e15nOoTZ+mhoEzx909awqGWRt61HBuxSx7Yn5WXHLeC9aI9ZxWS1SyzrVGLddZrfQX+eP7jLNP3LGxl8+Mn7Bmrn3e9jnqD6xQyuqPQCnrdZaodUIm+h2GZ4W5welwqlSVsAcMR8ozKYdfqp4Bxq2zaoAlRX3bcZBndTPpMAbWbTrDHQgkggHTgpbmkj9YaM9IBm/ZYSclu9Xfbm1YckgE45tEbsJq5LZM62gunOhqDgeN3mw0FHW5Qn6vN21XMv3zJ885PKHzNEqAKY4CrjgjrsbBuqhJvFXP6TAQUMfb4hOoATmtkHEu7HHr1uEBhZ2WkHWnhbRYQpgQxoQwXGpFuTqRuFo5EmtDPcGC9Xwhy06rZZcl9FB9tF9XwdXPeF/Nx5neaFmXfuh8KXoYW5WCprfn094a31XGmcakqbrlkkQadltAOJle4wCR6Yctw81ySzmiuiLlFrl5uIW8p7p0IXgup55pscybZ/E2mteZwTV+UB89AQA6LVF/A/PtfAz51a5IqVWWW0sRJgYfqsrIsb2XLA2W+fPNjV7LWgtrY0r8ZEIlu3qJ3cX77eQiaFvncz6r8+eqvPalgPN8gvtUnc3IaWkreVqh+Uxqz4h6u02zG/ntEN5C3/42Hmz+tkuSrkyQ+Yqi7k+8jWOrnj9NMXJfuCsM7tv6TcsAj09hVC8OwRHJVa20DjVz70iojyQSZDShztZL6OWHtYax9PC0OZPnH7U9K6PBtoN2LZQNOgFWoBnnAT5DOgHN4A9W+J5uI5tXWN1jFLo6S/bRLPl1ek6KVwZTWYf3zCctUUvrOdaIn21ccw+9wY1mlxVydvvRG4Zo85wlw+2C0Dn3GPUJqxVqSmxfnGuNaPT+K/wLjKeGsN7PNQC0TnApeCN3HCBF3K0UeVlMK3Al4coXwXPD5XVrJjP55ytrRmjDKZ87p+Urm89o+SK58eaW3fG7Wy7e0/IJvrKGrqu8RRvoBZU13ziZNgyr5l0tt53eckMWCv0RChE7FCMLodztI5W3qvvXTfwc3UJ0lEujXroXJY9an6XZEhVJmZRlJ482dtNePCQEd1m//87V5qLHOZ0nudqdlbtdrs+N0aHxyve3dp1V6t68uTt/5uY/JAYHE5G5g38KtLcHpM523ubEku1QBWu2O9vGK98dJ+edlT3rqs2zZp2ycXAWXTeYSAz2phIb2+VAe0tjQJfBf4FeiCervDiwNQJ4eMqttibbX8mdVfF7r02xq+/ayKM22/vkTrMgMNl7r91O3DY8qsBg+V/5W3nukDay29iIsC5Po3JPzQXj00m0K10tw99y4H36EzQ9s+sbiW/k9ybujO9J3JK6Rdlz1Z2JO6+6JXHL5Xcqe2m2ci3Nqg7yF5478D5vG6xsuwOKf3evcucv9ii3/ASKqY0QaYJADO50NZQ/a1B14PO6684PHHxm/HjuFG4z7Ddnc5fO/Fzs9Fr7dSq9qANYDYvTZZTroFX8cFpr43ii/FnUxSQdbM+5IrEgAe47h7p9WxlVwNE/o/ZLUpdW/UrXfkki1k9+xbT0+xOon687dRSbx+vCuoC+Br9+UD8u/0/0Gs9OjlbPW/cSZriDtx1GE72yVOsLVEltx+f0n2AymL1W9UGNmq20Wnzhf/Xdx08U/V4rYVK4NsAJuAkd3G/Ce8u6FbQRmDVbJ7GVSZh98AkPb7wIs7wYJ9kyOxdWgyDK+Zx8cCoSFYcqW+J9Ue0gR1RNVkPkVxPvUiicc+AdXsar8lKfQHhecBo7uwSXRegTRINRmGEan8KGK+9Vf0IjBOukVao6rH57mJT7DXbBIAhdXYLVKfQbSKU+Qaj0CTYjbzR2dhqtTiP8FtH1Pm6lu2DXiHLtqBFMqmfq9aP2QlzTVanKW5PT67HcNXfbnDnb5qovzd06FxzaN84emwVHWnJ4V786jBmfZrksZxN6dOecbZdjTfCGsseegGngDR0ytdbWeJt+CvEo7mjuGG4dt547jdvBXch9BrWP6sedGbk+aHr08m7tVZCQrDuZMv0ZxboIQcsvKLbTLMBgj0yEyM3PTJwOdbwzcXxz8vzoM5fFTGgYwLsqdnrskE69ncTVXyN1jXfNBsFEiP7I55g8gDq7vm3NnnTlZqgAhekch/dSTfOw6l3GVBLnDXkbPZfNZvYJLhkyOT0Oc40dRT/Ac7S5snySi3+407tizTrJEAf1awtFOPRx4JbDn6e9Y6NIjbw5aFq82Oh0WQ0G40bRKlgJOdTZXI2/Pt1Z2B9tFP1mgwlaEgSDzekSNpoI2T/1PO0UO6bD3CbuBO4s2FcugBl1xQz31ClH1x0aEqtMsAa9jGTQLRroJBLbWhl2V57ZpDu8ZY+1Dk9Omyk5NrsA9fM6st61ZrOiGA0Gu8ttVBSkARQFKQvwGzbGbo9NcUfQbBn3OLJMY7f6U55Gb9bhUX9iwRaNbpdNEIyTP+AF3/zfOO1eRu9sZg5k0p6zi4sD7qidKp7+WxaTVhfrLBFOgCzKbdm/Zcv+F9asxFMsK9ecuvKmlStvenDxQjzBsnDxqmy4OQzOjT/O27bsf/6RLVjsxTUri6XZK/7rmzetxGIPYflbpXC4JRzeppn9n5D5P15ns3nmULYOn4gd5uhjZYw3Hfjw0sRDCXAIbduQvGVasoy8LaSq5G2qcPWI+gY7rarxH36k4wGlGWqjoJRaZOenmRZolPAdh3mws8xhiZrVly1xy+VGanR63MbTjqBDscdCPSGreqeN2O4RvW4rNZo+p9OXh8b91s7syVFO753h7swMBjuITkj6D4vLqZtHRi5OPJWY4tSP+o3GfsFuFUWr/Qiv3HbgbboIEaA9Ex45qd/oMfYbeMFqPFe0CLTKL9oHfRAGCnBzlarWmcpKMoFDUiqjajLqVOKZId6hyQgyBLEPfbttI8a0kmCqQ4f5fENcJ6gpQEDyWKgz9FqyzJeb1XPsjhs9drsnvAS2nmabtZEYnaLFI3Sbgg5xN9kuer3ix5SnoQC5GGngyjXoJxIaaUu3hrtCrnBa+h+6BHbdJ7vTn23rM9nv8OYL3jDfSH6QnsWPAJnyf0TeJBZNvCMu3hI0X2IOEtL8K7O03u/8E1LFf0J6uSv8J41Q/5OUDruGp9h5r9q5nfFqw/+k/o2MGdiNZZZoWmbvn32EHe+joQuH/kedi4a3yWO67ZeqffCpTzcN7llmCIiCFuHixSPY9L58wizwEc11v6AZFEbTwbP1M3dML/SHMzn7MK1JybpInXxtR5y+GI9XWuPxT8Vin4qTltjlMXBw/xQmVe6t5tMfstOML9V+20R9GfEOnXb5qS5jxFEuc73cQu0bOVPle7CDHiKNHHRYTcof/E0SKPcGnjlT2UdBSFj9K7HfzRJ6WMLD6iqyl7Rj+FPonX3SSdqnR15jJ9VcHwO5TjZGIM6KR/2R4Y95EfP9kQjGoyMfk+H6+MX1xXE4HOx9v6vTLPUU7/HcGm4Ldyq3a2YUTL0+67QRvk7VQz5MjI/FetoDgfaeWOXP1VAdZaduSxCPoqh/Sky9by46AuGAg3nFacIzIGDuQDh2y+Se2eDAuqGgwxH8hgP9qUFdRo02fyV2DmkQevJYjktOnDnqJ3mpipnydbZJWAj2DtQqhClTzMMlo5cvKkK+jCFFTLOcMoaL/N7qpj+iPe7KyX0fTQo5lhcKywuBWcVZd5KG/hSfWx2NrM7xb169andYgxThML5pmH5+Mow56k+XLXt1zZpX2775zbZXH3741a985dWH06+2PgjzRa7ZA5NA40za2zmJ28Zt5y7iLuau5K49hO0dzSLUBOqJkum4pjwrIdaEcKKTkBw7c1HUJgDCDIAqNfJrzYxWsnY+FevOtM7s2waoppeuQ35qI9la5A1NmU2ybBqQ5DmRUSWASdKlLOpt9EKkct9h8q6pGmmv3kn/wajtK7Nnd8bWxuNrY/nY92PoYrrP26CdEzWS6VM2DyUYxfZZ5MDbh8t9BBs4dcLY3SkTnl99SWFfUTCiDnisedJx3JRvgdTqth/7b2i3C9NiQpN07uFB4rsx0gAd91aMNGu7u+avZrj+kQyr4/s9w3qA0Zay+ozWWaRn4lto18J71mDP1a9Psa0IQqKi4fo6BqOpitVp6Z8/esGCBReMLlzYva4b3MKNC8sbyuAWLtQzdldP4tFrRy+4/oLRoe71Z63vHtJvLOk9zRqhuQaGazr4vdw8WGmrqjgZ63vt61g6C0GsM/iqb9DxOiBaF5FRp1Quy+V0OS2WFTktRmltTK5+jmzTJoTmm55RX2JQvebDUFXwqrmRoSH1R2GvNxxqtjUZmwTF1nx6fZTeDfiYulf7ChlZBTgBYhF7qx8eW+VNhpyVK7UvEeAw5SqLSKekKFJYTLjTaXdCDNfFqvov35qwjz+dhPNI1vFrOUWeSQlnNbBtLVqrefwYzVrNMevnnzNv3jnXoDevsKpYXLUZvRmYrFk575xrsRZ4I4XVp6wuFFZtWVXgar9FgDJO/IrjjL5Ih7hkUscl02lcAChwO4jsvmqI5CY/CZT2o0DuoCUydOD3vJ8gT3DiLMhe+hf9TEGZzb7VjI+1GTCC7f8Cz2HKAQ/pEGkHnyKQ8pPnyeXJM+amw9nmuRbRnG8hcvPgvfeyG3r33n03WVQbu17/jNuDRfwCW3tU4/uu0mnZvTrfdxWjZfcmi+WofwviW1v80Z51Z0b9m7GZzRi5qTZCytoX3fzaTT/D/N/83/k2/cwhMxcZrWpZR7VPDSBDSvv4ikbtMptgeDmZKj7qw1Akt2g6VRqk6QxJ02NcpvWCZH/RKZtF65fMJpqk1kDE8V1rg+1xRyRghbjJ8l9W0Sw7X7RJxvXmVsNSR1dk32U+L0+iXwmWgubbo4LTI172cGPGwcdE9xM2J41SbzhsvcEctD/vDHmoeIIIDqhk5/P2oPkGazjshSJO2xM+x5o7GhWh4WJvodG8b5mdWg3L9jUWfeLFDYZooMoDf53/Hd8C2M9xbL5s487jPslxQiKtpNIT/2W0LF79xovkZ8eCU6ID3xlCeX8ZD2Di2s3L/jJbuemiXpUd05Q1e2yyn4e1W8pQY9nnhxUM0wQCsJZx5tQG2AyipqTEZ7PNyVDAzc8xm9JNVxy3ds0pq47qCnpocKRjrC0d9lsEwUJMTbcZtoeNJos7OgAgyhvyN3q6vHZ/a//C5UuWzN8aUYzGWU6fPxJvvj3cBlhca6Qn0hIBdyNcJNIWvlu7R5ZFW8Lh1nDEYGz0J4Ihn/GE0zasXnZhKOp0j3Yv68gb+WtH2mBuC35byBrocC4Z9c61QMsFb7NAeSKa3V1nkHR6YVevw9YQOGnuwqVrVnplX8gjk1549UgrXJcD7Q2/ER70RSJwm4O3SORmuMFvV+n6x3Xexky/y8JYf7qylibZPTyH6C1L2PLoo7qm3/MQecFyBK7EGxbLY4/heUmPW3zeYoE6Vd1gxJ9RaqvUfPVwLeCVN3Ff4R6qPr9Grx7Kpt/MjTbzMmq64MZTzstShMnx8Ui2LCpp3UQjm2dMmxKK+LVDpzg5mX0//Wypj4GbQnnylEw5SrC8JOrfSS2W2fY18f5JXdR0LvLVjq+3/Hy90eIcDxmTAjH6BePnYxkipIyS1eG0eeJ+m7dtcUZICdRgkuwmh9FmkyI2f/Pv2uRIRF6OXpuvoaElGLxKmCMkBaHZMIw3NhRIY+KPVwkE7XTZyJN15qIvET7Z4GpoVQRBMEgOq/1TgpCIC4LDYnGKBpNgcXYnm2JGwWo0U8Em8kbeIJqc0VATCYRODQcC4VNDgfZYXwxcit++VRAeF+jZ2/FWw7+vUtYzPGcluPNuWVLcQPTgiSs30kAA+YAwEpE4Kh4edfxZ94vX/azvOLjIzvt3wN8R0MWT1Dz5SfYG9W9rEzcQy9pEt9rUdw/pP6+Pm+C33lYDq6daHJkGWpuJTHgimyncbPqJTk+43OrHI51TPn73cEdl4WJyf0YdIza8jR8JfFe2032ZykKykDwApdX3yf0d6jij7SflJbW2qU7gdnCf4C7hLuWunpkEZYb2T71Vnt6/1dSRJC/TWDLN6RyoGdfShDK7mVAG7jpDazfWAO9YzTwptKuVBpS1o3F3tendiKbWlq6aM4W54ZtRf1/DfYn7MncXdze37///vp9Ws6C2JVL7GSX3f3CE1L3VgyZkFWL+Gh3gU/cErw+C+5/gSBDcGHZn5R60jxfZj+4/OV4rq+PVrP0Y4SLqb8Nh0gg3/CBfVedQ01/w1Xxjcaa0Rd13ASc7m+So4Yi0xQ3Ib3o7cbui3J6YAUVxYuLDBLgPdYVB4SCaveffoNXlmVHk6qwqRf4TZlgetijc0TRShPwJdri6HeoSJOlm1RDe3NTvD1R5DItnaGGmjgdUxxNmZg6hIjvFGDnCJ1TfjZEL4DUui8XQdF2MijbTUqPFQA1LDTareARQoX6MFPv5SLSq1yw12USrDepRg4XRdH/hf8ojlxttaQ3UvlVaxKMGiLCk696NWSQoo3r4Id6RXxYvx8H9cNDZ5przTKwUp7HuOF1V2esPR6SfeRw9A1an8TkpElFNTOlHfS7B2+PdcbUZPDLg5B2zo4UY+TXEjopKarMUtff3W2lUIi/7I6qqGZidxL+iU/g+Z3MXcp/mruXu098kSP4jCJiGa2nmfnyakQ7dtpWGbfkBB9Vte5RlDZM62Ogx5DBcS/LJeXgUWWKmdPoJls7/K6jX55syvKHLQHjR7bBYAYluW+G3Wg1ZAzGY/FazkVjbYo2tLxigjOHhbFfXxmw262UnDu82BAUhSAixmwWD3GS3SAaDpH+q411iJqaDEK8PR55qrvlTzwk3GQxGo8lkMZrFsBxu8pobHILBKpiIwFtFiykoN6Z7DX83gOvpP6a395j+bGOhEdzxhk5DwnC8CDOvyYDmLL2nQbzTMGGL4wWg5avyjEHtS1EHG9Q7dFrNsRmNPmecWB4pV9Q3ArCSr0r9ijBjdyK1zMjwu2+6ic7CwLcwCWLqL3gyQOkAFW80iKLhRn4jRAiZjNM/R6XKp9mH9M6RosOVO+nNUxNkKG6g/TzPNxkNMYOoUF69n6UNUD3N2ER5jafya/45Pg57eIbxzzgzmZitMmEmcUmpkGSMMrT1yk7aaF/6IDj9DsEjyjM1+W+oz3Zk1Up5sc8LhHu5sctL+HRFFL0+O+EdPjMpWUTRolbMvkafWX3WIv630pNI9Iygl3AiWHIGEgnyNCmlF5fVCtRXn01DW3zqzI/tPi/WfVarW8GGSMnsc7yh1wdv2NUYbXQmuhL62H6CfgCrtgHobdQ4OobjvJNUW6owqZsL8GrCoJJ26rvGvlKqkCRYUAvXZXx+l9HttsPM3GVp9NFe9C3gec2Vn5LrtSxRPV0PEH9NGhRSPyARrKC+Zg55yfUkIrrdqIGhvo6Nbd0F1J/N7THuMnsbKy/vsngboWlfowWauU40GOxut1E9zagH3qlJg/I2EsWi0JI3RK4jUdaUW2RxTsd9b9e/u14rxxtnci3kY51d91XGqfyoui8oyTNMq0edpjf6HUPbNJuYOZuxMbqaff18B/tC+aXqZwj0ASa8xBKi6h+IdFBCZTPDj0g/bPdPTdzx++eb9Kb7xj5XG9kTljbi74HXO75H/3HI7h2jQxFVABTon3BDFIi0YMsTeNCzNfqM1a+6HlEWiHzJOFxE12rk26rGpSt/npC3oSgPWQFMyWHHCJk3TOZptgv4RTMQzo1Uy6rfQjVDTff7Nf5V3lcnX+5B/Y062XKdkoa7dsQOhXjITDKXMIqSLquS2YaH+0dR3/XSK7tpbtasyk+7L83vz+X25/G0KP18NB8Fp94XLUQihehdLvkUp5/KrlO8TsFdDfscvA9RQdWPZ0vJ7/OqnM+Td/IfczVVyVKInHRQTaPWos4H/1GNjYr6962zTXvRhNGEnyDUI3ch7CEXYgrmcDrv8j3+Rma7t+OQek9aj9TI7vJVuV2KWrd8a8uWbzHlJTTFe5rDcwl+kehib6P3EhRKXeKdaqj3x5jY6L24ppgul3mCfszeyccFpn4xucZo+yeaYOtvOlBpIltf2pT/Q2HjS9esNKxYmGxqStKHKj+56iqaV/f+7W/6fnkjwARXPV7/L9slrjs9Un1jfJVScdXxp61aotkcXvLQEk3Fa4n625hmpDj2GhPnxbZ86/lvbcHCrAOG6gpjIKM+hwVvwypYVX/29+jDdXO73tJJsu6bnHW9lere2g2ONGt3NdLE9yaTB77fRDd0q/d2d5Oju4/tVo0Q+KhbfQ9QpCTH/b9FXBCNAHicY2BkYGAA4hlWXw7G89t8ZeBmYQCBJwf88xD0/wUszswJQC4HAxNIFABLOAtNAHicY2BkYGBu+N/AEMPyhgEIWJwZGBlQwRIAY68EPAAAeJxjYWBgYIHhN1DaBUkMHTvgkcOHYfrsCahxQFLrgEUcF8ZmBro4uhp7HHbYoKmxIeA+Qm40JtIP9kSoweYOEDbA4S9c4YseD/Z44gbZrHosdtvjUIvuXgYGTH+iqQMAQcQa0QAAAAAAfAC+AQYBTAGWAdoCUAL4AxoDrgQiBJAFggXmBk4GuAdOB8wIDgiSCQIJXAmuCiAKOApeCo4K2Ar+C2QL6AwuDFYMvA08DbwOfA9MD94QZBDkET4RdBHKEfoSXBK8EvITPhRAFYQVuBXsFkYWkhdCF64YfBkMGVwZxBoiGnYa3htAG4ocKhxEHJgc7B2UHiQeTh7CHzAftiAKIFQgfiDkIWYhwCJkIsYjQCNwI54jziQwJEYlCiVQJhonNid6J74oSCioKRIpUil2KdQqKCqKKs4rMCuuLDQs5C1ILZAt5i4OLmYvCi+GMCwwhjE2MbIyajLAMw4zZDPmNHY0yDUWNWg14DaINvg37DhgOKo5PjmiOeo6jDsIO9I8KD0WPXg9zj6GP2o/xkAeQIxA6EHQQkxCvkNMQ+5ERkTCROpFLEVWRbRF7gAAeJxjYGRgYFjC8IFBigEEmICYCwgZGP6D+QwAM1wCxQB4nF2RzU7CQBSFz8iPERIXmrhwNW6IiaEFluwMCexZ4LqUKT9pO810IGHl0/gEPoFP4BPoS/gCHsrVBNrMne+cuffOTQvgBt9QOD63XEdWuKQ68gX5XrhGfhCukx+FG2gjFG7SHwq38IRn4TY7puyg6ldUHbwKK1zjTfiC/C5cI38I18mfwg3c4Uu4Sf9HuIWZUsJtdNTLyJnIm4We7/U6tnlic5/E3QNOzXKbRk6UbDPjyrXNdT/oiTMxuXF/LcrdcuB9ohNnMz1mL5OmVhfObkzsg5X3xTAME/GD2GYYwcEggmdcQGOOPeMaMSxyJFX03GN0/90pc5fY8kNFrD49O1UzZjqU1Ael0UeA3lnOhDl5lXc+RYkd7xnQPUyguRwrMtJY5jKcISVrFNXZhk5MP8Cqqir4k0O+yVl+UN2d/QI8XWjpAAAAeJxtVId22zYU9bVIipJsp4nTvfdk2yjp3nvvvQsRTxJikGAAULK699577/5nHyipdc6pzhFBAA/AxR1cWV2Z/7or///7C6toIUKMBG2k6KCLHtawjg3swhHYjT3YxF4ciaNwNI7BsTgOx+MEnIiTcDJOwak4DafjDJyJs3A2zsG5OA/n4wJkuBAX4WLsQx/7cQCX4FJchstxBa7EVbga1+BaXIfrcQNuxE24GbfgVtyG23EH7sRduBv34F7ch/vxAB7EQ3gYj+BRPIbH8QSexFN4Gs/gWTyH5yEwQA4JwhAjjKFwEFvQKFDCoMIhWDh41Jhgim3M8AJexEt4Ga/gVbyG1/EG3sRbeBvv4F28h/fxAT7ER/gYn+BTfIbP8QW+xFf4Gt/gW3yH7/EDfsRP+Bm/4Ff8ht/xB/7EX/h7JZ5a5SkZWFHm40TSROWUOBI2H69XximvTJlpGvqEtitRyrQQdkuaadmTwovMkabcd0ZWyUyrkuLKqtJHufKzVJtchOXJIZsbSakqq9pnSiZqaEVBsailMt2hsUVW8LyOvfKaIqdGZVtIacm5jvO8hfMqbw9Es0s8UZJMIiZ8um272lVUyvZU2FKVo8hxJ7U05LXj/qpQ7dzouij3L9oDi7a/aPclWsxM7TuORgWVnmQ7NIy5MwfrxajlxWAtN+VQjTKa8CxjoioLsBOnGYxNpBLajDYP1gx0OMtcJXIm0NSlTOpKGyGj3FSzrlcFZczziFIv3FbGd9x72JoB+SlRmaiiMtb3+GBNzUH7ewsAA+Foj9DMEItWuMx5Sz4f7xwaatpmFuJ8TPlWJGpv0lxoHhF2eQ3nZ5p2LTpCTlh6ksvJyprKbbAc5CnLlc019dcP6ybz3l6pLGsf/GHN1DI31tGaVK5iUhsYkbRitL4cGbAftqLgmpik8v0oPIPQGYuxuaxSZbBRU7tEOOHb8TKKgu02Di9s0Yw6/M9ybRy1K4ahaNoLp2cloxJVMjSaReo1PptfoNNMh8kojHaaqcBepPjIWBViRLuXyoRadqH1G8uRPDjF7vpXurkGMcPZ6seFKmsXs4vqflQYS1F4TTWrXvOurUrIpDADpSmtuJ8VYpvLJsueKlO+QhYctlrlURhsL+ycWCFV7XpOTNgUSmuS6aGaXJMwl1ujdWhU5duOvG/SwKWJGwvmLZ0Iq4KfWjNya/MxppArgyLUDY+FN1kNLSrHK6eK78UZGwSCenxNWuQ9ZklqDrAmy1kXVqaN2wZmm73mchFCMai9N2W3+Uo0To5HHImqLVVIsI0Cr2uS5pD5Di4a+0InwRTGxk38okApM+Pc1FgZBwJMi72WzLWPLIOOGa6xyRxXdNAxGWVdDMjGTaS6/4VoR576UchixBVl6mmb1SXR9sZor6rIaeNTz1y4IVlO/6gz5XNs+O5FgYO42afTGHestIz4rd/ix+aOFIagBozdpqxx3e4d03MLraz8Aw7bNJ4AAAA=) format("woff")}.fc-icon{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:fc-icon!important;font-size:16px;font-style:normal}.icon-write:before{content:"\\e60b"}.icon-branch:before{content:"\\e6e3"}.icon-device:before{content:"\\e6e0"}.icon-position-left:before{content:"\\e6e2"}.icon-search:before{content:"\\e6e1"}.icon-expand:before{content:"\\e6df"}.icon-expand-left:before{content:"\\e6df";display:inline-block;transform:rotate(-180deg)}.icon-data-select:before{content:"\\e6dd"}.icon-markdown:before{content:"\\e893"}.icon-grid-line:before{content:"\\e600"}.icon-print:before{content:"\\e6de"}.icon-city:before{content:"\\e64b"}.icon-location:before{content:"\\e6d4"}.icon-qrcode:before{content:"\\e6ce"}.icon-input-id:before{content:"\\e6d1"}.icon-iframe:before{content:"\\e6d2"}.icon-audio:before{content:"\\e6d3"}.icon-form-model:before{content:"\\e6d5"}.icon-title:before{content:"\\e6d6"}.icon-sign:before{content:"\\e6d7"}.icon-address:before{content:"\\e6d8"}.icon-statistic:before{content:"\\e6d9"}.icon-barcode:before{content:"\\e6da"}.icon-video:before{content:"\\e6db"}.icon-avatar:before{content:"\\e6dc"}.icon-suspend:before{content:"\\e6cf"}.icon-warning:before{content:"\\e6d0"}.icon-send:before{content:"\\e6cc"}.icon-refresh2:before{content:"\\e6cd"}.icon-ai:before{content:"\\e6cb"}.icon-ai.bright{-webkit-text-fill-color:transparent;background:linear-gradient(90deg,#328ff7,#62e3a3);-webkit-background-clip:text}.icon-column4:before{content:"\\e6c7"}.icon-column3:before{content:"\\e6c6"}.icon-column2:before{content:"\\e6c8"}.icon-column1:before{content:"\\e6c9"}.icon-layout:before{content:"\\e6ca"}.icon-segmented:before{content:"\\e682"}.icon-mention:before{content:"\\e6c5"}.icon-input-tag:before{content:"\\e6c4"}.icon-up:before{content:"\\e697";display:inline-block;transform:rotate(180deg)}.icon-alignitems-flexstart:before{content:"\\e67f";display:inline-block;transform:rotate(180deg)}.icon-align-center:before{content:"\\e6a5";display:inline-block;transform:rotate(90deg)}.icon-align-flexstart:before{content:"\\e6a4";display:inline-block;transform:rotate(90deg)}.icon-align-spacearound:before{content:"\\e670";display:inline-block;transform:rotate(-90deg)}.icon-align-spacebetween:before{content:"\\e695";display:inline-block;transform:rotate(-90deg)}.icon-align-stretch:before{content:"\\e6a7";display:inline-block;transform:rotate(-90deg)}.icon-align-flexend:before{content:"\\e6a4";display:inline-block;transform:rotate(-90deg)}.icon-justify-flexend:before{content:"\\e6a4";display:inline-block;transform:rotate(180deg)}.icon-direction-row:before{content:"\\e68b";display:inline-block;transform:rotate(180deg)}.icon-direction-column:before{content:"\\e68b";display:inline-block;transform:rotate(-90deg)}.icon-direction-columnreverse:before{content:"\\e68b";display:inline-block;transform:rotate(90deg)}.icon-arrow:before{content:"\\e697";display:inline-block;transform:rotate(180deg)}.icon-cell:before{content:"\\e654"}.icon-table:before{content:"\\eb0a"}.icon-next-step:before{content:"\\e6b4";display:inline-block;transform:rotateY(180deg)}.icon-grid:before{content:"\\e65c";display:inline-block;transform:rotate(90deg)}.icon-alignitems-stretch:before{content:"\\e67e"}.icon-alignitems-flexend:before{content:"\\e67f"}.icon-check:before{content:"\\e680"}.icon-auto:before{content:"\\e681"}.icon-config-event:before{content:"\\e66e"}.icon-calendar:before{content:"\\e683"}.icon-config-style:before{content:"\\e684"}.icon-copy:before{content:"\\e676"}.icon-config-advanced:before{content:"\\e686"}.icon-config-props:before{content:"\\e687"}.icon-delete-circle2:before{content:"\\e688"}.icon-delete-circle:before,.icon-delete2:before{content:"\\e689"}.icon-delete:before{content:"\\e68a"}.icon-direction-rowreverse:before{content:"\\e68b"}.icon-display-flex:before{content:"\\e68c"}.icon-dialog:before{content:"\\e66f"}.icon-drag:before{content:"\\e68e"}.icon-display-block:before{content:"\\e68f"}.icon-data:before{content:"\\e690"}.icon-edit2:before{content:"\\e691"}.icon-edit:before{content:"\\e692"}.icon-add-col:before{content:"\\e693"}.icon-display-inlineblock:before{content:"\\e694"}.icon-config-base:before{content:"\\e6bf"}.icon-config-validate:before{content:"\\e696"}.icon-down:before{content:"\\e697"}.icon-display-inline:before{content:"\\e698"}.icon-eye:before{content:"\\e699"}.icon-eye-close:before{content:"\\e69a"}.icon-import:before{content:"\\e6a6"}.icon-preview:before{content:"\\e69b"}.icon-flex-nowrap:before{content:"\\e69c"}.icon-folder:before{content:"\\e69d"}.icon-form-circle:before{content:"\\e69e"}.icon-flex-wrap:before{content:"\\e69f"}.icon-form:before{content:"\\e6a0"}.icon-form-item:before{content:"\\e6a1"}.icon-icon:before{content:"\\e6a2"}.icon-image:before{content:"\\e6a3"}.icon-justify-flexstart:before{content:"\\e6a4"}.icon-justify-center:before{content:"\\e6a5"}.icon-justify-spacearound:before{content:"\\e670"}.icon-justify-stretch:before{content:"\\e6a7"}.icon-link2:before{content:"\\e6a8"}.icon-justify-spacebetween:before{content:"\\e695"}.icon-minus:before{content:"\\e6aa"}.icon-menu2:before{content:"\\e6ab"}.icon-more:before{content:"\\e6ac"}.icon-menu:before{content:"\\e6ad"}.icon-language:before{content:"\\e6ae"}.icon-pad:before{content:"\\e6af"}.icon-mobile:before{content:"\\e6b0"}.icon-page-max:before{content:"\\e6b1"}.icon-move:before{content:"\\e6b2"}.icon-page-min:before{content:"\\e6b3"}.icon-pre-step:before{content:"\\e6b4"}.icon-pc:before{content:"\\e6b5"}.icon-page:before{content:"\\e6b6"}.icon-refresh:before{content:"\\e6b7"}.icon-radius:before{content:"\\e6b8"}.icon-save-filled:before{content:"\\e6b9"}.icon-question:before{content:"\\e6ba"}.icon-scroll:before{content:"\\e6bb"}.icon-script:before{content:"\\e6bc"}.icon-setting:before{content:"\\e6bd"}.icon-save-online:before,.icon-save:before{content:"\\e6be"}.icon-task-add:before{content:"\\e68d"}.icon-shadow:before{content:"\\e6c0"}.icon-variable:before{content:"\\e6c1"}.icon-yes:before{content:"\\e6c2"}.icon-shadow-inset:before{content:"\\e6c3"}.icon-date:before{content:"\\e642"}.icon-date-range:before{content:"\\e643"}.icon-collapse:before{content:"\\e644"}.icon-slider:before{content:"\\e665"}.icon-switch:before{content:"\\e646"}.icon-subform:before{content:"\\e647"}.icon-time-range:before{content:"\\e685"}.icon-tree-select:before{content:"\\e649"}.icon-value:before{content:"\\e64a"}.icon-table-form3:before{content:"\\e6a9"}.icon-alert:before{content:"\\e64c"}.icon-card:before{content:"\\e64d"}.icon-checkbox:before{content:"\\e64e"}.icon-cascader:before{content:"\\e64f"}.icon-button:before{content:"\\e650"}.icon-data-table:before{content:"\\e651"}.icon-group:before{content:"\\e652"}.icon-divider:before{content:"\\e653"}.icon-flex:before{content:"\\e654"}.icon-descriptions:before{content:"\\e655"}.icon-html:before{content:"\\e656"}.icon-editor:before{content:"\\e657"}.icon-input:before{content:"\\e658"}.icon-link:before{content:"\\e659"}.icon-password:before{content:"\\e65a"}.icon-radio:before{content:"\\e65b"}.icon-row:before{content:"\\e65c"}.icon-inline:before{content:"\\e65d"}.icon-rate:before{content:"\\e65e"}.icon-color:before{content:"\\e65f"}.icon-select:before{content:"\\e660"}.icon-json:before{content:"\\e661"}.icon-number:before{content:"\\e662"}.icon-space:before{content:"\\e664"}.icon-step-form:before{content:"\\e663"}.icon-table-form:before{content:"\\e666"}.icon-table-form2:before{content:"\\e667"}.icon-time:before{content:"\\e668"}.icon-span:before{content:"\\e669"}.icon-textarea:before{content:"\\e66a"}.icon-tooltip:before{content:"\\e66b"}.icon-slot:before{content:"\\e66c"}.icon-transfer:before{content:"\\e66d"}.icon-upload:before{content:"\\e673"}.icon-tag:before{content:"\\e671"}.icon-watermark:before{content:"\\e672"}.icon-tab:before{content:"\\e674"}.icon-tree:before{content:"\\e675"}.icon-table:before{content:"\\e677"}.icon-add-child:before{content:"\\e678"}.icon-add2:before{content:"\\e679"}.icon-add:before{content:"\\e67a"}.icon-alignitems-baseline:before{content:"\\e67b"}.icon-add-circle:before{content:"\\e67c"}.icon-alignitems-center:before{content:"\\e67d"}pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#fff;color:#000}.hljs-subst,.hljs-title{color:#000;font-weight:400}.hljs-title.function_{color:#7a7a43}.hljs-code,.hljs-comment,.hljs-quote{color:#8c8c8c;font-style:italic}.hljs-meta{color:#9e880d}.hljs-section{color:#871094}.hljs-built_in,.hljs-keyword,.hljs-literal,.hljs-meta .hljs-keyword,.hljs-name,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-selector-pseudo,.hljs-selector-tag,.hljs-symbol,.hljs-template-tag,.hljs-type,.hljs-variable.language_{color:#0033b3}.hljs-attr,.hljs-property{color:#871094}.hljs-attribute{color:#174ad4}.hljs-number{color:#1750eb}.hljs-regexp{color:#264eff}.hljs-link{color:#006dcc;text-decoration:underline}.hljs-meta .hljs-string,.hljs-string{color:#067d17}.hljs-char.escape_{color:#0037a6}.hljs-doctag{text-decoration:underline}.hljs-template-variable{color:#248f8f}.hljs-addition{background:#bee6be}.hljs-deletion{background:#d6d6d6}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}')),document.head.appendChild(e)}}catch(t){console.error("vite-plugin-css-injected-by-js",t)}})();const vr={type(e,t){return Object.prototype.toString.call(e)==="[object "+t+"]"},Undef(e){return e==null},Element(e){return typeof e=="object"&&e!==null&&e.nodeType===1&&!vr.Object(e)},trueArray(e){return Array.isArray(e)&&e.length>0},Function(e){const t=this.getType(e);return t==="Function"||t==="AsyncFunction"},getType(e){const t=Object.prototype.toString.call(e);return/^\[object (.*)\]$/.exec(t)[1]},empty(e){return e==null||Array.isArray(e)&&Array.isArray(e)&&!e.length?!0:typeof e=="string"&&!e}};["Date","Object","String","Boolean","Array","Number"].forEach(e=>{vr[e]=function(t){return vr.type(t,e)}});function jp(e,t){return{}.hasOwnProperty.call(e,t)}function sy(e,t,n){e[t]=n}function w4(e,t={},n){let r=!1;for(let o in t)if(Object.prototype.hasOwnProperty.call(t,o)){let i=t[o];if((r=Array.isArray(i))||vr.Object(i)){let a=e[o]===void 0;r?(r=!1,a&&sy(e,o,[])):i._clone&&n!==void 0||a&&sy(e,o,{}),e[o]=w4(e[o],i,n)}else sy(e,o,i),vr.Undef(i)||(vr.Undef(i.__json)||(e[o].__json=i.__json),vr.Undef(i.__origin)||(e[o].__origin=i.__origin))}return n!==void 0&&Array.isArray(e)?e.filter(o=>!o||!o.__ctrl):e}function so(e){return w4({},{value:e}).value}function r2e(e,t,n){return`[form-create ${e}]: ${t}`}function o2e(e,t){console.error(r2e("err",e))}const OO="[[FORM-CREATE-PREFIX-",DO="-FORM-CREATE-SUFFIX]]";function S3(e){return new Function("return "+e)()}function i2e(e,t){if(e&&vr.String(e)&&e.length>4){let n=e.trim(),r=!1;try{if(n.indexOf(DO)>0&&n.indexOf(OO)===0)n=n.replace(DO,"").replace(OO,""),r=!0;else if(n.indexOf("$FN:")===0)n=n.substring(4),r=!0;else if(n.indexOf("$EXEC:")===0)n=n.substring(6),r=!0;else if(n.indexOf("$GLOBAL:")===0){const i=n.substring(8);return n=function(...a){const l=a[0].api.getGlobalEvent(i);if(l)return l.call(this,...a)},n.__json=e,n.__inject=!0,n}else{if(n.indexOf("$FNX:")===0)return n=S3(`function($inject){ +`+n.substring(5)+` +}`),n.__json=e,n.__inject=!0,n;(!t&&n.indexOf("function ")===0&&n!=="function "||!t&&n.indexOf("function(")===0&&n!=="function(")&&(r=!0)}if(!r)return e;let o;try{o=S3(n)}catch{o=S3("function "+n)}return o.__json=e,o}catch(o){o2e(`解析失败:${n} + +err: ${o}`);return}}return e}function pj(e){const t=e.replace(/(-[a-z])/g,function(n){return n.replace("-","").toLocaleUpperCase()});return uy(t)}function uy(e){return e.replace(e[0],e[0].toLowerCase())}const hj={name:"zh-cn",form:{field:"字段 ID",title:"字段名称",info:"提示信息",control:"组件联动",labelPosition:"标签的位置",labelStyle:"标签的样式",labelSuffix:"标签的后缀",size:"表单的尺寸",event:"表单事件",labelWidth:"标签的宽度",hideRequiredAsterisk:"隐藏必填字段的标签旁边的红色星号",showMessage:"显示校验错误信息",inlineMessage:"以行内形式展示校验信息",submitBtn:"是否显示表单提交按钮",resetBtn:"是否显示表单重置按钮",appendChild:"添加子级",formMode:"表单模式",formName:"表单名称",componentMode:"生成组件",htmlMode:"生成HTML",document:"帮助文档",controlDocument:"需要更详细的配置方法?请查看{doc}",onSubmit:"表单提交时触发",onReset:"表单重置后触发",onCreated:"表单组件初始化完毕后触发",onMounted:"表单组件渲染完毕后触发",onReload:"表单渲染规则重载后触发",onChange:"表单组件的值发生变化时触发",beforeSubmit:"表单验证通过时触发",beforeFetch:"远程数据请求发送前触发"},warning:{name:"组件的唯一标识,用于获取和修改该组件的配置规则。通过该标识可以精确定位组件,实现对组件属性和行为的控制。",field:"组件对应的字段名用于与组件的数据进行绑定。字段名需以字母开头,以确保能够正确识别。",fetch:"远程数据通过远程请求加载组件的配置项。配置请求参数后,组件会自动发起请求,获取远程数据并根据返回的结果更新组件。",fetchQuery:"定义请求的 GET 参数,通过 URL 传递数据。",fetchData:"定义请求的 POST 参数,通过请求体传递数据。",fetchDataType:"选择请求体的数据类型,确保数据格式正确。",beforeFetch:"在请求发送前,可通过处理函数修改请求参数;若需终止请求,直接返回 false 或一个拒绝的 Promise。",fetchParse:"请求返回后,可以通过处理函数对返回的结果进行处理,将结果转换为组件所需的数据和结构。",language:"管理页面的多语言数据,在组件中配置不同语言的文本,支持一键切换语言体系,便于在多语言环境下使用和展示内容。"},computed:{fieldUsed:"【{label}】在计算公式中被使用,请先修改对应公式",fieldExist:"【{label}】字段已存在",fieldEmpty:"字段名称不能为空",fieldChar:"字段名称必须以字母开头"},validate:{type:"字段类型",typePlaceholder:"请选择",trigger:"触发方式",mode:"验证方式",modes:{required:"必填",pattern:"正则表达式",validator:"自定义验证",min:"最小值",max:"最大值",len:"长度"},types:{string:"文本",boolean:"布尔",array:"多选",number:"数字",integer:"整数",float:"小数",object:"合集",date:"日期",url:"URL链接",email:"邮箱地址"},message:"错误信息",auto:"自动获取",autoRequired:"请输入{title}",autoMode:"请输入正确的{title}",requiredPlaceholder:"请输入提示语",required:"是否必填",rule:"验证规则"},tableOptions:{handle:"操作",add:"添加",empty1:"点击右下角",empty2:"按钮添加一列",rmCol:"删除当前列",rmRow:"删除当前行",batchRmCol:"删除列",batchRmRow:"删除行",batchSplit:"拆分单元格",batchMerge:"合并单元格",splitRow:"拆分成行",splitCol:"拆分成列",mergeBottom:"向下合并",mergeRight:"向右合并",addTop:"添加上列",addBottom:"添加下列",addLeft:"添加左列",addRight:"添加右列",keyValue:"键值对"},struct:{title:"编辑数据",only:"【{label}】只允许添加一个",errorMsg:"输入的内容语法错误",configured:"已配置"},event:{title:"设置事件",create:"创建事件",list:"事件列表",placeholder:"请输入事件的名称",saveMsg:"请先保存当前正在编辑的事件",type:"类型",info:"说明",label:"字段",inject:{api:"当前表单的api",rule:"当前表单的生成规则",self:"组件的生成规则",option:"表单的配置",args:"事件的原始参数"}},eventInfo:{blur:"失去焦点时触发",focus:"获得焦点时触发",change:"当绑定值变化时触发",input:"在值改变时触发",clear:"在点击清空按钮时触发",close:"关闭组件时触发",click:"点击组件时触发",add:"增加时触发",delete:"删除时触发",visibleChange:"下拉框出现/隐藏时触发",calendarChange:"在日历所选日期更改时触发",panelChange:"当日期面板改变时触发",open:"打开的回调",opened:"打开动画结束时的回调",closed:"关闭动画结束时的回调",openAutoFocus:"输入焦点聚焦在内容时的回调",closeAutoFocus:"输入焦点从内容失焦时的回调",submit:"表单提交时触发",confirm:"点击确认按钮时触发",validateFail:"表单验证失败时触发",hook_load:"组件规则加载后触发",hook_mounted:"组件挂载后触发",hook_deleted:"组件规则被移除后触发",hook_watch:"组件规则发生变化后触发",hook_value:"组件的值发生变化后触发",hook_hidden:"组件显示状态发生变化后触发"},fetch:{title:"设置数据源",create:"创建数据源",config:"请求配置",action:"请求链接",actionRequired:"请输入正确的链接",placeholder:"请输入数据源的名称",method:"请求方式",data:"附带数据",dataType:"数据类型",headers:"请求头部",query:"请求参数",parse:"数据处理",response:"接口返回的数据",onError:"错误处理",beforeFetch:"前置处理",remote:"远程数据",static:"静态数据",optionsType:{fetch:"远程数据",struct:"静态数据"}},style:{width:"宽度",height:"高度",color:"颜色",backgroundColor:"背景色",margin:"外边距",padding:"内边距",borderRadius:"圆角",border:"边框",solid:"实线",dashed:"虚线",dotted:"点状虚线",double:"双实线",opacity:"透明度",scale:"缩放",minWidth:"最小宽",minHeight:"最小高",maxWidth:"最大宽",maxHeight:"最大高",overflow:{name:"溢出",visible:"可见",hidden:"隐藏",scroll:"滚动",auto:"溢出后自动滚动"},shadow:{name:"阴影",x:"x轴偏移量",y:"y轴偏移量",vague:"模糊半径",extend:"扩散半径",inset:"向内",external:"向外",mode:"模式",classic:"经典",flat:"扁平",solid:"立体"},font:{name:"文字",size:"大小",family:"字体",align:"对齐方式",height:"行高",spacing:"字间距",preview:"样式预览"},family:{"Microsoft YaHei":"微软雅黑",SimSun:"宋体",SimHei:"黑体",KaiTi:"楷体",FangSong:"仿宋",LiSu:"隶书"},position:{name:"位置",static:"默认定位",relative:"相对定位",absolute:"绝对定位",fixed:"固定定位",sticky:"粘性定位"},decoration:{name:"修饰",underline:"下划线","line-through":"删除线",overline:"上划线"},weight:{name:"粗细",300:"细体",400:"常规体",500:"中黑体",700:"中粗体"}},designer:{component:"组件配置",id:"唯一值",name:"编号",type:"组件类型",form:"表单配置",json:"渲染规则",style:"组件样式配置",rule:"基础配置",advanced:"高级配置",props:"属性配置",customProps:"自定义属性配置",validate:"验证配置",event:"事件配置",clearWarn:"清空后将不能恢复,确定要清空吗?",childEmpty:"点击右下角 \\e789 按钮添加一列",dragEmpty:"拖拽左侧列表中的组件到此处",unload:"确定离开当前页面吗?",comList:"组件列表"},language:{name:"国际化配置",add:"新增词条",batchRemove:"批量删除",select:"选择多语言"},menu:{main:"基础组件",aide:"辅助组件",layout:"布局组件",component:"组件",subform:"子表单组件",tree:"大纲"},props:{disabled:"禁用",time:"时间",size:"尺寸",email:"邮箱",number:"数字",globalData:"全局数据",mobile:"移动端",reactive:"响应式",pc:"电脑端",title:"标题",content:"内容",collection:"合集",group:"分组",custom:"自定义",change:"改变",blur:"失去焦点",preview:"预览",clear:"清空",cancel:"取消",close:"关闭",ok:"确定",save:"保存",refresh:"刷新",submit:"提交",reset:"重置",copy:"复制",delete:"删除",hide:"隐藏",show:"显示",position:"位置",render:"渲染",large:"大",default:"默认",small:"小",always:"常显",never:"不显示",hover:"悬浮",click:"点击",button:"按钮",year:"年份",month:"月份",date:"日期",dates:"日期多选",week:"一周",datetime:"日期时间","datetime-local":"日期时间",datetimerange:"日期时间区间",daterange:"日期区间",monthrange:"月份区间",left:"左对齐",right:"右对齐",top:"顶部",text:"文字",picture:"图片","picture-card":"卡片",center:"居中",vertical:"竖向",horizontal:"横向",manage:"管理",key:"键名",name:"名称",value:"值",inputData:"默认值",append:"插入",options:"选项数据",option:"选项",callback:"回调",style:"样式",_self:"当前窗口",_blank:"新的窗口",_parent:"父级窗口",_top:"顶级窗口"},com:{cascader:{name:"级联选择器",event:{expandChange:"当展开节点发生变化时触发",removeTag:"在多选模式下,移除Tag时触发"},props:{props:"配置选项",placeholder:"输入框占位文本",disabled:"是否禁用",clearable:"是否支持清空选项",showAllLevels:"输入框中是否显示选中值的完整路径",collapseTags:"多选模式下是否折叠Tag",collapseTagsTooltip:"当鼠标悬停于折叠标签的文本时,是否显示所有选中的标签",separator:"选项分隔符",filterable:"该选项是否可以被搜索",tagType:"标签类型"},propsOpt:{multiple:"是否多选",expandTrigger:"次级菜单的展开方式",checkStrictly:"是否严格的遵守父子节点不互相关联",emitPath:"在选中节点改变时,是否返回由该节点所在的各级菜单的值所组成的数组",value:"指定选项的值为选项对象的某个属性值",label:"指定选项标签为选项对象的某个属性值",children:"指定选项的子选项为选项对象的某个属性值",disabled:"指定选项的禁用为选项对象的某个属性值",leaf:"指定选项的叶子节点的标志位为选项对象的某个属性值"}},checkbox:{name:"多选框",props:{input:"是否可以填写",type:"按钮类型",disabled:"是否禁用",min:"可被勾选的最小数量",max:"可被勾选的最大数量",textColor:"当按钮为活跃状态时的字体颜色",fill:"当按钮为活跃状态时的边框和背景颜色"}},col:{name:"布局格子",props:{span:"栅格占据的列数",offset:"栅格左侧的间隔格数",push:"栅格向右移动格数",pull:"栅格向左移动格数"}},colorPicker:{name:"颜色选择器",event:{activeChange:"面板中当前显示的颜色发生改变时触发"},props:{disabled:"是否禁用",showAlpha:"是否支持透明度选择",colorFormat:"颜色的格式",predefine:"预定义颜色"}},datePicker:{name:"日期",props:{pickerOptions:"当前时间日期选择器特有的选项",readonly:"完全只读",disabled:"禁用",type:"显示类型",editable:"文本框可输入",clearable:"是否显示清除按钮",placeholder:"非范围选择时的占位内容",startPlaceholder:"范围选择时开始日期的占位内容",endPlaceholder:"范围选择时结束日期的占位内容",format:"显示在输入框中的格式",align:"对齐方式",rangeSeparator:"选择范围时的分隔符",unlinkPanels:"在范围选择器里取消两个日期面板之间的联动"}},dateRange:{name:"日期区间"},timeRange:{name:"时间区间"},elAlert:{name:"提示",description:"说明文字",props:{title:"标题",type:"主题",description:"辅助性文字",closable:"是否可关闭",center:"文字是否居中",closeText:"关闭按钮自定义文本",showIcon:"是否显示图标",effect:"选择提供的主题"}},elButton:{name:"按钮",props:{formCreateChild:"内容",size:"尺寸",type:"类型",plain:"是否朴素按钮",round:"是否圆角按钮",circle:"是否圆形按钮",loading:"是否加载中状态",disabled:"是否禁用状态"}},elCard:{name:"卡片",props:{header:"标题",shadow:"阴影显示时机"}},elCollapse:{name:"折叠面板",event:{change:"切换当前活动面板,在手风琴模式下其类型是string,在其他模式下是array"},props:{accordion:"是否手风琴模式"}},elCollapseItem:{name:"面板",props:{title:"面板标题",name:"唯一标志符",disabled:"是否禁用"}},elDivider:{name:"分割线",props:{formCreateChild:"设置分割线文案",contentPosition:"设置分割线文案的位置"}},elTabPane:{name:"选项卡",props:{label:"选项卡标题",disabled:"是否禁用",name:"选项卡的标识符",lazy:"标签是否延迟渲染"}},elTabs:{name:"标签页",event:{tabClick:"tab 被选中时触发",tabChange:"activeName 改变时触发",tabRemove:"点击 tab 移除按钮时触发",tabAdd:"点击 tab 新增按钮时触发",edit:"点击 tab 的新增或移除按钮后触发"},props:{type:"风格类型",closable:"标签是否可关闭",tabPosition:"选项卡所在位置",stretch:"标签的宽度是否自撑开"}},elTag:{name:"标签",props:{formCreateNative:"是否显示标题",formCreateTitle:"标题",formCreateChild:"标签内容",type:"标签的类型",size:"标签的尺寸",effect:"标签的主题",closable:"是否可关闭",disableTransitions:"是否禁用渐变动画",hit:"是否有边框描边",round:"是否为圆形",color:"背景色"}},elTransfer:{name:"穿梭框",event:{leftCheckChange:"左侧列表元素被用户选中 / 取消选中时触发",rightCheckChange:"右侧列表元素被用户选中 / 取消选中时触发"},props:{filterable:"是否可搜索",filterPlaceholder:"搜索框占位符",targetOrder:"右侧列表元素的排序策略",targetOrderInfo:"若为 original,则保持与数据相同的顺序;若为 push,则新加入的元素排在最后;若为 unshift,则新加入的元素排在最前",titles:"自定义列表标题",buttonTexts:"自定义按钮文案",props:"数据源的字段别名"}},elTreeSelect:{name:"树形选择",event:{removeTag:"多选模式下移除tag时触发"},props:{multiple:"是否多选",disabled:"是否禁用",clearable:"是否可以清空选项",collapseTags:"多选时是否将选中值按文字的形式展示",multipleLimit:"多选时用户最多可以选择的项目数,为 0 则不限制",placeholder:"占位符",props:"配置选项",renderAfterExpand:"是否在第一次展开某个树节点后才渲染其子节点",defaultExpandAll:"是否默认展开所有节点",expandOnClickNode:"是否在点击节点的时候展开或者收缩节点",checkOnClickNode:"是否在点击节点的时候选中节点",nodeKey:"每个树节点用来作为唯一标识的属性,整棵树应该是唯一的"}},elImage:{name:"图片",props:{src:"图片链接"}},fcTitle:{name:"标题",props:{title:"标题",size:"尺寸",align:"对齐方式"}},fcEditor:{name:"富文本框",props:{disabled:"是否禁用"}},fcRow:{name:"栅格布局",props:{gutter:"栅格间隔",type:"flex布局模式",justify:"flex布局下的水平排列方式",align:"flex布局下的垂直排列方式"}},fcTable:{name:"表格布局",props:{border:"是否显示边框",borderColor:"边框颜色",borderWidth:"边框宽度"}},fcTableGrid:{name:"格子"},signaturePad:{name:"手写签名",props:{penColor:"线条的颜色",disabled:"禁用"}},group:{name:"子表单",props:{disabled:"是否禁用",syncDisabled:"是否与子表单强制同步禁用状态",expand:"设置默认展开几项",button:"是否显示操作按钮",sortBtn:"是否显示排序按钮",min:"设置最小添加几项",max:"设置最多添加几项"}},html:{name:"HTML",props:{formCreateNative:"是否显示标题",formCreateTitle:"标题",formCreateChild:"内容"}},input:{name:"输入框",event:{change:"当值改变时,当组件失去焦点或用户按Enter时触发"},props:{type:"类型",maxlength:"最大输入长度",minlength:"最小输入长度",placeholder:"输入框占位文本",clearable:"是否显示清除按钮",disabled:"是否禁用",readonly:"是否只读"}},inputNumber:{name:"计数器",props:{precision:"数值精度",min:"设置计数器允许的最小值",max:"设置计数器允许的最大值",step:"计数器步长",stepStrictly:"是否只能输入 step 的倍数",disabled:"是否禁用计数器",controls:"是否使用控制按钮",controlsPosition:"控制按钮位置",placeholder:"输入框占位文本"}},password:{name:"密码输入框",event:{change:"当值改变时,当组件失去焦点或用户按Enter时触发"},props:{disabled:"是否禁用",readonly:"是否只读",maxlength:"最大输入长度",minlength:"最小输入长度",placeholder:"输入框占位文本",clearable:"是否显示清除按钮"}},radio:{name:"单选框",props:{input:"是否可以填写",disabled:"是否禁用",type:"按钮形式",textColor:"按钮形式激活时的文本颜色",fill:"按钮形式激活时的填充色和边框色"}},rate:{name:"评分",props:{max:"最大分值",disabled:"是否禁用",allowHalf:"是否允许半选",voidColor:"未选中时图标的颜色",disabledVoidColor:"只读时未选中时图标的颜色",voidIconClass:"未选中时图标的类名",disabledVoidIconClass:"只读时未选中时图标的类名",showScore:"是否显示当前分数",textColor:"辅助文字的颜色",scoreTemplate:"分数显示模板"}},select:{name:"选择器",event:{removeTag:"多选模式下移除tag时触发"},props:{multiple:"是否多选",disabled:"是否禁用",clearable:"是否可以清空选项",collapseTags:"多选时是否将选中值按文字的形式展示",multipleLimit:"多选时用户最多可以选择的项目数,为 0 则不限制",placeholder:"占位符",filterable:"是否可搜索",allowCreate:"是否允许用户创建新条目",noMatchText:"搜索条件无匹配时显示的文字",noDataText:"选项为空时显示的文字",reserveKeyword:"多选且可搜索时,是否在选中一个选项后保留当前的搜索关键词",defaultFirstOption:"在输入框按下回车,选择第一个匹配项",remote:"其中的选项是否从服务器远程加载",remoteMethod:"自定义远程搜索方法"}},slider:{name:"滑块",props:{min:"最小值",max:"最大值",disabled:"是否禁用",step:"步长",showInput:"是否显示输入框,仅在非范围选择时有效",showInputControls:"在显示输入框的情况下,是否显示输入框的控制按钮",showStops:"是否显示间断点",range:"是否为范围选择",vertical:"是否竖向模式",height:"Slider 高度,竖向模式时必填"}},space:{name:"间距",props:{height:"高度"}},subForm:{name:"分组",props:{disabled:"是否禁用",syncDisabled:"是否与子表单强制同步禁用状态"}},switch:{name:"开关",props:{disabled:"是否禁用",width:"宽度(px)",activeText:"打开时的文字描述",inactiveText:"关闭时的文字描述",activeValue:"打开时的值",inactiveValue:"关闭时的值",activeColor:"打开时的背景色",inactiveColor:"关闭时的背景色"}},tableForm:{name:"表格表单",props:{disabled:"是否禁用",filterEmptyColumn:"是否过滤空行的数据",max:"最多添加几行,为 0 则不限制",min:"最少添加几行",addable:"允许新增",deletable:"允许删除"}},tableFormColumn:{name:"表格格子",label:"自定义名称",props:{align:"对齐方式",label:"标题",width:"宽度",color:"颜色",required:"是否显示必填星号"}},text:{name:"文字",props:{formCreateNative:"是否显示标题",formCreateTitle:"标题",formCreateChild:"内容"}},textarea:{name:"多行输入框",event:{change:"当值改变时,当组件失去焦点或用户按Enter时触发"},props:{disabled:"是否禁用",readonly:"是否只读",maxlength:"最大输入长度",minlength:"最小输入长度",showWordLimit:"是否显示统计字数",placeholder:"输入框占位文本",rows:"输入框行数",autosize:"高度是否自适应"}},timePicker:{name:"时间",props:{pickerOptions:"当前时间日期选择器特有的选项",readonly:"完全只读",disabled:"禁用",editable:"文本框可输入",clearable:"是否显示清除按钮",placeholder:"非范围选择时的占位内容",startPlaceholder:"范围选择时开始日期的占位内容",endPlaceholder:"范围选择时开始日期的占位内容",isRange:"是否为时间范围选择",arrowControl:"是否使用箭头进行时间选择",align:"对齐方式"}},tree:{name:"树形控件",event:{nodeClick:"当节点被点击的时候触发",nodeContextmenu:"当某一节点被鼠标右键点击时会触发该事件",checkChange:"当复选框被点击的时候触发",check:"点击节点复选框之后触发",currentChange:"当前选中节点变化时触发的事件",nodeExpand:"节点被展开时触发的事件",nodeCollapse:"节点被关闭时触发的事件",nodeDragStart:"节点开始拖拽时触发的事件",nodeDragEnter:"拖拽进入其他节点时触发的事件",nodeDragLeave:"拖拽离开某个节点时触发的事件",nodeDragOver:"在拖拽节点时触发的事件(类似浏览器的 mouseover 事件)",nodeDragEnd:"拖拽结束时(可能未成功)触发的事件",nodeDrop:"拖拽成功完成时触发的事件"},props:{emptyText:"内容为空的时候展示的文本",props:"配置选项",renderAfterExpand:"是否在第一次展开某个树节点后才渲染其子节点",defaultExpandAll:"是否默认展开所有节点",expandOnClickNode:"是否在点击节点的时候展开或者收缩节点,如果为 false,则只有点箭头图标的时候才会展开或者收缩节点。",checkOnClickNode:"是否在点击节点的时候选中节点",autoExpandParent:"展开子节点的时候是否自动展开父节点",checkStrictly:"在显示复选框的情况下,是否严格的遵循父子不互相关联的做法",accordion:"是否每次只打开一个同级树节点展开",indent:"相邻级节点间的水平缩进(px)",nodeKey:"每个树节点用来作为唯一标识的属性,整棵树应该是唯一的"}},upload:{name:"上传",info:"上传成功后,将接口返回的 URL 赋值给 file.url,或将返回结果赋值给 file.value,以便在后续的表单提交时获取这些数据。",event:{remove:"文件列表移除文件时触发",preview:"点击文件列表中已上传的文件时触发",error:"文件上传失败时触发",progress:"文件上传时触发",exceed:"当超出限制时触发"},props:{listType:"上传类型",multiple:"是否支持多选文件",action:"上传的地址(必填)",beforeUpload:"上传文件之前触发",onSuccess:"上传成功时触发",beforeRemove:"删除文件之前触发",headers:"设置上传的请求头部",data:"上传时附带的额外参数",name:"上传的文件字段名",withCredentials:"支持发送 cookie 凭证信息",accept:"接受上传的文件类型",autoUpload:"是否在选取文件后立即进行上传",disabled:"是否禁用",limit:"最大允许上传个数"}}}},vj=(e,t)=>Cme({message:e,type:t||"info",customClass:"_fc-message-tip"}),ns=e=>vj(e,"error"),a2e=Object.assign||function(e){for(let t,n=1;n + + + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-ui/package.json b/smart-flow-api/warm-flow/warm-flow-ui/package.json new file mode 100644 index 0000000..61419cd --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/package.json @@ -0,0 +1,41 @@ +{ + "name": "Warm-Flow", + "version": "1.8.2", + "description": "Warm-Flow设计器", + "author": "warm", + "license": "MIT", + "type": "module", + "scripts": { + "dev": "vite", + "build:prod": "vite build", + "preview": "vite preview" + }, + "repository": { + "type": "git", + "url": "https://gitee.com/dromara/warm-flow.git" + }, + "dependencies": { + "@element-plus/icons-vue": "2.3.1", + "@form-create/designer": "^3.2.6", + "@form-create/element-ui": "^3.2.10", + "@logicflow/core": "^2.0.12", + "@logicflow/extension": "^2.0.16", + "axios": "0.27.2", + "element-plus": "2.4.3", + "file-saver": "2.0.5", + "pinia": "2.1.7", + "vue": "3.3.9", + "vue-router": "4.2.5" + }, + "devDependencies": { + "@vitejs/plugin-vue": "4.5.0", + "@vue/compiler-sfc": "3.3.9", + "sass": "1.69.5", + "unplugin-auto-import": "0.17.1", + "unplugin-vue-setup-extend-plus": "1.0.0", + "vite": "5.0.4", + "vite-plugin-compression": "0.5.1", + "vite-plugin-svg-icons": "2.0.1" + }, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" +} diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/App.vue b/smart-flow-api/warm-flow/warm-flow-ui/src/App.vue new file mode 100644 index 0000000..fdf0559 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/App.vue @@ -0,0 +1,25 @@ + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/api/anony.js b/smart-flow-api/warm-flow/warm-flow-ui/src/api/anony.js new file mode 100644 index 0000000..4a908e5 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/api/anony.js @@ -0,0 +1,13 @@ +import request from '@/utils/request' + +const urlPrefix = import.meta.env.VITE_URL_PREFIX + +// 保存流程定义 +export function config() { + return request({ + url: urlPrefix + 'warm-flow-ui/config', + method: 'get' + }) +} + + diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/api/flow/definition.js b/smart-flow-api/warm-flow/warm-flow-ui/src/api/flow/definition.js new file mode 100644 index 0000000..e7f7df4 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/api/flow/definition.js @@ -0,0 +1,86 @@ +import request from '@/utils/request' + +const urlPrefix = import.meta.env.VITE_URL_PREFIX + +// 保存json流程定义 +export function saveJson(data, onlyNodeSkip) { + return request({ + url: urlPrefix + 'warm-flow/save-json', + method: 'post', + data: data, + headers: { + 'onlyNodeSkip': onlyNodeSkip + } + }) +} + +// 获取流程定义 +export function queryDef(id) { + if (id) { + id = '/' + id + } else { + id = '' + } + return request({ + url: urlPrefix + 'warm-flow/query-def' + id, + method: 'get' + }) +} + +// 获取流程图 +export function queryFlowChart(id) { + return request({ + url: urlPrefix + 'warm-flow/query-flow-chart/' + id, + method: 'get' + }) +} + +// 办理人权限设置列表tabs页签 +export function handlerType() { + return request({ + url: urlPrefix + 'warm-flow/handler-type', + method: 'get', + }) +} + +// 办理人权限设置列表结果 +export function handlerResult(query) { + return request({ + url: urlPrefix + 'warm-flow/handler-result', + method: 'get', + params: query + }) +} + +// 办理人权限名称回显 +export function handlerFeedback(query) { + return request({ + url: urlPrefix + 'warm-flow/handler-feedback', + method: 'get', + params: query + }) +} + +// 办理人选择项 +export function handlerDict() { + return request({ + url: urlPrefix + 'warm-flow/handler-dict', + method: 'get' + }) +} + +// 查询已发布表单定义列表 +export function publishedList() { + return request({ + url: urlPrefix + 'warm-flow/published-form', + method: 'get' + }) +} + +// 办理人选择项 +export function nodeExt() { + return request({ + url: urlPrefix + 'warm-flow/node-ext', + method: 'get' + }) +} diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/api/form/form.js b/smart-flow-api/warm-flow/warm-flow-ui/src/api/form/form.js new file mode 100644 index 0000000..a662f09 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/api/form/form.js @@ -0,0 +1,45 @@ +import request from '@/utils/request' + +const urlPrefix = import.meta.env.VITE_URL_PREFIX + +// 查询表单定义列表 +export function getFormContent(id) { + return request({ + url: urlPrefix + 'warm-flow/form-content/' + id, + method: 'get' + }) +} + +// 保存表单设计 +export function saveFormContent(data) { + return request({ + url: urlPrefix + 'warm-flow/form-content', + method: 'post', + data: data + }) +} + +// 获取表单设计详情及数据 +export function executeLoad(id) { + return request({ + url: urlPrefix + 'warm-flow/execute/load/' + id, + method: 'get' + }) +} + +// 办理OA 申请 +export function executeHandle(data, taskId, skipType, message) { + return request({ + url: urlPrefix + 'warm-flow/execute/handle?taskId=' + taskId + '&skipType=' + skipType + '&message=' + message, + data: data, + method: 'post' + }) +} + +// 查看已办表单详情 +export function hisLoad(taskId) { + return request({ + url: urlPrefix + 'warm-flow/execute/hisLoad/' + taskId, + method: 'get' + }) +} diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/assets/401_images/401.gif b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/401_images/401.gif new file mode 100644 index 0000000..cd6e0d9 Binary files /dev/null and b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/401_images/401.gif differ diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/assets/404_images/404.png b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/404_images/404.png new file mode 100644 index 0000000..3d8e230 Binary files /dev/null and b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/404_images/404.png differ diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/assets/404_images/404_cloud.png b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/404_images/404_cloud.png new file mode 100644 index 0000000..c6281d0 Binary files /dev/null and b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/404_images/404_cloud.png differ diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/assets/favicon.ico b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/favicon.ico new file mode 100644 index 0000000..d9c9543 Binary files /dev/null and b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/favicon.ico differ diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/assets/icons/svg/baseInfo.svg b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/icons/svg/baseInfo.svg new file mode 100644 index 0000000..26b36b7 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/icons/svg/baseInfo.svg @@ -0,0 +1,7 @@ + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/assets/icons/svg/between.svg b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/icons/svg/between.svg new file mode 100644 index 0000000..7d64c6f --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/icons/svg/between.svg @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/assets/icons/svg/close.svg b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/icons/svg/close.svg new file mode 100644 index 0000000..7510e46 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/icons/svg/close.svg @@ -0,0 +1,4 @@ + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/assets/icons/svg/flowDesign.svg b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/icons/svg/flowDesign.svg new file mode 100644 index 0000000..bfbbcde --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/icons/svg/flowDesign.svg @@ -0,0 +1,13 @@ + + + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/assets/icons/svg/flowName.svg b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/icons/svg/flowName.svg new file mode 100644 index 0000000..234b677 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/icons/svg/flowName.svg @@ -0,0 +1,5 @@ + + + diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/assets/icons/svg/formDesign.svg b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/icons/svg/formDesign.svg new file mode 100644 index 0000000..4e8560a --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/icons/svg/formDesign.svg @@ -0,0 +1,9 @@ + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/assets/icons/svg/inclusive.svg b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/icons/svg/inclusive.svg new file mode 100644 index 0000000..71fb0b1 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/icons/svg/inclusive.svg @@ -0,0 +1,8 @@ + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/assets/icons/svg/parallel.svg b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/icons/svg/parallel.svg new file mode 100644 index 0000000..63b2715 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/icons/svg/parallel.svg @@ -0,0 +1,10 @@ + + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/assets/icons/svg/save.svg b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/icons/svg/save.svg new file mode 100644 index 0000000..4f69098 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/icons/svg/save.svg @@ -0,0 +1,11 @@ + + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/assets/icons/svg/serial.svg b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/icons/svg/serial.svg new file mode 100644 index 0000000..ffe8590 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/icons/svg/serial.svg @@ -0,0 +1,10 @@ + + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/assets/styles/btn.scss b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/styles/btn.scss new file mode 100644 index 0000000..3590d8d --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/styles/btn.scss @@ -0,0 +1,99 @@ +@import './variables.module.scss'; + +@mixin colorBtn($color) { + background: $color; + + &:hover { + color: $color; + + &:before, + &:after { + background: $color; + } + } +} + +.blue-btn { + @include colorBtn($blue) +} + +.light-blue-btn { + @include colorBtn($light-blue) +} + +.red-btn { + @include colorBtn($red) +} + +.pink-btn { + @include colorBtn($pink) +} + +.green-btn { + @include colorBtn($green) +} + +.tiffany-btn { + @include colorBtn($tiffany) +} + +.yellow-btn { + @include colorBtn($yellow) +} + +.pan-btn { + font-size: 14px; + color: #fff; + padding: 14px 36px; + border-radius: 8px; + border: none; + outline: none; + transition: 600ms ease all; + position: relative; + display: inline-block; + + &:hover { + background: #fff; + + &:before, + &:after { + width: 100%; + transition: 600ms ease all; + } + } + + &:before, + &:after { + content: ''; + position: absolute; + top: 0; + right: 0; + height: 2px; + width: 0; + transition: 400ms ease all; + } + + &::after { + right: inherit; + top: inherit; + left: 0; + bottom: 0; + } +} + +.custom-button { + display: inline-block; + line-height: 1; + white-space: nowrap; + cursor: pointer; + background: #fff; + color: #fff; + -webkit-appearance: none; + text-align: center; + box-sizing: border-box; + outline: 0; + margin: 0; + padding: 10px 15px; + font-size: 14px; + border-radius: 4px; +} diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/assets/styles/element-ui.scss b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/styles/element-ui.scss new file mode 100644 index 0000000..0f175f2 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/styles/element-ui.scss @@ -0,0 +1,96 @@ +// cover some element-ui styles + +.el-breadcrumb__inner, +.el-breadcrumb__inner a { + font-weight: 400 !important; +} + +.el-upload { + input[type="file"] { + display: none !important; + } +} + +.el-upload__input { + display: none; +} + +.cell { + .el-tag { + margin-right: 0px; + } +} + +.small-padding { + .cell { + padding-left: 5px; + padding-right: 5px; + } +} + +.fixed-width { + .el-button--mini { + padding: 7px 10px; + width: 60px; + } +} + +.status-col { + .cell { + padding: 0 10px; + text-align: center; + + .el-tag { + margin-right: 0px; + } + } +} + +// to fixed https://github.com/ElemeFE/element/issues/2461 +.el-dialog { + transform: none; + left: 0; + position: relative; + margin: 0 auto; +} + +// refine element ui upload +.upload-container { + .el-upload { + width: 100%; + + .el-upload-dragger { + width: 100%; + height: 200px; + } + } +} + +// dropdown +.el-dropdown-menu { + a { + display: block + } +} + +// fix date-picker ui bug in filter-item +.el-range-editor.el-input__inner { + display: inline-flex !important; +} + +// to fix el-date-picker css style +.el-range-separator { + box-sizing: content-box; +} + +.el-menu--collapse + > div + > .el-submenu + > .el-submenu__title + .el-submenu__icon-arrow { + display: none; +} + +.el-dropdown .el-dropdown-link{ + color: var(--el-color-primary) !important; +} \ No newline at end of file diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/assets/styles/index.scss b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/styles/index.scss new file mode 100644 index 0000000..2b8dca5 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/styles/index.scss @@ -0,0 +1,184 @@ +@import './variables.module.scss'; +@import './mixin.scss'; +@import './transition.scss'; +@import './element-ui.scss'; +@import './sidebar.scss'; +@import './btn.scss'; +@import './ruoyi.scss'; + +body { + height: 100%; + margin: 0; + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; + font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; +} + +label { + font-weight: 700; +} + +html { + height: 100%; + box-sizing: border-box; +} + +#app { + height: 100%; +} + +*, +*:before, +*:after { + box-sizing: inherit; +} + +.no-padding { + padding: 0px !important; +} + +.padding-content { + padding: 4px 0; +} + +a:focus, +a:active { + outline: none; +} + +a, +a:focus, +a:hover { + cursor: pointer; + color: inherit; + text-decoration: none; +} + +div:focus { + outline: none; +} + +.fr { + float: right; +} + +.fl { + float: left; +} + +.pr-5 { + padding-right: 5px; +} + +.pl-5 { + padding-left: 5px; +} + +.block { + display: block; +} + +.pointer { + cursor: pointer; +} + +.inlineBlock { + display: block; +} + +.clearfix { + &:after { + visibility: hidden; + display: block; + font-size: 0; + content: " "; + clear: both; + height: 0; + } +} + +aside { + background: #eef1f6; + padding: 8px 24px; + margin-bottom: 20px; + border-radius: 2px; + display: block; + line-height: 32px; + font-size: 16px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; + color: #2c3e50; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + a { + color: #337ab7; + cursor: pointer; + + &:hover { + color: rgb(32, 160, 255); + } + } +} + +//main-container全局样式 +.app-container { + padding: 20px; +} + +.components-container { + margin: 30px 50px; + position: relative; +} + +.pagination-container { + margin-top: 30px; +} + +.text-center { + text-align: center +} + +.sub-navbar { + height: 50px; + line-height: 50px; + position: relative; + width: 100%; + text-align: right; + padding-right: 20px; + transition: 600ms ease position; + background: linear-gradient(90deg, rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%, rgba(33, 120, 241, 1) 100%, rgba(33, 120, 241, 1) 100%); + + .subtitle { + font-size: 20px; + color: #fff; + } + + &.draft { + background: #d0d0d0; + } + + &.deleted { + background: #d0d0d0; + } +} + +.link-type, +.link-type:focus { + color: #337ab7; + cursor: pointer; + + &:hover { + color: rgb(32, 160, 255); + } +} + +.filter-container { + padding-bottom: 10px; + + .filter-item { + display: inline-block; + vertical-align: middle; + margin-bottom: 10px; + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/assets/styles/mixin.scss b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/styles/mixin.scss new file mode 100644 index 0000000..06fa061 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/styles/mixin.scss @@ -0,0 +1,66 @@ +@mixin clearfix { + &:after { + content: ""; + display: table; + clear: both; + } +} + +@mixin scrollBar { + &::-webkit-scrollbar-track-piece { + background: #d3dce6; + } + + &::-webkit-scrollbar { + width: 6px; + } + + &::-webkit-scrollbar-thumb { + background: #99a9bf; + border-radius: 20px; + } +} + +@mixin relative { + position: relative; + width: 100%; + height: 100%; +} + +@mixin pct($pct) { + width: #{$pct}; + position: relative; + margin: 0 auto; +} + +@mixin triangle($width, $height, $color, $direction) { + $width: $width/2; + $color-border-style: $height solid $color; + $transparent-border-style: $width solid transparent; + height: 0; + width: 0; + + @if $direction==up { + border-bottom: $color-border-style; + border-left: $transparent-border-style; + border-right: $transparent-border-style; + } + + @else if $direction==right { + border-left: $color-border-style; + border-top: $transparent-border-style; + border-bottom: $transparent-border-style; + } + + @else if $direction==down { + border-top: $color-border-style; + border-left: $transparent-border-style; + border-right: $transparent-border-style; + } + + @else if $direction==left { + border-right: $color-border-style; + border-top: $transparent-border-style; + border-bottom: $transparent-border-style; + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/assets/styles/ruoyi.scss b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/styles/ruoyi.scss new file mode 100644 index 0000000..7a7ccce --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/styles/ruoyi.scss @@ -0,0 +1,286 @@ + /** + * 通用css样式布局处理 + * Copyright (c) 2019 ruoyi + */ + + /** 基础通用 **/ +.pt5 { + padding-top: 5px; +} +.pr5 { + padding-right: 5px; +} +.pb5 { + padding-bottom: 5px; +} +.mt5 { + margin-top: 5px; +} +.mr5 { + margin-right: 5px; +} +.mb5 { + margin-bottom: 5px; +} +.mb8 { + margin-bottom: 8px; +} +.ml5 { + margin-left: 5px; +} +.mt10 { + margin-top: 10px; +} +.mr10 { + margin-right: 10px; +} +.mb10 { + margin-bottom: 10px; +} +.ml10 { + margin-left: 10px; +} +.mt20 { + margin-top: 20px; +} +.mr20 { + margin-right: 20px; +} +.mb20 { + margin-bottom: 20px; +} +.ml20 { + margin-left: 20px; +} + +.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 { + font-family: inherit; + font-weight: 500; + line-height: 1.1; + color: inherit; +} + +.flex-hc { + display: flex; + align-items: center; +} + +.el-form .el-form-item__label { + font-weight: 700; +} +.el-dialog:not(.is-fullscreen) { + margin-top: 6vh !important; +} + +.el-dialog.scrollbar .el-dialog__body { + overflow: auto; + overflow-x: hidden; + max-height: 70vh; + padding: 10px 20px 0; +} + +.el-table { + .el-table__header-wrapper, .el-table__fixed-header-wrapper { + th { + word-break: break-word; + background-color: #f8f8f9 !important; + color: #515a6e; + height: 40px !important; + font-size: 13px; + } + } + .el-table__body-wrapper { + .el-button [class*="el-icon-"] + span { + margin-left: 1px; + } + } +} + +/** 表单布局 **/ +.form-header { + font-size:15px; + color:#6379bb; + border-bottom:1px solid #ddd; + margin:8px 10px 25px 10px; + padding-bottom:5px +} + +/** 表格布局 **/ +.pagination-container { + position: relative; + height: 25px; + margin-bottom: 10px; + margin-top: 15px; + padding: 10px 20px !important; +} + +.el-dialog .pagination-container { + position: static !important; +} + +/* tree border */ +.tree-border { + margin-top: 5px; + border: 1px solid #e5e6e7; + background: #FFFFFF none; + border-radius:4px; + width: 100%; +} + +.pagination-container .el-pagination { + right: 0; + position: absolute; +} + +@media ( max-width : 768px) { + .pagination-container .el-pagination > .el-pagination__jump { + display: none !important; + } + .pagination-container .el-pagination > .el-pagination__sizes { + display: none !important; + } +} + +.el-table .fixed-width .el-button--small { + padding-left: 0; + padding-right: 0; + width: inherit; +} + +/** 表格更多操作下拉样式 */ +.el-table .el-dropdown-link { + cursor: pointer; + color: #409EFF; + margin-left: 10px; +} + +.el-table .el-dropdown, .el-icon-arrow-down { + font-size: 12px; +} + +.el-tree-node__content > .el-checkbox { + margin-right: 8px; +} + +.list-group-striped > .list-group-item { + border-left: 0; + border-right: 0; + border-radius: 0; + padding-left: 0; + padding-right: 0; +} + +.list-group { + padding-left: 0px; + list-style: none; +} + +.list-group-item { + border-bottom: 1px solid #e7eaec; + border-top: 1px solid #e7eaec; + margin-bottom: -1px; + padding: 11px 0px; + font-size: 13px; +} + +.pull-right { + float: right !important; +} + +.el-card__header { + padding: 14px 15px 7px !important; + min-height: 40px; +} + +.el-card__body { + padding: 15px 20px 20px 20px !important; +} + +.card-box { + padding-right: 15px; + padding-left: 15px; + margin-bottom: 10px; +} + +/* button color */ +.el-button--cyan.is-active, +.el-button--cyan:active { + background: #20B2AA; + border-color: #20B2AA; + color: #FFFFFF; +} + +.el-button--cyan:focus, +.el-button--cyan:hover { + background: #48D1CC; + border-color: #48D1CC; + color: #FFFFFF; +} + +.el-button--cyan { + background-color: #20B2AA; + border-color: #20B2AA; + color: #FFFFFF; +} + +/* text color */ +.text-navy { + color: #1ab394; +} + +.text-primary { + color: inherit; +} + +.text-success { + color: #1c84c6; +} + +.text-info { + color: #23c6c8; +} + +.text-warning { + color: #f8ac59; +} + +.text-danger { + color: #ed5565; +} + +.text-muted { + color: #888888; +} + +/* image */ +.img-circle { + border-radius: 50%; +} + +.img-lg { + width: 120px; + height: 120px; +} + +.avatar-upload-preview { + position: absolute; + top: 50%; + transform: translate(50%, -50%); + width: 200px; + height: 200px; + border-radius: 50%; + box-shadow: 0 0 4px #ccc; + overflow: hidden; +} + +/* 拖拽列样式 */ +.sortable-ghost{ + opacity: .8; + color: #fff!important; + background: #42b983!important; +} + +/* 表格右侧工具栏样式 */ +.top-right-btn { + margin-left: auto; +} diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/assets/styles/sidebar.scss b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/styles/sidebar.scss new file mode 100644 index 0000000..8b3c472 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/styles/sidebar.scss @@ -0,0 +1,238 @@ +#app { + + .main-container { + height: 100%; + transition: margin-left .28s; + margin-left: $base-sidebar-width; + position: relative; + } + + .sidebarHide { + margin-left: 0!important; + } + + .sidebar-container { + -webkit-transition: width .28s; + transition: width 0.28s; + width: $base-sidebar-width !important; + background-color: $base-menu-background; + height: 100%; + position: fixed; + font-size: 0px; + top: 0; + bottom: 0; + left: 0; + z-index: 1001; + overflow: hidden; + -webkit-box-shadow: 2px 0 6px rgba(0,21,41,.35); + box-shadow: 2px 0 6px rgba(0,21,41,.35); + + // reset element-ui css + .horizontal-collapse-transition { + transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out; + } + + .scrollbar-wrapper { + overflow-x: hidden !important; + } + + .el-scrollbar__bar.is-vertical { + right: 0px; + } + + .el-scrollbar { + height: 100%; + } + + &.has-logo { + .el-scrollbar { + height: calc(100% - 50px); + } + } + + .is-horizontal { + display: none; + } + + a { + display: inline-block; + width: 100%; + overflow: hidden; + } + + .svg-icon { + margin-right: 16px; + } + + .el-menu { + border: none; + height: 100%; + width: 100% !important; + } + + .el-menu-item, .menu-title { + overflow: hidden !important; + text-overflow: ellipsis !important; + white-space: nowrap !important; + } + + .el-menu-item .el-menu-tooltip__trigger { + display: inline-block !important; + } + + // menu hover + .sub-menu-title-noDropdown, + .el-sub-menu__title { + &:hover { + background-color: rgba(0, 0, 0, 0.06) !important; + } + } + + & .theme-dark .is-active > .el-sub-menu__title { + color: $base-menu-color-active !important; + } + + & .nest-menu .el-sub-menu>.el-sub-menu__title, + & .el-sub-menu .el-menu-item { + min-width: $base-sidebar-width !important; + + &:hover { + background-color: rgba(0, 0, 0, 0.06) !important; + } + } + + & .theme-dark .nest-menu .el-sub-menu>.el-sub-menu__title, + & .theme-dark .el-sub-menu .el-menu-item { + background-color: $base-sub-menu-background !important; + + &:hover { + background-color: $base-sub-menu-hover !important; + } + } + } + + .hideSidebar { + .sidebar-container { + width: 54px !important; + } + + .main-container { + margin-left: 54px; + } + + .sub-menu-title-noDropdown { + padding: 0 !important; + position: relative; + + .el-tooltip { + padding: 0 !important; + + .svg-icon { + margin-left: 20px; + } + } + } + + .el-sub-menu { + overflow: hidden; + + &>.el-sub-menu__title { + padding: 0 !important; + + .svg-icon { + margin-left: 20px; + } + + } + } + + .el-menu--collapse { + .el-sub-menu { + &>.el-sub-menu__title { + &>span { + height: 0; + width: 0; + overflow: hidden; + visibility: hidden; + display: inline-block; + } + &>i { + height: 0; + width: 0; + overflow: hidden; + visibility: hidden; + display: inline-block; + } + } + } + } + } + + .el-menu--collapse .el-menu .el-sub-menu { + min-width: $base-sidebar-width !important; + } + + // mobile responsive + .mobile { + .main-container { + margin-left: 0px; + } + + .sidebar-container { + transition: transform .28s; + width: $base-sidebar-width !important; + } + + &.hideSidebar { + .sidebar-container { + pointer-events: none; + transition-duration: 0.3s; + transform: translate3d(-$base-sidebar-width, 0, 0); + } + } + } + + .withoutAnimation { + + .main-container, + .sidebar-container { + transition: none; + } + } +} + +// when menu collapsed +.el-menu--vertical { + &>.el-menu { + .svg-icon { + margin-right: 16px; + } + } + + .nest-menu .el-sub-menu>.el-sub-menu__title, + .el-menu-item { + &:hover { + // you can use $sub-menuHover + background-color: rgba(0, 0, 0, 0.06) !important; + } + } + + // the scroll bar appears when the sub-menu is too long + >.el-menu--popup { + max-height: 100vh; + overflow-y: auto; + + &::-webkit-scrollbar-track-piece { + background: #d3dce6; + } + + &::-webkit-scrollbar { + width: 6px; + } + + &::-webkit-scrollbar-thumb { + background: #99a9bf; + border-radius: 20px; + } + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/assets/styles/transition.scss b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/styles/transition.scss new file mode 100644 index 0000000..073f8c6 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/styles/transition.scss @@ -0,0 +1,49 @@ +// global transition css + +/* fade */ +.fade-enter-active, +.fade-leave-active { + transition: opacity 0.28s; +} + +.fade-enter, +.fade-leave-active { + opacity: 0; +} + +/* fade-transform */ +.fade-transform--move, +.fade-transform-leave-active, +.fade-transform-enter-active { + transition: all .5s; +} + +.fade-transform-enter { + opacity: 0; + transform: translateX(-30px); +} + +.fade-transform-leave-to { + opacity: 0; + transform: translateX(30px); +} + +/* breadcrumb transition */ +.breadcrumb-enter-active, +.breadcrumb-leave-active { + transition: all .5s; +} + +.breadcrumb-enter, +.breadcrumb-leave-active { + opacity: 0; + transform: translateX(20px); +} + +.breadcrumb-move { + transition: all .5s; +} + +.breadcrumb-leave-active { + position: absolute; +} diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/assets/styles/variables.module.scss b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/styles/variables.module.scss new file mode 100644 index 0000000..3dbfaa7 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/assets/styles/variables.module.scss @@ -0,0 +1,65 @@ +// base color +$blue: #324157; +$light-blue: #3A71A8; +$red: #C03639; +$pink: #E65D6E; +$green: #30B08F; +$tiffany: #4AB7BD; +$yellow: #FEC171; +$panGreen: #30B08F; + +// 默认菜单主题风格 +$base-menu-color: #bfcbd9; +$base-menu-color-active: #f4f4f5; +$base-menu-background: #304156; +$base-logo-title-color: #ffffff; + +$base-menu-light-color: rgba(0, 0, 0, 0.7); +$base-menu-light-background: #ffffff; +$base-logo-light-title-color: #001529; + +$base-sub-menu-background: #1f2d3d; +$base-sub-menu-hover: #001528; + +// 自定义暗色菜单风格 +/** +$base-menu-color:hsla(0,0%,100%,.65); +$base-menu-color-active:#fff; +$base-menu-background:#001529; +$base-logo-title-color: #ffffff; + +$base-menu-light-color:rgba(0,0,0,.70); +$base-menu-light-background:#ffffff; +$base-logo-light-title-color: #001529; + +$base-sub-menu-background:#000c17; +$base-sub-menu-hover:#001528; +*/ + +$--color-primary: #409EFF; +$--color-success: #67C23A; +$--color-warning: #E6A23C; +$--color-danger: #F56C6C; +$--color-info: #909399; + +$base-sidebar-width: 200px; + +// the :export directive is the magic sauce for webpack +// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass +:export { + menuColor: $base-menu-color; + menuLightColor: $base-menu-light-color; + menuColorActive: $base-menu-color-active; + menuBackground: $base-menu-background; + menuLightBackground: $base-menu-light-background; + subMenuBackground: $base-sub-menu-background; + subMenuHover: $base-sub-menu-hover; + sideBarWidth: $base-sidebar-width; + logoTitleColor: $base-logo-title-color; + logoLightTitleColor: $base-logo-light-title-color; + primaryColor: $--color-primary; + successColor: $--color-success; + dangerColor: $--color-danger; + infoColor: $--color-info; + warningColor: $--color-warning; +} diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/SvgIcon/index.vue b/smart-flow-api/warm-flow/warm-flow-ui/src/components/SvgIcon/index.vue new file mode 100644 index 0000000..8c101f6 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/SvgIcon/index.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/SvgIcon/svgicon.js b/smart-flow-api/warm-flow/warm-flow-ui/src/components/SvgIcon/svgicon.js new file mode 100644 index 0000000..4431719 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/SvgIcon/svgicon.js @@ -0,0 +1,10 @@ +import * as components from '@element-plus/icons-vue' + +export default { + install: (app) => { + for (const key in components) { + const componentConfig = components[key]; + app.component(componentConfig.name, componentConfig); + } + }, +}; diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/classics/initClassicsData.json b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/classics/initClassicsData.json new file mode 100644 index 0000000..b80aecc --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/classics/initClassicsData.json @@ -0,0 +1,146 @@ +{ + "nodes": [ + { + "id": "start", + "type": "start", + "x": 260, + "y": 200, + "properties": { + "nodeRatio": "0", + "permissionFlag": null, + "anyNodeSkip": null, + "listenerType": null, + "listenerPath": null, + "formCustom": null, + "formPath": null, + "status": null, + "chartStatusColor": null, + "promptContent": null, + "ext": {}, + "width": 40, + "height": 40 + }, + "text": { + "x": 260, + "y": 200, + "value": "开始" + } + }, + { + "id": "submit", + "type": "between", + "x": 420, + "y": 200, + "properties": { + "nodeRatio": "0", + "permissionFlag": null, + "anyNodeSkip": null, + "listenerType": null, + "listenerPath": null, + "formCustom": null, + "formPath": null, + "status": null, + "chartStatusColor": null, + "promptContent": null, + "ext": {}, + "width": 100, + "height": 80 + }, + "text": { + "x": 420, + "y": 200, + "value": "中间节点" + } + }, + { + "id": "end", + "type": "end", + "x": 597, + "y": 200, + "properties": { + "nodeRatio": "0", + "permissionFlag": null, + "anyNodeSkip": null, + "listenerType": null, + "listenerPath": null, + "formCustom": null, + "formPath": null, + "status": null, + "chartStatusColor": null, + "promptContent": null, + "ext": {}, + "width": 40, + "height": 40 + }, + "text": { + "x": 597, + "y": 200, + "value": "结束" + } + } + ], + "edges": [ + { + "id": "90fdf94f-a37f-4920-ac97-350a3dccff4d", + "type": "skip", + "properties": { + "skipCondition": null, + "skipName": null, + "skipType": "PASS", + "status": null, + "chartStatusColor": null + }, + "sourceNodeId": "start", + "targetNodeId": "submit", + "sourceAnchorId": "start_1", + "targetAnchorId": "submit_3", + "startPoint": { + "x": 280, + "y": 200 + }, + "endPoint": { + "x": 370, + "y": 200 + }, + "pointsList": [ + { + "x": 280, + "y": 200 + }, + { + "x": 370, + "y": 200 + } + ] + }, + { + "id": "dc40f6b7-6a81-4f15-8626-ceb54e50e13c", + "type": "skip", + "properties": { + "skipType": "PASS" + }, + "sourceNodeId": "submit", + "targetNodeId": "end", + "sourceAnchorId": "submit_1", + "targetAnchorId": "end_3", + "startPoint": { + "x": 470, + "y": 200 + }, + "endPoint": { + "x": 577, + "y": 200 + }, + "pointsList": [ + { + "x": 470, + "y": 200 + }, + { + "x": 577, + "y": 200 + } + ] + } + ] +} diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/classics/js/between.js b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/classics/js/between.js new file mode 100644 index 0000000..520871d --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/classics/js/between.js @@ -0,0 +1,81 @@ +import {RectNode, RectNodeModel, h} from "@logicflow/core"; +import {setCommonStyle} from "@/components/design/common/js/tool.js"; + +class BetweenModel extends RectNodeModel { + + initNodeData(data) { + super.initNodeData(data); + this.width = 100; + this.height = 80; + this.radius = 5; + } + getNodeStyle() { + return setCommonStyle(super.getNodeStyle(), this.properties, "node"); + } +} + +class BetweenView extends RectNode { + + getLabelShape() { + const {model} = this.props; + const {x, y, width, height, text} = model; + const style = model.getNodeStyle(); + + // 创建一个包含图标和文本的容器 + return h( + 'svg', + { + x: x - width / 2 + 2, + y: y - height / 2 + 2, + width: 25, + height: 25, + viewBox: '0 0 1024 1024', + }, + [ + h('path', { + d: 'M135.314286 877.714286h735.085714v-208.457143s-69.485714-128-215.771429-160.914286h-285.257142c-135.314286 40.228571-237.714286 179.2-237.714286 179.2l3.657143 190.171429z', + fill: '#FCF7EC' + }), + h('path', { + d: 'M676.571429 457.142857H362.057143c-149.942857 47.542857-266.971429 204.8-266.971429 204.8v256H914.285714v-277.942857c3.657143 0-73.142857-138.971429-237.714285-182.857143zM863.085714 877.714286H138.971429v-190.171429s102.4-142.628571 237.714285-179.2h285.257143c146.285714 36.571429 204.8 153.6 204.8 153.6V877.714286h-3.657143z', + fill: style.stroke ? style.stroke : '#2B2F36', + }), + h('path', { + d: 'M650.971429 446.171429c36.571429-36.571429 58.514286-84.114286 58.514285-138.971429 0-21.942857-3.657143-43.885714-10.971428-62.171429v-7.314285c-29.257143-73.142857-98.742857-128-182.857143-128-109.714286 0-197.485714 87.771429-197.485714 197.485714 0 58.514286 25.6 113.371429 69.485714 149.942857-14.628571 14.628571-25.6 25.6-25.6 51.2 0 32.914286 0 54.857143 43.885714 98.742857s201.142857 40.228571 245.028572 0c40.228571-40.228571 36.571429-84.114286 36.571428-113.371428-3.657143-18.285714-21.942857-40.228571-36.571428-47.542857zM354.742857 307.2z m288.914286 182.857143v14.628571c0 7.314286 0 25.6-3.657143 36.571429s-14.628571 25.6-18.285714 29.257143l-3.657143 3.657143c-3.657143 3.657143-3.657143 3.657143-7.314286 3.657142s-14.628571 7.314286-32.914286 10.971429c-3.657143 0-10.971429 3.657143-14.628571 3.657143-18.285714 3.657143-43.885714 3.657143-65.828571 0-7.314286 0-14.628571 0-21.942858-3.657143-7.314286 0-10.971429-3.657143-14.628571-3.657143-14.628571-3.657143-18.285714-7.314286-21.942857-7.314286l-3.657143-3.657142c-7.314286-3.657143-14.628571-14.628571-14.628571-14.628572l-3.657143-3.657143c-3.657143-7.314286-14.628571-18.285714-14.628572-21.942857s-3.657143-18.285714-3.657143-25.6v-3.657143s0-7.314286 3.657143-14.628571c0-3.657143 3.657143-7.314286 3.657143-7.314286l3.657143-3.657143c29.257143 18.285714 65.828571 29.257143 102.4 29.257143 43.885714 0 80.457143-14.628571 113.371429-36.571428 3.657143 0 3.657143 3.657143 7.314285 7.314285l10.971429 10.971429c0-3.657143 0-3.657143 0 0z m-7.314286-95.085714c-7.314286 10.971429-14.628571 21.942857-21.942857 29.257142-25.6 21.942857-62.171429 40.228571-102.4 40.228572-25.6 0-51.2-7.314286-73.142857-18.285714l14.628571-14.628572s-25.6-10.971429-32.914285-14.628571c-3.657143 0-7.314286-3.657143-14.628572-10.971429-7.314286-3.657143-10.971429-10.971429-18.285714-14.628571-3.657143-7.314286-10.971429-10.971429-14.628572-14.628572-3.657143-3.657143-7.314286-7.314286-7.314285-10.971428v-7.314286s47.542857-40.228571 87.771428-47.542857c40.228571-10.971429 58.514286-3.657143 58.514286-3.657143s7.314286 3.657143 18.285714 3.657143c10.971429 3.657143 25.6 3.657143 36.571429 3.657143s21.942857 0 36.571428-3.657143c7.314286 0 10.971429-3.657143 18.285715-3.657143s10.971429-3.657143 14.628571-3.657143c14.628571-3.657143 18.285714-7.314286 18.285714-7.314286s0 21.942857-3.657142 36.571429c0 7.314286-3.657143 18.285714-7.314286 29.257143 3.657143 10.971429-3.657143 21.942857-7.314286 32.914286zM288.914286 749.714286h54.857143V877.714286H288.914286zM658.285714 749.714286h54.857143V877.714286H658.285714z', + fill: style.stroke ? style.stroke : '#2B2F36', + }), + h('path', { + d: 'M365.714286 358.4s47.542857-40.228571 87.771428-47.542857c40.228571-10.971429 58.514286-3.657143 58.514286-3.657143s32.914286 7.314286 54.857143 7.314286 51.2-3.657143 69.485714-10.971429l18.285714-7.314286s3.657143 21.942857 0 40.228572c-3.657143 14.628571-10.971429 40.228571-21.942857 58.514286-14.628571 21.942857-29.257143 40.228571-40.228571 51.2 0 0 21.942857 14.628571 25.6 18.285714 0 0 7.314286 3.657143 14.628571 10.971428 7.314286 7.314286 10.971429 10.971429 10.971429 14.628572v14.628571c0 7.314286 0 25.6-3.657143 36.571429s-14.628571 25.6-18.285714 29.257143l-10.971429 10.971428c-3.657143 3.657143-25.6 10.971429-47.542857 14.628572-21.942857 3.657143-62.171429 3.657143-87.771429 0-25.6-7.314286-32.914286-10.971429-36.571428-10.971429s-18.285714-18.285714-18.285714-18.285714-14.628571-18.285714-18.285715-25.6-3.657143-29.257143-3.657143-29.257143-3.657143-14.628571 7.314286-25.6 32.914286-43.885714 32.914286-43.885714-7.314286-7.314286-21.942857-14.628572c-3.657143-3.657143-21.942857-18.285714-32.914286-29.257143-10.971429-10.971429-18.285714-18.285714-21.942857-25.6 0-14.628571 0-14.628571 3.657143-14.628571z', + fill: '#FCF7EC' + }) + ] + ); + } + + // 自定义节点外观 + getShape() { + const { model } = this.props; + const { x, y, width, height, radius } = model; + const style = model.getNodeStyle(); + return h('g', {style: { + cursor: 'pointer' + }}, [ + h('rect', { + ...style, + x: x - width / 2, // 矩形默认x,y代表左上角顶点坐标,切换为中心点 + y: y - height / 2, + rx: radius, + ry: radius, + width, + height, + }), + this.getLabelShape(), + ]); + } +} + +export default { + type: "between", + model: BetweenModel, + view: BetweenView, +}; diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/classics/js/end.js b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/classics/js/end.js new file mode 100644 index 0000000..008e075 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/classics/js/end.js @@ -0,0 +1,24 @@ +import { CircleNode, CircleNodeModel } from "@logicflow/core"; +import {setCommonStyle} from "@/components/design/common/js/tool.js"; + +class endModel extends CircleNodeModel { + + initNodeData(data) { + super.initNodeData(data); + this.r = 25 + } + + getNodeStyle() { + const style = setCommonStyle(super.getNodeStyle(), this.properties, "node"); + style.strokeWidth = 4; + return style; + } +} + +class endView extends CircleNode {} + +export default { + type: "end", + model: endModel, + view: endView, +}; diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/classics/js/inclusive.js b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/classics/js/inclusive.js new file mode 100644 index 0000000..0310b13 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/classics/js/inclusive.js @@ -0,0 +1,62 @@ +import { h, PolygonNode, PolygonNodeModel } from '@logicflow/core' +import {setCommonStyle} from "@/components/design/common/js/tool.js"; + +class InclusiveModel extends PolygonNodeModel { + static extendKey = 'InclusiveModel'; + constructor (data, graphModel) { + if (!data.text) { + data.text = '' + } + if (data.text && typeof data.text === 'string') { + data.text = { + value: data.text, + x: data.x, + y: data.y + 40 + } + } + super(data, graphModel) + this.points = [ + [25, 0], + [50, 25], + [25, 50], + [0, 25] + ] + } + + getNodeStyle() { + return setCommonStyle(super.getNodeStyle(), this.properties, "node"); + } +} + +class InclusiveView extends PolygonNode { + static extendKey = 'InclusiveNode'; + getShape () { + const { model } = this.props + const { x, y, width, height, points } = model + const style = model.getNodeStyle() + return h( + 'g', + { + transform: `matrix(1 0 0 1 ${x - width / 2} ${y - height / 2})` + }, + h('polygon', { + ...style, + x, + y, + points + }), + h('circle', { + cx: 25, + cy: 25, + r: 12, + ...style, + }) + ) + } +} + +export default { + type: 'inclusive', + view: InclusiveView, + model: InclusiveModel +}; diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/classics/js/parallel.js b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/classics/js/parallel.js new file mode 100644 index 0000000..bc0dedd --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/classics/js/parallel.js @@ -0,0 +1,62 @@ +import { h, PolygonNode, PolygonNodeModel } from '@logicflow/core' +import {setCommonStyle} from "@/components/design/common/js/tool.js"; + +class ParallelModel extends PolygonNodeModel { + static extendKey = 'ParallelModel'; + constructor (data, graphModel) { + if (!data.text) { + data.text = '' + } + if (data.text && typeof data.text === 'string') { + data.text = { + value: data.text, + x: data.x, + y: data.y + 40 + } + } + super(data, graphModel) + this.points = [ + [25, 0], + [50, 25], + [25, 50], + [0, 25] + ] + } + + getNodeStyle() { + return setCommonStyle(super.getNodeStyle(), this.properties, "node"); + } + +} + +class ParallelView extends PolygonNode { + static extendKey = 'ParallelNode'; + getShape () { + const { model } = this.props + const { x, y, width, height, points } = model + const style = model.getNodeStyle() + return h( + 'g', + { + transform: `matrix(1 0 0 1 ${x - width / 2} ${y - height / 2})` + }, + h('polygon', { + ...style, + x, + y, + points + }), + h('path', { + d: + 'm 23,10 0,12.5 -12.5,0 0,5 12.5,0 0,12.5 5,0 0,-12.5 12.5,0 0,-5 -12.5,0 0,-12.5 -5,0 z', + ...style + }) + ) + } +} + +export default { + type: 'parallel', + view: ParallelView, + model: ParallelModel +}; diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/classics/js/serial.js b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/classics/js/serial.js new file mode 100644 index 0000000..622a5f9 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/classics/js/serial.js @@ -0,0 +1,61 @@ +import { h, PolygonNode, PolygonNodeModel } from '@logicflow/core' +import {setCommonStyle} from "@/components/design/common/js/tool.js"; + +class SerialModel extends PolygonNodeModel { + static extendKey = 'SerialModel'; + constructor (data, graphModel) { + if (!data.text) { + data.text = '' + } + if (data.text && typeof data.text === 'string') { + data.text = { + value: data.text, + x: data.x, + y: data.y + 40 + } + } + super(data, graphModel) + this.points = [ + [25, 0], + [50, 25], + [25, 50], + [0, 25] + ] + } + + getNodeStyle() { + return setCommonStyle(super.getNodeStyle(), this.properties, "node"); + } +} + +class SerialView extends PolygonNode { + static extendKey = 'SerialNode'; + getShape () { + const { model } = this.props + const { x, y, width, height, points } = model + const style = model.getNodeStyle() + return h( + 'g', + { + transform: `matrix(1 0 0 1 ${x - width / 2} ${y - height / 2})` + }, + h('polygon', { + ...style, + x, + y, + points + }), + h('path', { + d: + 'm 16,15 7.42857142857143,9.714285714285715 -7.42857142857143,9.714285714285715 3.428571428571429,0 5.714285714285715,-7.464228571428572 5.714285714285715,7.464228571428572 3.428571428571429,0 -7.42857142857143,-9.714285714285715 7.42857142857143,-9.714285714285715 -3.428571428571429,0 -5.714285714285715,7.464228571428572 -5.714285714285715,-7.464228571428572 -3.428571428571429,0 z', + ...style + }) + ) + } +} + +export default { + type: 'serial', + view: SerialView, + model: SerialModel +}; diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/classics/js/skip.js b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/classics/js/skip.js new file mode 100644 index 0000000..e29e610 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/classics/js/skip.js @@ -0,0 +1,36 @@ +import { PolylineEdge, PolylineEdgeModel } from "@logicflow/core"; +import {setCommonStyle} from "@/components/design/common/js/tool.js"; + +class SkipModel extends PolylineEdgeModel { + setAttributes() { + this.offset = 20; + } + + getEdgeStyle() { + return setCommonStyle(super.getEdgeStyle(), this.properties, "skip"); + + } + + getTextStyle() { + const style = super.getTextStyle(); + style.background = {fill: "#fff"}; + return style; + } + + /** + * 重写此方法,使保存数据是能带上锚点数据。 + */ + getData() { + const data = super.getData(); + data.sourceAnchorId = this.sourceAnchorId; + data.targetAnchorId = this.targetAnchorId; + return data; + } + +} + +export default { + type: "skip", + view: PolylineEdge, + model: SkipModel, +}; diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/classics/js/start.js b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/classics/js/start.js new file mode 100644 index 0000000..085e5fe --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/classics/js/start.js @@ -0,0 +1,22 @@ +import { CircleNode, CircleNodeModel } from "@logicflow/core"; +import {setCommonStyle} from "@/components/design/common/js/tool.js"; + +class StartModel extends CircleNodeModel { + + initNodeData(data) { + super.initNodeData(data); + this.r = 25 + } + + getNodeStyle() { + return setCommonStyle(super.getNodeStyle(), this.properties, "node"); + } +} + +class StartView extends CircleNode {} + +export default { + type: "start", + model: StartModel, + view: StartView, +}; diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/common/js/tool.js b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/common/js/tool.js new file mode 100644 index 0000000..ddcadba --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/common/js/tool.js @@ -0,0 +1,322 @@ +const NODE_TYPE_MAP = {0: 'start', 1: 'between', 2: 'end', 3: 'serial', 4: 'parallel', 5: 'inclusive'} + +/** + * 将warm-flow的定义json数据转成LogicFlow支持的数据格式 + * @param {*} json + * @returns LogicFlow的数据 + */ +export const json2LogicFlowJson = (definition) => { + const graphData = { + nodes: [], + edges: [] + } + // 解析definition属性 + graphData.flowCode = definition.flowCode + graphData.flowName = definition.flowName + graphData.modelValue = definition.modelValue + graphData.category = definition.category + graphData.version = definition.version + graphData.formCustom = definition.formCustom + graphData.formPath = definition.formPath + graphData.listenerType = definition.listenerType + graphData.listenerPath = definition.listenerPath + + if (!definition.nodeList) { + return graphData; + } + // 解析节点 + const allSkips = definition.nodeList.reduce((acc, node) => { + if (node.skipList && Array.isArray(node.skipList)) { + acc.push(...node.skipList); + } + return acc; + }, []) + const allNodes = definition.nodeList; + // 解析节点 + if (allNodes.length) { + for (var i = 0, len = allNodes.length; i < len; i++) { + let node = allNodes[i] + let lfNode = { + text:{}, + properties: {} + } + // 处理节点 + lfNode.type = NODE_TYPE_MAP[node.nodeType] + lfNode.id = node.nodeCode + let coordinate = node.coordinate + if (coordinate) { + const attr = coordinate.split('|') + const nodeXy = attr[0].split(',') + lfNode.x = parseInt(nodeXy[0]) + lfNode.y = parseInt(nodeXy[1]) + if (attr.length === 2) { + const textXy = attr[1].split(',') + lfNode.text.x = parseInt(textXy[0]) + lfNode.text.y = parseInt(textXy[1]) + } + } + lfNode.text.value = node.nodeName + lfNode.properties.nodeRatio = node.nodeRatio.toString() + lfNode.properties.permissionFlag = node.permissionFlag + lfNode.properties.anyNodeSkip = node.anyNodeSkip + lfNode.properties.listenerType = node.listenerType + lfNode.properties.listenerPath = node.listenerPath + lfNode.properties.formCustom = node.formCustom + lfNode.properties.formPath = node.formPath + lfNode.properties.status = node.status + lfNode.properties.chartStatusColor = definition.chartStatusColor + lfNode.properties.promptContent = node.promptContent + lfNode.properties.ext = {}; + if (node.ext && typeof node.ext === "string") { + try { + node.ext = JSON.parse(node.ext); + node.ext.forEach(e => { + lfNode.properties.ext[e.code] = String(e.value).includes(",") ? e.value.split(",") : String(e.value); + }); + } catch (error) { + console.error("Error parsing JSON:", error); + } + } + graphData.nodes.push(lfNode) + } + } + if (allSkips.length) { + // 处理边 + let skipEle = null + let edge = {} + for (var j = 0, lenn = allSkips.length; j < lenn; j++) { + skipEle = allSkips[j] + edge = { + text: {}, + properties: {}, + } + edge.id = skipEle.id + edge.type = 'skip' + edge.sourceNodeId = skipEle.nowNodeCode + edge.targetNodeId = skipEle.nextNodeCode + edge.text = { value: skipEle.skipName } + edge.properties.skipCondition = skipEle.skipCondition + edge.properties.skipName = skipEle.skipName + edge.properties.skipType = skipEle.skipType + edge.properties.status = skipEle.status + edge.properties.chartStatusColor = definition.chartStatusColor + const expr = skipEle.expr + if (expr) { + edge.properties.expr = skipEle.expr + } + const coordinate = skipEle.coordinate + if (coordinate) { + const coordinateXy = coordinate.split('|') + edge.pointsList = [] + coordinateXy[0].split(';').forEach((item) => { + const pointArr = item.split(',') + edge.pointsList.push({ + x: parseInt(pointArr[0]), + y: parseInt(pointArr[1]) + }) + }) + edge.startPoint = edge.pointsList[0] + edge.endPoint = edge.pointsList[edge.pointsList.length - 1] + if (coordinateXy.length > 1) { + let textXy = coordinateXy[1].split(","); + edge.text.x = parseInt(textXy[0]) + edge.text.y = parseInt(textXy[1]) + } + } + graphData.edges.push(edge) + } + } + + return graphData +} + +/** + * 将LogicFlow的数据转成warm-flow的json定义文件 + * @param {*} data(...definitionInfo,nodes,edges) + * @returns + */ +export const logicFlowJsonToWarmFlow = (data) => { + // 先构建成流程对象 + const definition = { + nodeList : [] + } + + /** + * 根据节点的类型值,获取key + * @param {*} mapValue 节点类型映射 + * @returns + */ + const getNodeTypeValue = (mapValue) => { + for (const key in NODE_TYPE_MAP) { + if (NODE_TYPE_MAP[key] === mapValue) { + return key + } + } + } + /** + * 根据节点的编码,获取节点的类型 + * @param {*} nodeCode 当前节点名称 + * @returns + */ + const getNodeType = (nodeCode) => { + for (const node of definition.nodeList) { + if (nodeCode === node.nodeCode) { + return node.nodeType + } + } + } + /** + * 拼接skip坐标 + * @param {*} edge logicFlow的edge + * @returns + */ + const getCoordinate = (edge) => { + let coordinate = '' + for (let i = 0; i < edge.pointsList.length; i++) { + coordinate = coordinate + parseInt(edge.pointsList[i].x) + ',' + parseInt(edge.pointsList[i].y) + if (i !== edge.pointsList.length - 1) { + coordinate = coordinate + ';' + } + } + if (edge.text) { + coordinate = coordinate + '|' + parseInt(edge.text.x) + ',' + parseInt(edge.text.y) + } + return coordinate + } + + // 流程定义 + definition.id = data.id + definition.flowCode = data.flowCode + definition.flowName = data.flowName + definition.modelValue = data.modelValue + definition.category = data.category + definition.version = data.version + definition.formCustom = data.formCustom + definition.formPath = data.formPath + definition.listenerType = data.listenerType + definition.listenerPath = data.listenerPath + // 流程节点 + data.nodes.forEach(anyNode => { + let node = {} + node.nodeType = getNodeTypeValue(anyNode.type) + node.nodeCode = anyNode.id + if (anyNode.text) { + node.nodeName = anyNode.text.value + } + node.permissionFlag = anyNode.properties.permissionFlag + node.nodeRatio = anyNode.properties.nodeRatio + node.anyNodeSkip = anyNode.properties.anyNodeSkip + node.listenerType = anyNode.properties.listenerType + node.listenerPath = anyNode.properties.listenerPath + node.formCustom = anyNode.properties.formCustom + node.formPath = anyNode.properties.formPath + node.ext = []; + for (const key in anyNode.properties.ext) { + if (Object.prototype.hasOwnProperty.call(anyNode.properties.ext, key)) { + let e = anyNode.properties.ext[key]; + node.ext.push({ code: key, value: Array.isArray(e) ? e.join(",") : e }); + } + } + node.ext = JSON.stringify(node.ext); + node.coordinate = anyNode.x + ',' + anyNode.y + if (anyNode.text && anyNode.text.x && anyNode.text.y) { + node.coordinate = node.coordinate + '|' + anyNode.text.x + ',' + anyNode.text.y + } + node.handlerType = anyNode.properties.handlerType + node.handlerPath = anyNode.properties.handlerPath + node.version = definition.version + node.skipList = [] + data.edges.forEach(anyEdge => { + if (anyEdge.sourceNodeId === anyNode.id) { + let skip = {} + skip.skipType = anyEdge.properties.skipType + skip.skipCondition = anyEdge.properties.skipCondition + skip.skipName = anyEdge?.text?.value || anyEdge.properties.skipName + skip.nowNodeCode = anyEdge.sourceNodeId + skip.nowNodeType = getNodeType(skip.nowNodeCode) + skip.nextNodeCode = anyEdge.targetNodeId + skip.nextNodeType = getNodeType(skip.nextNodeCode) + skip.coordinate = getCoordinate(anyEdge) + node.skipList.push(skip) + } + }) + definition.nodeList.push(node) + }) + return JSON.stringify(definition) +} + +export const setCommonStyle = (style, properties, nodeType, type) => { + // 从 chartStatusColor 数组中提取颜色值 + const [doneColor, todoColor, notDoneColor] = (properties.chartStatusColor && + properties.chartStatusColor.length === 3) ? properties.chartStatusColor : + ["135,206,250", "255,197,90", "166,178,189"]; // 提供默认值 + + if (properties.status === 2) { + // 使用活跃状态的 RGB 颜色 + if (nodeType === 'node' && type !== "mimic") { + style.fill = `rgba(${doneColor}, 0.15)`; // 带透明度 + } + style.stroke = `rgb(${doneColor})`; + } else if (properties.status === 1) { + // 使用非活跃状态的 RGB 颜色 + if (nodeType === 'node' && type !== "mimic") { + style.fill = `rgba(${todoColor}, 0.15)`; + } + style.stroke = `rgb(${todoColor})`; + style.strokeDasharray = "5 4"; + } else { + // 默认状态 + if (nodeType === 'node' && type !== "mimic") { + style.fill = `rgba(255, 255, 255)`; + } + style.stroke = `rgb(${notDoneColor})`; + } + + style.strokeWidth = 1.5 + style.cursor = 'pointer' + return style; +} + +export function getPreviousNodes(nodes, skips, nowNodeCode) { + let previousCode = getPreviousCode(skips, nowNodeCode, new Set()); + // 使用 Set 去重后再转换为数组 + const uniquePreviousCode = [...new Set(previousCode)]; + return nodes.filter(node => uniquePreviousCode.includes(node.id)).reverse(); +} + +function getPreviousCode(skips, nowNodeCode, visited = new Set()) { + // 防止循环引用导致的无限递归 + if (visited.has(nowNodeCode)) { + return []; + } + + visited.add(nowNodeCode); + let passSkip = skips.filter(skip => skip.properties.skipType === "PASS"); + const previousCode = []; + + for (const skip of passSkip) { + if (skip.targetNodeId === nowNodeCode) { + previousCode.push(skip.sourceNodeId); + // 递归获取更前面的节点 + const ancestors = getPreviousCode(passSkip, skip.sourceNodeId, visited); + previousCode.push(...ancestors); + } + } + + return previousCode; +} + +/** + * 判断是否经典模式 + */ +export function isClassics(modelValue) { + return "CLASSICS" === modelValue +} + +/** + * 判断是否网关节点 + */ +export function isGateWay(nodeType) { + return ['serial', 'parallel', 'inclusive'].includes(nodeType); + +} diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/common/vue/baseInfo.vue b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/common/vue/baseInfo.vue new file mode 100644 index 0000000..168d362 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/common/vue/baseInfo.vue @@ -0,0 +1,277 @@ + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/common/vue/between.vue b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/common/vue/between.vue new file mode 100644 index 0000000..549ed97 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/common/vue/between.vue @@ -0,0 +1,641 @@ + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/common/vue/end.vue b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/common/vue/end.vue new file mode 100644 index 0000000..1aa06b6 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/common/vue/end.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/common/vue/gateway.vue b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/common/vue/gateway.vue new file mode 100644 index 0000000..2a1bde4 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/common/vue/gateway.vue @@ -0,0 +1,33 @@ + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/common/vue/nodeExtList.vue b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/common/vue/nodeExtList.vue new file mode 100644 index 0000000..016a182 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/common/vue/nodeExtList.vue @@ -0,0 +1,160 @@ + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/common/vue/propertySetting.vue b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/common/vue/propertySetting.vue new file mode 100644 index 0000000..4d11163 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/common/vue/propertySetting.vue @@ -0,0 +1,336 @@ + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/common/vue/selectUser.vue b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/common/vue/selectUser.vue new file mode 100644 index 0000000..fd75192 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/common/vue/selectUser.vue @@ -0,0 +1,360 @@ + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/common/vue/skip.vue b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/common/vue/skip.vue new file mode 100644 index 0000000..5ec2220 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/common/vue/skip.vue @@ -0,0 +1,149 @@ + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/common/vue/start.vue b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/common/vue/start.vue new file mode 100644 index 0000000..5daaae4 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/common/vue/start.vue @@ -0,0 +1,126 @@ + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/initMimicData.json b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/initMimicData.json new file mode 100644 index 0000000..4ac515c --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/initMimicData.json @@ -0,0 +1,110 @@ +{ + "nodes": [ + { + "id": "startId", + "type": "start", + "x": 700, + "y": 100, + "properties": { + "width": 220, + "height": 80 + }, + "text": { + "x": 700, + "y": 100, + "value": "开始" + } + }, + { + "id": "betweenId", + "type": "between", + "x": 700, + "y": 250, + "properties": { + "collaborativeWay": "1", + "width": 220, + "height": 80 + }, + "text": { + "x": 700, + "y": 250, + "value": "中间节点" + } + }, + { + "id": "endId", + "type": "end", + "x": 700, + "y": 380, + "properties": { + "width": 40, + "height": 40 + }, + "text": { + "x": 700, + "y": 380, + "value": "结束" + } + } + ], + "edges": [ + { + "id": "startToBetweenId", + "type": "skip", + "properties": { + "skipType": "PASS" + }, + "sourceNodeId": "startId", + "targetNodeId": "betweenId", + "sourceAnchorId": "startId_2", + "targetAnchorId": "betweenId_0", + "startPoint": { + "x": 700, + "y": 140 + }, + "endPoint": { + "x": 700, + "y": 210 + }, + "pointsList": [ + { + "x": 700, + "y": 140 + }, + { + "x": 700, + "y": 210 + } + ] + }, + { + "id": "betweenToEndId", + "type": "skip", + "properties": { + "skipType": "PASS" + }, + "sourceNodeId": "betweenId", + "targetNodeId": "endId", + "sourceAnchorId": "betweenId_2", + "targetAnchorId": "endId_0", + "startPoint": { + "x": 700, + "y": 290 + }, + "endPoint": { + "x": 700, + "y": 360 + }, + "pointsList": [ + { + "x": 700, + "y": 290 + }, + { + "x": 700, + "y": 360 + } + ] + } + ] +} diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/js/baseNodeModel.js b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/js/baseNodeModel.js new file mode 100644 index 0000000..1601180 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/js/baseNodeModel.js @@ -0,0 +1,29 @@ +import {HtmlNodeModel} from "@logicflow/core"; +import {setCommonStyle} from "@/components/design/common/js/tool"; + +export class BaseNodeModel extends HtmlNodeModel { + + setAttributes() { + this.width = 220; + this.height = 80; + this.radius = 20; + this.inputData = this.text.value + + } + getNodeStyle() { + return setCommonStyle(super.getNodeStyle(), this.properties, "node", "mimic"); + } + + getData () { + const data = super.getData() + data.text.value = this.inputData + return data + } + + getTextStyle() { + const style = super.getTextStyle(); + style.display = 'none'; + return style; + } + +} diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/js/baseNodeView.js b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/js/baseNodeView.js new file mode 100644 index 0000000..cf01f95 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/js/baseNodeView.js @@ -0,0 +1,70 @@ +import { HtmlNode } from "@logicflow/core"; +import baseNode from '../vue/baseNode.vue' +import { createApp, h } from 'vue'; +import {ClickOutside} from "element-plus"; + +export class BaseNodeView extends HtmlNode { + + constructor(props) { + super(props) + this.isMounted = false + this.r = h(baseNode, { + text: props.model.inputData, + permissionFlag: props.model.properties.permissionFlag, + chartStatusColor: props.model.properties.chartStatusColor, + status: props.model.properties.status, + type: props.model.type, + fill: props.model.getNodeStyle().fill, + stroke: props.model.getNodeStyle().stroke, + onUpdateNodeName: (nodeName) => { + props.model.text.value = nodeName + props.graphModel.eventCenter.emit("update:nodeName", {id: props.model.id, nodeName: nodeName}); + }, + onEditNode: () => { + props.graphModel.eventCenter.emit("edit:node", {id: props.model.id, click: true}); + }, + onDeleteNode: () => { + props.graphModel.eventCenter.emit("delete:node", {id: props.model.id}); + } + }) + this.app = createApp({ + render: () => this.r + }) + this.app.directive('click-outside', ClickOutside); + } + + shouldUpdate() { + const currentProperties = JSON.parse(this.currentProperties) + if (this.preProperties) { + const preProperties = JSON.parse(this.preProperties) + let flag = false + if (currentProperties.permissionFlag !== preProperties.permissionFlag) { + this.preProperties = this.currentProperties; + flag = true + } + if (this.props.model.text.value!== this.preText) { + this.preText = this.props.model.text.value + flag = true + } + return flag + } + + this.preProperties = this.currentProperties; + this.preText = this.props.model.text.value + return true; + } + + + setHtml(rootEl) { + if (!this.isMounted) { + this.isMounted = true + const node = document.createElement('div') + rootEl.appendChild(node) + this.app.mount(node) + } else { + this.r.component.props.text = this.props.model.text.value + this.r.component.props.permissionFlag = this.props.model.properties.permissionFlag + } + } +} + diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/js/between.js b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/js/between.js new file mode 100644 index 0000000..283e485 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/js/between.js @@ -0,0 +1,12 @@ +import {BaseNodeModel} from "@/components/design/mimic/js/baseNodeModel"; +import {BaseNodeView} from "@/components/design/mimic/js/baseNodeView"; + +class BetweenModel extends BaseNodeModel {} + +class BetweenView extends BaseNodeView {} + +export default { + type: "between", + model: BetweenModel, + view: BetweenView, +}; diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/js/end.js b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/js/end.js new file mode 100644 index 0000000..7b839e0 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/js/end.js @@ -0,0 +1,24 @@ +import { CircleNode, CircleNodeModel } from "@logicflow/core"; +import {setCommonStyle} from "@/components/design/common/js/tool.js"; + +class endModel extends CircleNodeModel { + + initNodeData(data) { + super.initNodeData(data); + this.r = 20 + } + + getNodeStyle() { + const style = setCommonStyle(super.getNodeStyle(), this.properties, "node", "mimic"); + style.strokeWidth = 4; + return style; + } +} + +class endView extends CircleNode {} + +export default { + type: "end", + model: endModel, + view: endView, +}; diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/js/gatewayModel.ts b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/js/gatewayModel.ts new file mode 100644 index 0000000..2bf3412 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/js/gatewayModel.ts @@ -0,0 +1,21 @@ +import { RectNodeModel } from '@logicflow/core' +import {setCommonStyle} from "../../common/js/tool"; +export class GatewayModel extends RectNodeModel { + + initNodeData(data) { + super.initNodeData(data); + this.width = 70; + this.height = 30; + this.radius = 5; + } + getNodeStyle() { + return setCommonStyle(super.getNodeStyle(), this.properties, "node", "mimic"); + } + + getTextStyle() { + const style = super.getTextStyle(); + style.display = 'none'; + return style; + } +} + diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/js/gatewayView.ts b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/js/gatewayView.ts new file mode 100644 index 0000000..e8d539e --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/js/gatewayView.ts @@ -0,0 +1,64 @@ +import { RectNode, h} from '@logicflow/core' + +export abstract class GatewayView extends RectNode { + + abstract getSvg(x: number, y: number, width: number, height: number, textValue: string, style: any): any + + getLabelShape() { + const {model} = this.props; + const {x, y, width, height, text} = model; + const style = model.getNodeStyle(); + const incomingEdges = model.graphModel.getNodeIncomingEdge(model.id) + + // 创建一个包含图标和文本的容器 + if (incomingEdges.length > 1) { + return h( + 'svg', + { + x: x - width / 2 + 23, + y: y - height / 2 + 3, + width: 25, + height: 25, + viewBox: '0 0 1024 1024', + }, + [ + // 多个输入边时的图标 + h('path', { + fill: style.stroke ? style.stroke : '#FF8B00', + d: 'M171.52 280.064H51.2c-28.672 0-51.2-22.528-51.2-51.2V108.544c0-28.672 22.528-51.2 51.2-51.2h120.32c28.672 0 51.2 22.528 51.2 51.2v120.32c0 28.672-22.528 51.2-51.2 51.2zM51.2 108.544v120.32h120.32V108.544H51.2zM572.416 280.064H451.584c-28.672 0-51.2-22.528-51.2-51.2V108.544c0-28.672 22.528-51.2 51.2-51.2h120.32c28.672 0 51.2 22.528 51.2 51.2v119.808c0.512 13.312-4.608 26.112-13.824 35.84-9.728 10.24-23.04 15.872-36.864 15.872z m-0.512-50.176zM451.584 108.544v120.32h120.32V108.544H451.584zM972.8 280.064h-120.32c-28.672 0-51.2-22.528-51.2-51.2V108.544c0-28.672 22.528-51.2 51.2-51.2H972.8c28.672 0 51.2 22.528 51.2 51.2v120.32c0 28.672-22.528 51.2-51.2 51.2z m-120.32-171.52v120.32H972.8V108.544h-120.32zM572.416 966.656H451.584c-28.672 0-51.2-22.528-51.2-51.2v-120.32c0-28.672 22.528-51.2 51.2-51.2h120.32c28.672 0 51.2 22.528 51.2 51.2v119.808c0.512 13.312-4.608 26.112-13.824 35.84-9.728 10.24-23.04 15.872-36.864 15.872z m-0.512-50.176z m-120.32-121.344v120.32h120.32v-120.32H451.584z', + }), + h('path', { + fill: style.stroke ? style.stroke : '#FF8B00', + d: 'M861.696 566.272H162.816c-42.496 0-76.8-34.304-76.8-76.8V254.464c0-14.336 11.264-25.6 25.6-25.6s25.6 11.264 25.6 25.6v235.008c0 14.336 11.264 25.6 25.6 25.6h698.88c14.336 0 25.6-11.264 25.6-25.6V254.464c0-14.336 11.264-25.6 25.6-25.6s25.6 11.264 25.6 25.6v235.008c0 42.496-34.304 76.8-76.8 76.8z', + }), + h('path', { + fill: style.stroke ? style.stroke : '#FF8B00', + d: 'M512 766.464c-14.336 0-25.6-11.264-25.6-25.6V283.136c0-14.336 11.264-25.6 25.6-25.6s25.6 11.264 25.6 25.6v457.728c0 14.336-11.264 25.6-25.6 25.6z', + }), + ], + ); + } + return this.getSvg(x, y, width, height, text.value, style) + } + + // 自定义节点外观 + getShape() { + const { model } = this.props; + const { x, y, width, height, radius } = model; + const style = model.getNodeStyle(); + return h('g', {style: { + cursor: 'pointer' + }}, [ + h('rect', { + ...style, + x: x - width / 2, // 矩形默认x,y代表左上角顶点坐标,切换为中心点 + y: y - height / 2, + rx: radius, + ry: radius, + width, + height, + }), + this.getLabelShape(), + ]); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/js/inclusive.ts b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/js/inclusive.ts new file mode 100644 index 0000000..b06caca --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/js/inclusive.ts @@ -0,0 +1,54 @@ +import {GatewayModel} from "./gatewayModel"; +import {GatewayView} from "./gatewayView"; +import {h} from "@logicflow/core"; +class InclusiveModel extends GatewayModel {} + +class InclusiveView extends GatewayView { + + getSvg(x: number, y: number, width: number, height: number, textValue: string, style: { stroke: string, fill: string }) { + return h('g', {}, [ + // 图标 SVG + h('svg', { + x: x - width / 2, + y: y - height / 2 + 3, + width: 25, + height: 25, + viewBox: '0 0 1024 1024', + }, [ + // 单个输入边时的图标 + h('path', { + fill: style.fill ? style.fill : '#FFFCEB', + d: 'M85.418667 512L512.021333 85.397333 938.624 512 512.021333 938.602667z', + }), + h('path', { + fill: style.stroke ? style.stroke : '#FF8B00', + d: 'M512 970.24a66.346667 66.346667 0 0 1-47.146667-19.413333L72.96 558.72a66.346667 66.346667 0 0 1 0-94.08L464.853333 72.533333a68.266667 68.266667 0 0 1 94.293334 0l392.106666 392.106667a66.773333 66.773333 0 0 1 0 94.08L559.146667 950.826667a66.346667 66.346667 0 0 1-47.146667 19.413333z m0-853.333333a2.986667 2.986667 0 0 0-1.92 0L118.186667 509.866667a2.56 2.56 0 0 0 0 3.626666l391.893333 392.106667a3.2 3.2 0 0 0 3.84 0l391.893333-392.106667a2.56 2.56 0 0 0 0-3.626666L513.92 117.76a2.986667 2.986667 0 0 0-1.92-0.64z', + }), + h('circle', { + cx: 512, + cy: 512, + r: 200, + fill: 'none', + stroke: style.stroke ? style.stroke : '#FF8B00', + strokeWidth: 60, + }), + ]), + // 文本元素,相对于g元素定位 + textValue ? h('text', { + x: x - width / 2 + 27, + y: y - height / 2 + 20, + fontSize: 13, + fill: "#000", + style: { + userSelect: 'none', + } + }, textValue) : null + ]); + } +} + +export default { + type: 'inclusive', + view: InclusiveView, + model: InclusiveModel +}; diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/js/mimic.js b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/js/mimic.js new file mode 100644 index 0000000..00c4d8c --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/js/mimic.js @@ -0,0 +1,557 @@ +import {isGateWay} from "@/components/design/common/js/tool.js"; + +const OFFSET_X = 150; +const OFFSET_Y = 150; + +function addNode(lf, nodeType, x, y, name) { + let text = null; + if (name) { + text = { + value: name, + x: x, + y: y, + } + } + return lf.addNode({ + type: nodeType, + x: x, + y: y, + text: text, + }); +} + +function addEdge(lf, nodeType, sourceNodeId, targetNodeId, pointsList) { + return lf.addEdge({ + type: nodeType, + sourceNodeId: sourceNodeId, + targetNodeId: targetNodeId, + pointsList: pointsList, + }); +} + +function addEdgeAll(lf, nodeType, sourceNodeId, targetNodeId, properties, text, pointsList) { + return lf.addEdge({ + type: nodeType, + sourceNodeId: sourceNodeId, + targetNodeId: targetNodeId, + properties: properties, + text: text, + pointsList: pointsList, + startPoint: pointsList ? pointsList[0]: null, + endPoint: pointsList ? pointsList[pointsList.length - 1]: null, + }); +} + +function deleteAndAddEdge(sourceNode, nodes, edges, lf, edgeMap = new Map() + , visitedNodes = new Set(), nodeMap = new Map()) { + if (nodeMap.size === 0) { + nodes.forEach((node) => nodeMap.set(node.id, node)); + } + // 如果该节点已经被访问过,则直接返回,防止死循环 + if (visitedNodes.has(sourceNode.id)) { + return; + } + visitedNodes.add(sourceNode.id); + + const nextEdges = findNextEdges(sourceNode.id, edges); + if (!nextEdges || nextEdges.length === 0) { + return; + } + + if (nextEdges.length > 1) { + for (let i = 0; i < nextEdges.length; i++) { + const edge = nextEdges[i]; + lf.deleteEdge(edge.id); + const targetNode = nodeMap.get(edge.targetNodeId); + if (!targetNode) continue; + + // 边重复检测 + if (isGateWay(sourceNode.type) && edgeMap.has(edge.sourceNodeId + ":" + edge.targetNodeId)) { + continue; // 跳过重复边 + } + + // 添加新边 + if (targetNode.x > sourceNode.x) { + addEdgeAll(lf, "skip", sourceNode.id, edge.targetNodeId, edge.properties, edge.text, [ + {x: sourceNode.x + sourceNode.properties.width / 2, y: sourceNode.y}, + {x: targetNode.x, y: sourceNode.y}, + {x: targetNode.x, y: targetNode.y - targetNode.properties.height / 2}, + ]); + } else if (targetNode.x < sourceNode.x) { + addEdgeAll(lf, "skip", sourceNode.id, edge.targetNodeId, edge.properties, edge.text, [ + {x: sourceNode.x - sourceNode.properties.width / 2, y: sourceNode.y}, + {x: targetNode.x, y: sourceNode.y}, + {x: targetNode.x, y: targetNode.y - targetNode.properties.height / 2}, + ]); + } else { + addEdgeAll(lf, "skip", sourceNode.id, edge.targetNodeId, edge.properties, edge.text, [ + {x: sourceNode.x, y: sourceNode.y + sourceNode.properties.height / 2}, + {x: targetNode.x, y: targetNode.y - targetNode.properties.height / 2}, + ]); + } + + edgeMap.set(edge.sourceNodeId + ":" + edge.targetNodeId, edge); + // 继续处理子节点,带上 visitedNodes + deleteAndAddEdge(targetNode, nodes, edges, lf, edgeMap, visitedNodes, nodeMap); + } + } else { + const edge = nextEdges[0]; + lf.deleteEdge(edge.id); + const targetNode = nodeMap.get(edge.targetNodeId); + if (!targetNode) return; + + if (isGateWay(sourceNode.type) && edgeMap.has(edge.sourceNodeId + ":" + edge.targetNodeId)) { + return; + } + + if (targetNode.x > sourceNode.x) { + addEdgeAll(lf, "skip", sourceNode.id, edge.targetNodeId, edge.properties, edge.text, [ + {x: sourceNode.x, y: sourceNode.y + sourceNode.properties.height / 2}, + {x: sourceNode.x, y: targetNode.y}, + {x: targetNode.x - targetNode.properties.width / 2, y: targetNode.y} + ]); + } else if (targetNode.x < sourceNode.x) { + addEdgeAll(lf, "skip", sourceNode.id, edge.targetNodeId, edge.properties, edge.text, [ + {x: sourceNode.x, y: sourceNode.y + sourceNode.properties.height / 2}, + {x: sourceNode.x, y: targetNode.y}, + {x: targetNode.x + targetNode.properties.width / 2, y: targetNode.y} + ]); + } else { + edgeMap.set(edge.sourceNodeId + ":" + edge.targetNodeId, edge); + addEdgeAll(lf, "skip", sourceNode.id, edge.targetNodeId, edge.properties, edge.text, [ + {x: sourceNode.x, y: sourceNode.y + sourceNode.properties.height / 2}, + {x: targetNode.x, y: targetNode.y - targetNode.properties.height / 2} + ]); + } + + // 带着 visitedNodes 递归 + deleteAndAddEdge(targetNode, nodes, edges, lf, edgeMap, visitedNodes, nodeMap); + } +} + +/** + * 水平自动布局,不需要移动的节点 + */ +function LevelNotMoveNode(targetEdges, nodes, edges, lf, visitedIds, type = true + , nodeMap = new Map()) { + if (nodeMap.size === 0) { + nodeMap = new Map(nodes.map(node => [node.id, node])); + } + + if (targetEdges.length > 1) { + for (let i = 0; i < targetEdges.length; i++) { + const edge = targetEdges[i]; + extracted(edge); + } + } else { + const edge = targetEdges[0] + extracted(edge); + } + + function extracted(edge) { + const targetNode = type ? nodeMap.get(edge.targetNodeId) : nodeMap.get(edge.sourceNodeId); + // 如果该节点已经被访问过,则直接返回,防止死循环 + if (visitedIds.has(targetNode.id)) { + return; + } + visitedIds.add(targetNode.id); + const targetEdges = type ? findNextEdges(targetNode.id, edges) : findLastEdges(targetNode.id, edges); + if (!targetEdges || targetEdges.length === 0) { + return; + } + LevelNotMoveNode(targetEdges, nodes, edges, lf, visitedIds, type, nodeMap) + } +} + + +// 更新边 +function updateEdges(lf) { + const nodes = lf.getGraphData().nodes; + const startNode = nodes.find(node => node.type === "start") + // 以上两行重构,改成一行 + + const edges = lf.getGraphData().edges; + // 获取源节点的后置节点 + deleteAndAddEdge(startNode, nodes, edges, lf); +} + +export const addBetweenNode = (lf, edge) => { + const nodes = lf.getGraphData().nodes; + const sourceNode = nodes.find(node => node.id === edge.sourceNodeId); + const targetNode = nodes.find(node => node.id === edge.targetNodeId); + + // 添加中间节点 + const betweenNode = addNode(lf, 'between', sourceNode.x, getMoveY(sourceNode, 'between'), "中间节点") + addEdge(lf, "skip", sourceNode.id, betweenNode.id) + + // 找到并删除开始节点和结束节点之间的直接连接 + lf.deleteEdge(edge.id); + + addEdgeAll(lf, "skip", betweenNode.id, targetNode.id, edge.properties) + + // 当目标节点减新增中间节点差,小于竖向偏移量,才移动节点 + recursivelyMoveNodes(lf, betweenNode); + + updateEdges(lf) +} + +export const addGatewayNode = (lf, edge, nodeType) => { + const nodes = lf.getGraphData().nodes; + const edges = lf.getGraphData().edges; + const sourceNode = nodes.find(node => node.id === edge.sourceNodeId); + const targetNode = nodes.find(node => node.id === edge.targetNodeId); + + // 添加互斥网关开始节点 + const gatewayNode = addNode(lf, nodeType, sourceNode.x, getMoveY(sourceNode, nodeType), "加节点") + // 连接父节点与互斥网关开始节点 + addEdge(lf, "skip", sourceNode.id, gatewayNode.id) + + // 添加两个中间节点 + const between1Node = addNode(lf, "between", sourceNode.x - OFFSET_X, getMoveY(gatewayNode, "between"), "中间节点") + const between2Node = addNode(lf, "between", sourceNode.x + OFFSET_X, getMoveY(gatewayNode, "between"), "中间节点") + addEdge(lf, "skip", gatewayNode.id, between1Node.id) + addEdge(lf, "skip", gatewayNode.id, between2Node.id) + + // 添加互斥网关结束节点 + const gatewayNode2 = addNode(lf, nodeType, sourceNode.x, getMoveY(between1Node, nodeType), "加节点") + // 连接两个中间节点与互斥网关结束节点 + addEdge(lf, "skip", between1Node.id, gatewayNode2.id) + addEdge(lf, "skip", between2Node.id, gatewayNode2.id) + + // 找到并删除开始节点和目标节点之间的直接连接 + lf.deleteEdge(edge.id); + + // 连接互斥网关结束节点到目标节点 + addEdge(lf, "skip", gatewayNode2.id, targetNode.id) + + // 当目标节点减新增互斥网关结束节点差,小于竖向偏移量,才移动节点 + recursivelyMoveNodes(lf, gatewayNode2); + + // 找到属于新增位置区域的节点,水平移动的时候排除它 + const targetEdges = [edges.find(edge1 => edge1.id === edge.id)]; + moveLevelNode(nodes, edges, targetEdges, lf, between1Node, between2Node); + updateEdges(lf) +} + +export const gatewayAddNode = (lf, gatewayNode) => { + const incomingEdges = lf.getNodeIncomingEdge(gatewayNode.id) + // 如果是网关的结束节点,则不新增节点 + if (incomingEdges.length > 1) { + return + } + + const nodes = lf.getGraphData().nodes; + const edges = lf.getGraphData().edges; + + // 找到互斥网关结束节点之间的节点 + const nextNodes = findNextNodes(gatewayNode.id, nodes, edges); + const gatewayEnd = getGateWayEnd(gatewayNode, nodes, edges, nextNodes) + const regionNodes = findAllNextNodes(gatewayNode.id, nodes, edges, gatewayEnd); + let xSet = new Set() + regionNodes.forEach(node => { + if (node.type === 'between' && !xSet.has(node.x)) { + xSet.add(node.x) + } + }) + + regionNodes.forEach(node => { + if (gatewayEnd.id !== node.id) { + lf.graphModel.moveNode(node.id, -OFFSET_X, 0, true); + node.x = node.x - OFFSET_X + } + }) + + const mostChild = getRightmostNode(regionNodes) + + // 连接互斥网关开始节点到新的中间节点 + const betweenNode = addNode(lf, "between", mostChild.x + OFFSET_X * 2, + getMoveY(gatewayNode, "between"), "中间节点") + regionNodes.push(lf.getDataById(betweenNode.id)) + + // 连接网关开始节点到新的中间节点 + addEdge(lf, "skip", gatewayNode.id, betweenNode.id) + + // 找到属于新增位置区域的节点,水平移动的时候排除它 + const targetEdges = [edges.find(edge1 => edge1.targetNodeId === gatewayNode.id)]; + moveLevelNode(nodes, edges, targetEdges, lf, getLeftmostNode(regionNodes) , getRightmostNode(regionNodes)); + + // 连接新的中间节点到互斥网关结束节点 + addEdge(lf, "skip", betweenNode.id, gatewayEnd.id) + updateEdges(lf) +}; + +function getGateWayEnd(startNode, nodes, edges, nextNodes, n = 1) { + if (!nextNodes || nextNodes.length === 0) { + nextNodes = findNextNodes(startNode.id, nodes, edges); + } + if (nextNodes && nextNodes.length > 0) { + if (nextNodes[0].type === startNode.type) { + if (n === 1 && nextNodes[0].x === startNode.x) { + return nextNodes[0] + } + if (findNextEdges(nextNodes[0].id, edges).length > 1) { + n++ + } else { + n-- + } + } + startNode = {id : nextNodes[0].id, type : startNode.type, x : startNode.x} + nextNodes = findNextNodes(startNode.id, nodes, edges); + return getGateWayEnd(startNode, nodes, edges, nextNodes, n) + } +} + +export const removeNode = (lf, nodeModel) => { + const sourceNodes = lf.getNodeIncomingNode(nodeModel.id) + const targetNodes = lf.getNodeOutgoingNode(nodeModel.id) + const [sourceNode, targetNode] = [sourceNodes[0], targetNodes[0]] + let moveSourceNode = sourceNode + let moveTargetNode = targetNode + const targetNodeEdges = lf.getNodeIncomingEdge(targetNode.id) + lf.deleteNode(nodeModel.id) + let nodes = lf.getGraphData().nodes; + let edges = lf.getGraphData().edges; + + if (isGateWay(sourceNode.type) && isGateWay(targetNode.type) + && sourceNode.type === targetNode.type && targetNodeEdges.length > 1) { + let sourceEdges = lf.getNodeIncomingEdge(sourceNode.id) + if (lf.getNodeOutgoingNode(sourceNode.id).length >= 2) { + nodes = lf.getGraphData().nodes; + edges = lf.getGraphData().edges; + // 找到互斥网关结束节点之间的节点 + const nextNodes = findNextNodes(sourceNode.id, nodes, edges); + const gatewayEnd = getGateWayEnd(sourceNode, nodes, edges, nextNodes) + const regionNodes = findAllNextNodes(sourceNode.id, nodes, edges, gatewayEnd); + let xSet = new Set() + regionNodes.forEach(node => { + if (node.type === 'between' && !xSet.has(node.x)) { + xSet.add(node.x) + } + }) + // regionNodes按照x轴排序 + regionNodes.sort((a, b) => { + return a.x - b.x + }) + let mostLeftX = sourceNode.x - (xSet.size - 1) * OFFSET_X + let moveX + regionNodes.forEach(node => { + if (gatewayEnd.id !== node.id) { + if (moveX) { + moveX = moveX + OFFSET_X * 2 + } else { + moveX = mostLeftX + } + lf.graphModel.moveNode2Coordinate(node.id, moveX, node.y, true); + } + }) + } + if (lf.getNodeOutgoingNode(sourceNode.id).length === 1) { + moveSourceNode = lf.getNodeIncomingNode(sourceNode.id)[0] + moveTargetNode = lf.getNodeOutgoingNode(targetNode.id)[0] + const delNodes = findAllNextNodes(sourceNode.id, nodes, edges, targetNode); + lf.deleteNode(sourceNode.id) + delNodes.forEach(node => { + lf.deleteNode(node.id) + }) + sourceEdges = [addEdge(lf, "skip", moveSourceNode.id, moveTargetNode.id)] + } + + nodes = lf.getGraphData().nodes; + edges = lf.getGraphData().edges; + let visitedIds = new Set() + LevelNotMoveNode(sourceEdges, nodes, edges, lf, visitedIds) + LevelNotMoveNode(sourceEdges, nodes, edges, lf, visitedIds, false) + // 查询nodes中visitedIds包含node.id的数组 + const visitedNodes = nodes.filter(node => visitedIds.has(node.id)) + + moveLevelNode(nodes, edges, sourceEdges, lf, nodeModel.x < sourceNode.x ? getLeftmostNode(visitedNodes) : null + , nodeModel.x < sourceNode.x ? null : getRightmostNode(visitedNodes), false, visitedIds); + } else { + addEdge(lf, "skip", moveSourceNode.id, moveTargetNode.id) + } + + // 当目标节点减新增互斥网关结束节点差,小于竖向偏移量,才移动节点 + recursivelyMoveNodes(lf, moveSourceNode, false); + updateEdges(lf) +} + +/** + * 水平移动节点 + */ +function moveLevelNode(nodes, edges, targetEdges, lf, betweenLeft, betweenRight, addFlag = true, visitedIds = new Set()) { + if (!visitedIds || visitedIds.size === 0) { + LevelNotMoveNode(targetEdges, nodes, edges, lf, visitedIds) + LevelNotMoveNode(targetEdges, nodes, edges, lf, visitedIds, false) + } + + // 找出所有节点nodes中的x坐标,减去sourceNode.x的最小的差值,小于横向偏移量,才移动节点 + let needMoveLeftNode = [] + let needMoveRightNode = [] + let LeftMove = !addFlag; + let rightMove = !addFlag; + // 过滤nodes在between1Node和between2Node之间的节点 + nodes.forEach(node => { + if (visitedIds.has(node.id)) { + return + } + if (betweenLeft && node.x <= betweenLeft.x) { + needMoveLeftNode.push(node) + if (addFlag && betweenLeft.x - node.x <= OFFSET_X) { + LeftMove = true + } + if (!addFlag && betweenLeft.x - node.x <= OFFSET_X * 2) { + LeftMove = false + } + } else if (betweenRight && node.x >= betweenRight.x) { + needMoveRightNode.push(node) + if (addFlag && node.x - betweenRight.x <= OFFSET_X) { + rightMove = true + } + if (!addFlag && node.x - betweenRight.x <= OFFSET_X * 2) { + rightMove = false + } + } + }) + + let n = betweenLeft && betweenRight ? 1 : 2; + // 水平自动布局 + if (LeftMove) { + needMoveLeftNode.forEach(node => { + lf.graphModel.moveNode(node.id, addFlag ? -OFFSET_X * n : OFFSET_X * n, 0, true); + }); + } + if (rightMove) { + needMoveRightNode.forEach(node => { + lf.graphModel.moveNode(node.id, addFlag ? OFFSET_X * n : -OFFSET_X * n, 0, true); + }); + } +} + +function getMoveY(sourceNode, targetType) { + let moveY; + if (['start', 'between'].includes(sourceNode.type) && targetType === 'between') { + moveY = sourceNode.y + OFFSET_Y; + } else if (['start', 'between'].includes(sourceNode.type) && targetType === 'end') { + moveY = sourceNode.y + OFFSET_Y - 20; + } else if (['start', 'between'].includes(sourceNode.type) && isGateWay(targetType)) { + moveY = sourceNode.y + OFFSET_Y - 25; + } else if (isGateWay(sourceNode.type) && isGateWay(targetType)) { + moveY = sourceNode.y + OFFSET_Y - 50; + } else if (isGateWay(sourceNode.type) && targetType === 'between') { + moveY = sourceNode.y + OFFSET_Y - 25; + } else if (isGateWay(sourceNode.type) && targetType === 'end') { + moveY = sourceNode.y + OFFSET_Y - 45; + } + return moveY; +} + +/** + * 递归处理节点的 Y 轴移动逻辑 + */ +function recursivelyMoveNodes(lf, lastNode, addFlag = true) { + const nodes = lf.getGraphData().nodes; + const edges = lf.getGraphData().edges + // 查找当前节点的后续节点并递归处理 + const nodesToMove = findNextNodes(lastNode.id, nodes, edges); + + if (!nodesToMove || nodesToMove.length === 0) return; + + nodesToMove.forEach(node => { + let moveY = getMoveY(lastNode, node.type); + let move = false; + if (addFlag && node.y < moveY) { + move = true; + } else if (!addFlag) { + if (isGateWay(node.type)) { + const lastNodes = findLastNodes(node.id, nodes, edges) + // 找出lastNodes中的最大的y坐标的节点 + const maxNode = lastNodes.reduce((prev, current) => { + return (current.y > prev.y ? current : prev); + }, lastNodes[0]); + moveY = getMoveY(maxNode, node.type); + if (node.y >= moveY) { + move = true; + } + } else if (node.y >= moveY) { + move = true; + } + } + + if (move) { + // 移动当前节点 + lf.graphModel.moveNode2Coordinate(node.id, node.x, moveY, true); + // 更新 lastNode + const updatedNode = lf.graphModel.getNodeModelById(node.id); + recursivelyMoveNodes(lf, updatedNode, addFlag); + } + }); +} + +function findAllNextNodes(nodeId, nodes, edges, gateWayEnd, visited = new Set(), result = []) { + if (visited.has(nodeId)) { + return result; + } + visited.add(nodeId); + + const targetNode = nodes.find(node => node.id === nodeId); + if (!targetNode) { + return result; + } + + const directChildren = edges + .filter(edge => edge.sourceNodeId === nodeId) + .map(edge => nodes.find(node => node.id === edge.targetNodeId)) + .filter(Boolean); + + for (const child of directChildren) { + // 如果子节点还未添加到结果数组中,则添加 + if (!result.some(node => node.id === child.id)) { + result.push(child); + } + if (gateWayEnd && gateWayEnd.id === child.id) { + gateWayEnd = child; + } else { + // 递归查找子节点的后继节点 + findAllNextNodes(child.id, nodes, edges, gateWayEnd, visited, result); + } + } + + return result; +} + +function findNextEdges(nodeId, edges) { + return edges.filter(edge => edge.sourceNodeId === nodeId); +} + +function findLastEdges(nodeId, edges) { + return edges.filter(edge => edge.targetNodeId === nodeId); +} + +function findNextNodes(nodeId, nodes, edges) { + return edges.filter(edge => edge.sourceNodeId === nodeId) + .map(edge => nodes.find(node => node.id === edge.targetNodeId)) + .filter(Boolean); +} + +function findLastNodes(nodeId, nodes, edges) { + return edges.filter(edge => edge.targetNodeId === nodeId) + .map(edge => nodes.find(node => node.id === edge.sourceNodeId)) + .filter(Boolean); +} + +function getRightmostNode(nodes) { + if (nodes.length === 0) return null; + + return nodes.reduce((rightmost, current) => { + return (current.x > rightmost.x ? current : rightmost); + }, nodes[0]); +} + + +function getLeftmostNode(nodes) { + if (nodes.length === 0) return null; + + return nodes.reduce((leftmost, current) => { + return (current.x < leftmost.x ? current : leftmost); + }, nodes[0]); +} diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/js/parallel.ts b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/js/parallel.ts new file mode 100644 index 0000000..c45e99c --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/js/parallel.ts @@ -0,0 +1,51 @@ +import {GatewayModel} from "./gatewayModel"; +import {GatewayView} from "./gatewayView"; +import {h} from "@logicflow/core"; + +class ParallelModel extends GatewayModel {} + +class ParallelView extends GatewayView { + + getSvg(x: number, y: number, width: number, height: number, textValue: string, style: { stroke: string, fill: string }) { + // 创建一个包含图标和文本的容器 + return h('g', {}, [ + // 图标 SVG + h('svg', { + x: x - width / 2, + y: y - height / 2 + 3, + width: 25, + height: 25, + viewBox: '0 0 1024 1024', + }, [ + h('path', { + fill: style.fill ? style.fill : '#FFFCEB', + d: 'M85.418667 512L512.021333 85.397333 938.624 512 512.021333 938.602667z', + }), + h('path', { + fill: style.stroke ? style.stroke : '#FF8B00', + d: 'M512 714.453333A32 32 0 0 1 480 682.666667v-138.666667H341.333333a32 32 0 1 1 0-64h138.666667V341.333333a32 32 0 0 1 64 0v138.666667H682.666667a32 32 0 0 1 0 64h-138.666667V682.666667a32 32 0 0 1-32 31.786666z', + }), + h('path', { + fill: style.stroke ? style.stroke : '#FF8B00', + d: 'M512 970.453333a66.56 66.56 0 0 1-47.146667-19.626666L72.96 558.933333a66.346667 66.346667 0 0 1 0-94.293333L464.853333 72.533333a68.266667 68.266667 0 0 1 94.293334 0l391.893333 392.106667a66.346667 66.346667 0 0 1 0 94.293333L559.146667 950.826667a66.56 66.56 0 0 1-47.146667 19.626666z m0-853.333333a2.986667 2.986667 0 0 0-1.92 0L118.186667 509.866667a2.56 2.56 0 0 0 0 3.626666l391.893333 392.106667a3.2 3.2 0 0 0 3.84 0l391.893333-392.106667a2.986667 2.986667 0 0 0 0-3.626666L513.92 117.76a2.986667 2.986667 0 0 0-1.92-0.64z', + }), + ]), + // 文本元素,相对于g元素定位 + textValue ? h('text', { + x: x - width / 2 + 27, + y: y - height / 2 + 20, + fontSize: 13, + fill: "#000", + style: { + userSelect: 'none', + } + }, textValue) : null + ]); + } +} + +export default { + type: 'parallel', + view: ParallelView, + model: ParallelModel +}; diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/js/serial.ts b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/js/serial.ts new file mode 100644 index 0000000..028fc29 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/js/serial.ts @@ -0,0 +1,50 @@ +import {GatewayModel} from "./gatewayModel"; +import {GatewayView} from "./gatewayView"; +import {h} from "@logicflow/core"; +class SerialModel extends GatewayModel {} + +class SerialView extends GatewayView { + + getSvg(x: number, y: number, width: number, height: number, textValue: string, style: { stroke: string, fill: string }) { + return h('g', {}, [ + // 图标 SVG + h('svg', { + x: x - width / 2, + y: y - height / 2 + 3, + width: 25, + height: 25, + viewBox: '0 0 1024 1024', + }, [ + // 单个输入边时的图标 + h('path', { + fill: style.fill ? style.fill : '#FFFCEB', + d: 'M85.418667 512L512.021333 85.397333 938.624 512 512.021333 938.602667z', + }), + h('path', { + fill: style.stroke ? style.stroke : '#FF8B00', + d: 'M512 970.24a66.346667 66.346667 0 0 1-47.146667-19.413333L72.96 558.72a66.346667 66.346667 0 0 1 0-94.08L464.853333 72.533333a68.266667 68.266667 0 0 1 94.293334 0l392.106666 392.106667a66.773333 66.773333 0 0 1 0 94.08L559.146667 950.826667a66.346667 66.346667 0 0 1-47.146667 19.413333z m0-853.333333a2.986667 2.986667 0 0 0-1.92 0L118.186667 509.866667a2.56 2.56 0 0 0 0 3.626666l391.893333 392.106667a3.2 3.2 0 0 0 3.84 0l391.893333-392.106667a2.56 2.56 0 0 0 0-3.626666L513.92 117.76a2.986667 2.986667 0 0 0-1.92-0.64z', + }), + h('path', { + fill: style.stroke ? style.stroke : '#FF8B00', + d: 'M554.666667 516.266667l107.946666-107.946667a30.506667 30.506667 0 0 0-42.666666-42.666667L512 473.173333l-107.946667-107.946666a30.506667 30.506667 0 0 0-42.666666 42.666666L469.333333 516.266667l-108.8 107.946666a30.506667 30.506667 0 1 0 42.666667 42.666667l108.8-107.306667 106.666667 107.733334a30.506667 30.506667 0 0 0 42.666666-42.666667z', + }), + ]), + // 文本元素,相对于g元素定位 + textValue ? h('text', { + x: x - width / 2 + 27, + y: y - height / 2 + 20, + fontSize: 13, + fill: "#000", + style: { + userSelect: 'none', + } + }, textValue) : null + ]); + } +} + +export default { + type: 'serial', + view: SerialView, + model: SerialModel +}; diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/js/skip.ts b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/js/skip.ts new file mode 100644 index 0000000..13fedc0 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/js/skip.ts @@ -0,0 +1,272 @@ +import {setCommonStyle} from "../../common/js/tool"; +import {CurvedEdge, CurvedEdgeModel} from "@logicflow/extension"; +import {h} from "@logicflow/core"; +import {getCurvedEdgePath} from "@logicflow/extension/src/materials/curved-edge"; + +class SkipModel extends CurvedEdgeModel { + + setAttributes() { + this.isHitable = false // 细粒度控制边是否对用户操作进行反应 + } + + getEdgeStyle() { + return setCommonStyle(super.getEdgeStyle(), this.properties, "skip", "mimic"); + + } + + // getTextPosition() { + // const position = super.getTextPosition(); + // + // const currentPositionList = this.points.split(' '); + // + // // 取最后两个点,用于判断终点方向 + // const lastTwoPoints = currentPositionList.slice(-2); + // const [x2, y2] = lastTwoPoints[1].split(',').map(Number); + // + // // 设置文本位置 + // position.x = x2; + // position.y = y2 - 30; + // + // return position; + // } + + getTextStyle() { + const style = super.getTextStyle(); + style.display = 'none'; + style.background = {fill: "transparent"}; + return style; + } + + /** + * 重写此方法,使保存数据是能带上锚点数据。 + */ + getData() { + const data = super.getData(); + data.sourceAnchorId = this.sourceAnchorId; + data.targetAnchorId = this.targetAnchorId; + return data; + } + +} + +class SkipView extends CurvedEdge { + + getEdge(): h.JSX.Element { + const { model } = this.props; + const { points: pointsStr, isAnimation, arrowConfig, radius = 0 } = model; + const style = model.getEdgeStyle(); + const animationStyle = model.getEdgeAnimationStyle(); + let points = this.pointFilter(pointsStr.split(' ').map((p) => p.split(',').map((a) => +a))); + + // 主路径 + let mainPath = getCurvedEdgePath(points, radius as number); + let plusElements: h.JSX.Element[] = []; + + const offsetY = 30 + + // 跳转线是直线 + if (points.length === 2) { + let nextEdge = model.graphModel.edges.filter(edge => edge.sourceNodeId === model.sourceNode.id); + // 如果上一个节点是互斥网关,并且网关后节点大于1个,也就是说是互斥网关结束节点时 + if (['serial', 'inclusive'].includes(model.sourceNode.type as string) && nextEdge.length > 1) { + const midPoint = [points[0][0], points[0][1] + offsetY - 10]; + plusElements = this.getForeignObject(midPoint, style.stroke, model.text.value); + } else if (!model.properties.chartStatusColor) { + const midPoint = [points[0][0], points[0][1] + offsetY]; + plusElements = this.getPlusElements(midPoint); + } + } else { + const p0 = points[0]; + const p1 = points[1]; + const p2 = points[2]; + + // 判断是否由竖线变为横线 + if (p0[0] === p1[0] && p0[1] !== p1[1] && !model.properties.chartStatusColor) { + const midPoint = [p0[0] , p0[1] + offsetY]; + plusElements = this.getPlusElements(midPoint); + } + + // 判断是否由横线变为竖线,并且是互斥网关 + if (model.sourceNode && ['serial', 'inclusive'].includes(model.sourceNode.type as string) && p0[1] === p1[1] && p0[0] !== p1[0]) { + const midPoint = [p2[0], p1[1] + offsetY]; + plusElements = this.getForeignObject(midPoint, style.stroke, model.text.value); + } + } + + // 绘制主路径 + let mainPathElement: h.JSX.Element[] = []; + mainPathElement.push(h('path', { + d: mainPath, + style: isAnimation ? animationStyle : {}, + ...style, + ...arrowConfig, + fill: 'none', + })); + + // 返回所有路径元素 + return h('g', {}, [...mainPathElement, ...plusElements]); + } + + private getForeignObject(midPoint: number[], stroke: string, text: string) { + let elements: h.JSX.Element[] = []; + elements = [ + // 使用 SVG 图标代替原来的图形 + h('foreignObject', { + x: midPoint[0] - 16, + y: midPoint[1] - 20, + width: 32, + height: 32, + }, [ + h('div', { + style: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + width: '100%', + height: '100%' + }, + innerHTML: ` +
+ + + + +
+`, + }) + ]) + ]; + + // 只有当文本有值时才添加背景矩形和文本 + if (text && text.trim().length > 0) { + // 由于我们无法直接在当前环境中测量文本,我们使用一个估算方法 + // 通常每个字符大约占用 8-10 像素宽度(取决于字体) + const charWidth = 8; // 每个字符的估计宽度 + const padding = 10; // 左右内边距 + const minWidth = 40; // 最小宽度 + const textWidth = Math.max(minWidth, text.length * charWidth + padding); + + // 添加背景矩形 + elements.push( + h('rect', { + x: midPoint[0] - textWidth / 2, + y: midPoint[1] + 10, + width: textWidth, + height: 20, + fill: "#fff", // 背景颜色 + rx: 3, // 圆角 + ry: 3 + }) + ); + + // 添加文本 + elements.push( + h('text', { + x: midPoint[0], + y: midPoint[1] + 25, + fontSize: 13, + fill: "#000", + style: { + userSelect: 'none', + textAnchor: 'middle' // 文本居中对齐 + } + }, `${text}`) + ); + } + + return this.getAddElements(midPoint, elements, true); + } + + private getPlusElements(midPoint: number[]) { + return this.getAddElements(midPoint, null); + } + + private getAddElements(midPoint: number[], obj, isCondition: boolean = false) { + if (!obj) { + const x = midPoint[0] + const y = midPoint[1] + obj = [ + h('circle', { + cx: x, + cy: y, + r: 13, + fill: 'blue', + }), + h('line', { + x1: x - 8, + y1: y, + x2: x + 8, + y2: y, + stroke: 'white', + 'stroke-width': '2', + }), + h('line', { + x1: x, + y1: y - 8, + x2: x, + y2: y + 8, + stroke: 'white', + 'stroke-width': '2', + }) + ] + } + let plusElements: h.JSX.Element[] = []; + plusElements.push( + h('g', { + ...this.getEventHandlers(isCondition), // 根据 isCondition 动态选择事件处理器 + style: { + pointerEvents: 'auto', + cursor: 'pointer' + } + }, obj) + ); + return plusElements; + } + + // 新增方法:根据 isCondition 返回不同的事件处理器 + private getEventHandlers(isCondition: boolean): any { + if (isCondition) { + return { + onClick: () => { + this.props.graphModel.eventCenter.emit("show:EdgeSetting", { id: this.props.model.id }); + } + }; + } else { + return { + onMouseEnter: (e) => { + this.props.graphModel.eventCenter.emit("show:EdgeTooltip", { e: e, id: this.props.model.id }); + }, + onMouseLeave: (e) => { + setTimeout(() => { + if (!(window as any).isTooltipHovered) { + this.props.graphModel.eventCenter.emit("hide:EdgeTooltip", e); + } + }, 100); + } + }; + } + } + + private pointFilter(points: number[][]) { + const all = points + let i = 1 + while (i < all.length - 1) { + const [x, y] = all[i - 1] + const [x1, y1] = all[i] + const [x2, y2] = all[i + 1] + if ((x === x1 && x1 === x2) || (y === y1 && y1 === y2)) { + all.splice(i, 1) + } else { + i++ + } + } + return all + } +} + +export default { + type: "skip", + view: SkipView, + model: SkipModel, +}; diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/js/start.js b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/js/start.js new file mode 100644 index 0000000..726e91b --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/js/start.js @@ -0,0 +1,13 @@ +import {BaseNodeModel} from "@/components/design/mimic/js/baseNodeModel"; +import {BaseNodeView} from "@/components/design/mimic/js/baseNodeView"; + +class StartModel extends BaseNodeModel {} + +class StartView extends BaseNodeView {} + +export default { + type: "start", + model: StartModel, + view: StartView, +}; + diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/vue/EdgeTooltip.vue b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/vue/EdgeTooltip.vue new file mode 100644 index 0000000..d985ef8 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/vue/EdgeTooltip.vue @@ -0,0 +1,96 @@ + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/vue/baseNode.vue b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/vue/baseNode.vue new file mode 100644 index 0000000..97a4cc2 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/design/mimic/vue/baseNode.vue @@ -0,0 +1,194 @@ + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/form/design.vue b/smart-flow-api/warm-flow/warm-flow-ui/src/components/form/design.vue new file mode 100644 index 0000000..13732b2 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/form/design.vue @@ -0,0 +1,81 @@ + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/components/form/formCreate.vue b/smart-flow-api/warm-flow/warm-flow-ui/src/components/form/formCreate.vue new file mode 100644 index 0000000..15b87f2 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/components/form/formCreate.vue @@ -0,0 +1,105 @@ + + + diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/main.js b/smart-flow-api/warm-flow/warm-flow-ui/src/main.js new file mode 100644 index 0000000..da3fd47 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/main.js @@ -0,0 +1,41 @@ +import { createApp } from 'vue' + +import FcDesigner from '@form-create/designer'; +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' +import locale from 'element-plus/es/locale/lang/zh-cn' + +import '@/assets/styles/index.scss' // global css + +import App from './App' +import store from './store' + +// 注册指令 +import plugins from './plugins' // plugins + +// svg图标 +import 'virtual:svg-icons-register' +import SvgIcon from '@/components/SvgIcon' +import elementIcons from '@/components/SvgIcon/svgicon' + +import { parseTime } from '@/utils/ruoyi' + +const app = createApp(App) + + +// 全局方法挂载 +app.config.globalProperties.parseTime = parseTime +app.use(store) +app.use(plugins) +app.use(elementIcons) +app.component('svg-icon', SvgIcon) + +// 使用element-plus 并且设置全局的大小 +app.use(ElementPlus, { + locale: locale, +}) + +app.use(FcDesigner); +app.use(FcDesigner.formCreate); + +app.mount('#app') diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/plugins/cache.js b/smart-flow-api/warm-flow/warm-flow-ui/src/plugins/cache.js new file mode 100644 index 0000000..9190c69 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/plugins/cache.js @@ -0,0 +1,37 @@ +const sessionCache = { + set (key, value) { + if (!sessionStorage) { + return + } + if (key != null && value != null) { + sessionStorage.setItem(key, value) + } + }, + get (key) { + if (!sessionStorage) { + return null + } + if (key == null) { + return null + } + return sessionStorage.getItem(key) + }, + setJSON (key, jsonValue) { + if (jsonValue != null) { + this.set(key, JSON.stringify(jsonValue)) + } + }, + getJSON (key) { + const value = this.get(key) + if (value != null) { + return JSON.parse(value) + } + }, +} + +export default { + /** + * 会话级缓存 + */ + session: sessionCache, +} diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/plugins/index.js b/smart-flow-api/warm-flow/warm-flow-ui/src/plugins/index.js new file mode 100644 index 0000000..2f3b3b7 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/plugins/index.js @@ -0,0 +1,9 @@ +import cache from './cache' +import modal from './modal' + +export default function installPlugins(app){ + // 缓存对象 + app.config.globalProperties.$cache = cache + // 模态框对象 + app.config.globalProperties.$modal = modal +} diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/plugins/modal.js b/smart-flow-api/warm-flow/warm-flow-ui/src/plugins/modal.js new file mode 100644 index 0000000..b59e14d --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/plugins/modal.js @@ -0,0 +1,82 @@ +import { ElMessage, ElMessageBox, ElNotification, ElLoading } from 'element-plus' + +let loadingInstance; + +export default { + // 消息提示 + msg(content) { + ElMessage.info(content) + }, + // 错误消息 + msgError(content) { + ElMessage.error(content) + }, + // 成功消息 + msgSuccess(content) { + ElMessage.success(content) + }, + // 警告消息 + msgWarning(content) { + ElMessage.warning(content) + }, + // 弹出提示 + alert(content) { + ElMessageBox.alert(content, "系统提示") + }, + // 错误提示 + alertError(content) { + ElMessageBox.alert(content, "系统提示", { type: 'error' }) + }, + // 成功提示 + alertSuccess(content) { + ElMessageBox.alert(content, "系统提示", { type: 'success' }) + }, + // 警告提示 + alertWarning(content) { + ElMessageBox.alert(content, "系统提示", { type: 'warning' }) + }, + // 通知提示 + notify(content) { + ElNotification.info(content) + }, + // 错误通知 + notifyError(content) { + ElNotification.error(content); + }, + // 成功通知 + notifySuccess(content) { + ElNotification.success(content) + }, + // 警告通知 + notifyWarning(content) { + ElNotification.warning(content) + }, + // 确认窗体 + confirm(content) { + return ElMessageBox.confirm(content, "系统提示", { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: "warning", + }) + }, + // 提交内容 + prompt(content) { + return ElMessageBox.prompt(content, "系统提示", { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: "warning", + }) + }, + // 打开遮罩层 + loading(content) { + loadingInstance = ElLoading.service({ + lock: true, + text: content, + background: "rgba(0, 0, 0, 0.7)", + }) + }, + // 关闭遮罩层 + closeLoading() { + loadingInstance.close(); + } +} diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/store/app.js b/smart-flow-api/warm-flow/warm-flow-ui/src/store/app.js new file mode 100644 index 0000000..a9e00d4 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/store/app.js @@ -0,0 +1,35 @@ +import {setTokenName, setToken} from "@/utils/auth.js"; +import {config} from "@/api/anony.js"; + +const useAppStore = defineStore( + 'app', + { + state: () => ({ + appParams: null + }), + actions: { + async fetchTokenName() { + const urlParams = new URLSearchParams(window.location.search); + const params = {}; + let tokenNames = [] + await config().then(res => { + if (res.code === 200 && res.data && res.data.tokenNameList && res.data.tokenNameList.length > 0) { + tokenNames = res.data.tokenNameList + setTokenName(res.data.tokenNameList) + } + }).catch(() => {}); + this.appParams= null + for (const [key, value] of urlParams.entries()) { + if (value && 'undefined' !== value) { + if (tokenNames && tokenNames.length > 0 && tokenNames.includes(key)) { + setToken(key, value); + } + params[key] = value; + } + } + this.appParams = params; + } + } + }) + +export default useAppStore; diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/store/index.js b/smart-flow-api/warm-flow/warm-flow-ui/src/store/index.js new file mode 100644 index 0000000..f10f389 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/store/index.js @@ -0,0 +1,3 @@ +const store = createPinia() + +export default store \ No newline at end of file diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/utils/auth.js b/smart-flow-api/warm-flow/warm-flow-ui/src/utils/auth.js new file mode 100644 index 0000000..1074f75 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/utils/auth.js @@ -0,0 +1,26 @@ +// 接收外部业务系统token,并存入localStorage中 +export const TokenPrefix = 'Warm-' +// 设置外部token保存到header中的名称 +export const TokenName = TokenPrefix + 'TokenName' + +export function getToken(key) { + return localStorage.getItem(TokenPrefix + key) +} + +export function getTokenName() { + return localStorage.getItem(TokenName) +} + +export function removeToken(key) { + return localStorage.removeItem(TokenPrefix + key) +} + +// 添加 setToken 方法来存储 token +export function setToken(key, value) { + return localStorage.setItem(TokenPrefix + key, value) +} + +// 添加 setTokenName 方法来存储 token name +export function setTokenName(value) { + return localStorage.setItem(TokenName, value) +} diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/utils/request.js b/smart-flow-api/warm-flow/warm-flow-ui/src/utils/request.js new file mode 100644 index 0000000..9b8537b --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/utils/request.js @@ -0,0 +1,104 @@ +import axios from 'axios' +import { ElNotification , ElMessage } from 'element-plus' +import { tansParams } from '@/utils/ruoyi' +import cache from '@/plugins/cache' +import {getToken, getTokenName} from "./auth.js"; + +axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8' +// 创建axios实例 +const service = axios.create({ + // axios中请求配置有baseURL选项,表示请求URL公共部分 + baseURL: import.meta.env.VITE_APP_BASE_API, + // 超时 + timeout: 10000 +}) + +// request拦截器 +service.interceptors.request.use(config => { + // 是否需要防止数据重复提交 + const isRepeatSubmit = (config.headers || {}).repeatSubmit === false + if (getTokenName()) { + let tokenName = getTokenName(); + if (tokenName) { + let tokenNames = tokenName.split(","); + for (let i = 0; i < tokenNames.length; i++) { + if (getToken(tokenNames[i])) { + // 让每个请求携带自定义token 请根据实际情况自行修改 + config.headers[tokenNames[i]] = getToken(tokenNames[i]) + } + } + } + } + // get请求映射params参数 + if (config.method === 'get' && config.params) { + let url = config.url + '?' + tansParams(config.params); + url = url.slice(0, -1); + config.params = {}; + config.url = url; + } + if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) { + const requestObj = { + url: config.url, + data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data, + time: new Date().getTime() + } + const requestSize = Object.keys(JSON.stringify(requestObj)).length; // 请求数据大小 + const limitSize = 5 * 1024 * 1024; // 限制存放数据5M + if (requestSize >= limitSize) { + console.warn(`[${config.url}]: ` + '请求数据大小超出允许的5M限制,无法进行防重复提交验证。') + return config; + } + const sessionObj = cache.session.getJSON('sessionObj') + if (sessionObj === undefined || sessionObj === null || sessionObj === '') { + cache.session.setJSON('sessionObj', requestObj) + } else { + const s_url = sessionObj.url; // 请求地址 + const s_data = sessionObj.data; // 请求数据 + const s_time = sessionObj.time; // 请求时间 + const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交 + if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) { + const message = '数据正在处理,请勿重复提交'; + console.warn(`[${s_url}]: ` + message) + return Promise.reject(new Error(message)) + } else { + cache.session.setJSON('sessionObj', requestObj) + } + } + } + return config +}, error => { + Promise.reject(error) +}) + +// 响应拦截器 +service.interceptors.response.use(res => { + // 未设置状态码则默认成功状态 + const code = res.data.code || 200; + // 获取错误信息 + const msg = res.data.msg + // 二进制数据则直接返回 + if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') { + return res.data + } + if (code !== 200) { + ElNotification.error({ title: msg }) + return Promise.reject(new Error(msg)) + } else { + return Promise.resolve(res.data) + } + }, + error => { + let { message } = error; + if (message == "Network Error") { + message = "后端接口连接异常"; + } else if (message.includes("timeout")) { + message = "系统接口请求超时"; + } else if (message.includes("Request failed with status code")) { + message = "系统接口" + message.substr(message.length - 3) + "异常"; + } + ElMessage({ message: message, type: 'error', duration: 5 * 1000 }) + return Promise.reject(error) + } +) + +export default service diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/utils/ruoyi.js b/smart-flow-api/warm-flow/warm-flow-ui/src/utils/ruoyi.js new file mode 100644 index 0000000..2f85b67 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/utils/ruoyi.js @@ -0,0 +1,73 @@ + + +/** + * 通用js方法封装处理 + * Copyright (c) 2019 ruoyi + */ + +// 日期格式化 +export function parseTime(time, pattern) { + if (arguments.length === 0 || !time) { + return null + } + const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}' + let date + if (typeof time === 'object') { + date = time + } else { + if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) { + time = parseInt(time) + } else if (typeof time === 'string') { + time = time.replace(new RegExp(/-/gm), '/').replace('T', ' ').replace(new RegExp(/\.[\d]{3}/gm), ''); + } + if ((typeof time === 'number') && (time.toString().length === 10)) { + time = time * 1000 + } + date = new Date(time) + } + const formatObj = { + y: date.getFullYear(), + m: date.getMonth() + 1, + d: date.getDate(), + h: date.getHours(), + i: date.getMinutes(), + s: date.getSeconds(), + a: date.getDay() + } + const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { + let value = formatObj[key] + // Note: getDay() returns 0 on Sunday + if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] } + if (result.length > 0 && value < 10) { + value = '0' + value + } + return value || 0 + }) + return time_str +} + +/** +* 参数处理 +* @param {*} params 参数 +*/ +export function tansParams(params) { + let result = '' + for (const propName of Object.keys(params)) { + const value = params[propName]; + var part = encodeURIComponent(propName) + "="; + if (value !== null && value !== "" && typeof (value) !== "undefined") { + if (typeof value === 'object') { + for (const key of Object.keys(value)) { + if (value[key] !== null && value[key] !== "" && typeof (value[key]) !== 'undefined') { + let params = propName + '[' + key + ']'; + var subPart = encodeURIComponent(params) + "="; + result += subPart + encodeURIComponent(value[key]) + "&"; + } + } + } else { + result += part + encodeURIComponent(value) + "&"; + } + } + } + return result +} diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/views/flow-design/flowChart.vue b/smart-flow-api/warm-flow/warm-flow-ui/src/views/flow-design/flowChart.vue new file mode 100644 index 0000000..dc21de9 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/views/flow-design/flowChart.vue @@ -0,0 +1,353 @@ + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/views/flow-design/index.vue b/smart-flow-api/warm-flow/warm-flow-ui/src/views/flow-design/index.vue new file mode 100644 index 0000000..c8f4caa --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/views/flow-design/index.vue @@ -0,0 +1,739 @@ + + + + + diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/views/form-design/formCreate.vue b/smart-flow-api/warm-flow/warm-flow-ui/src/views/form-design/formCreate.vue new file mode 100644 index 0000000..3c33ae1 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/views/form-design/formCreate.vue @@ -0,0 +1,7 @@ + + + diff --git a/smart-flow-api/warm-flow/warm-flow-ui/src/views/form-design/index.vue b/smart-flow-api/warm-flow/warm-flow-ui/src/views/form-design/index.vue new file mode 100644 index 0000000..e461f1f --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/src/views/form-design/index.vue @@ -0,0 +1,7 @@ + + + diff --git a/smart-flow-api/warm-flow/warm-flow-ui/vite.config.js b/smart-flow-api/warm-flow/warm-flow-ui/vite.config.js new file mode 100644 index 0000000..6d810ee --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/vite.config.js @@ -0,0 +1,70 @@ +import { defineConfig, loadEnv } from 'vite' +import path from 'path' +import createVitePlugins from './vite/plugins' + +// https://vitejs.dev/config/ +export default defineConfig(({ mode, command }) => { + const env = loadEnv(mode, process.cwd()) + return { + // 部署生产环境和开发环境下的URL。 + // 默认情况下,vite 会假设你的应用是被部署在一个域名的根路径上 + // 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。 + base: './', + + plugins: createVitePlugins(env, command === 'build'), + // 设置打包后的路径 + build: { + chunkSizeWarningLimit: 3000, + rollupOptions: { + output: { + chunkFileNames: 'js/[name]-[hash].js', + entryFileNames: 'js/[name]-[hash].js', + assetFileNames: '[ext]/[name]-[hash].[ext]' + } + } + }, + resolve: { + // https://cn.vitejs.dev/config/#resolve-alias + alias: { + // 设置路径 + '~': path.resolve(__dirname, './'), + // 设置别名 + '@': path.resolve(__dirname, './src'), + // 设置别名 + }, + // https://cn.vitejs.dev/config/#resolve-extensions + extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'] + }, + // vite 相关配置 + server: { + port: 8082, + host: true, + open: true, + proxy: { + // https://cn.vitejs.dev/config/#server-proxy + '/dev-api': { + target: 'http://localhost:8080', + changeOrigin: true, + rewrite: (p) => p.replace(/^\/dev-api/, '') + } + } + }, + //fix:error:stdin>:7356:1: warning: "@charset" must be the first rule in the file + css: { + postcss: { + plugins: [ + { + postcssPlugin: 'internal:charset-removal', + AtRule: { + charset: (atRule) => { + if (atRule.name === 'charset') { + atRule.remove(); + } + } + } + } + ] + } + } + } +}) diff --git a/smart-flow-api/warm-flow/warm-flow-ui/vite/plugins/auto-import.js b/smart-flow-api/warm-flow/warm-flow-ui/vite/plugins/auto-import.js new file mode 100644 index 0000000..a5d3576 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/vite/plugins/auto-import.js @@ -0,0 +1,12 @@ +import autoImport from 'unplugin-auto-import/vite' + +export default function createAutoImport() { + return autoImport({ + imports: [ + 'vue', + 'vue-router', + 'pinia' + ], + dts: false + }) +} diff --git a/smart-flow-api/warm-flow/warm-flow-ui/vite/plugins/index.js b/smart-flow-api/warm-flow/warm-flow-ui/vite/plugins/index.js new file mode 100644 index 0000000..f58de0c --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/vite/plugins/index.js @@ -0,0 +1,13 @@ +import vue from '@vitejs/plugin-vue' + +import createAutoImport from './auto-import' +import createSvgIcon from './svg-icon' +import createSetupExtend from './setup-extend' + +export default function createVitePlugins(viteEnv, isBuild = false) { + const vitePlugins = [vue()] + vitePlugins.push(createAutoImport()) + vitePlugins.push(createSetupExtend()) + vitePlugins.push(createSvgIcon(isBuild)) + return vitePlugins +} diff --git a/smart-flow-api/warm-flow/warm-flow-ui/vite/plugins/setup-extend.js b/smart-flow-api/warm-flow/warm-flow-ui/vite/plugins/setup-extend.js new file mode 100644 index 0000000..ed8342e --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/vite/plugins/setup-extend.js @@ -0,0 +1,5 @@ +import setupExtend from 'unplugin-vue-setup-extend-plus/vite' + +export default function createSetupExtend() { + return setupExtend({}) +} diff --git a/smart-flow-api/warm-flow/warm-flow-ui/vite/plugins/svg-icon.js b/smart-flow-api/warm-flow/warm-flow-ui/vite/plugins/svg-icon.js new file mode 100644 index 0000000..30a4140 --- /dev/null +++ b/smart-flow-api/warm-flow/warm-flow-ui/vite/plugins/svg-icon.js @@ -0,0 +1,10 @@ +import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' +import path from 'path' + +export default function createSvgIcon(isBuild) { + return createSvgIconsPlugin({ + iconDirs: [path.resolve(process.cwd(), 'src/assets/icons/svg')], + symbolId: 'icon-[dir]-[name]', + svgoOptions: isBuild + }) +} diff --git a/smart-flow-web/.env.development b/smart-flow-web/.env.development new file mode 100644 index 0000000..820a12f --- /dev/null +++ b/smart-flow-web/.env.development @@ -0,0 +1,6 @@ +NODE_ENV=development +VITE_APP_TITLE='维护警权一键通' +VITE_APP_API_URL='http://127.0.0.1:1024' +VITE_APP_BASE_PATH=mjwq +# 客户端id +VITE_APP_CLIENT_ID = 'e5cd7e4891bf95d1d19206ce24a7b32e' \ No newline at end of file diff --git a/smart-flow-web/.env.localhost b/smart-flow-web/.env.localhost new file mode 100644 index 0000000..dd610bc --- /dev/null +++ b/smart-flow-web/.env.localhost @@ -0,0 +1,3 @@ +NODE_ENV=development +VITE_APP_TITLE='SmartFlow 本地环境(Local)' +VITE_APP_API_URL='http://127.0.0.1:1024' diff --git a/smart-flow-web/.env.pre b/smart-flow-web/.env.pre new file mode 100644 index 0000000..227ce97 --- /dev/null +++ b/smart-flow-web/.env.pre @@ -0,0 +1,4 @@ +NODE_ENV=production +VITE_APP_TITLE='维护警权一键通' +VITE_APP_API_URL='https://8.148.25.165/wqapi' +VITE_APP_BASE_PATH=mjwq diff --git a/smart-flow-web/.env.production b/smart-flow-web/.env.production new file mode 100644 index 0000000..b09af1c --- /dev/null +++ b/smart-flow-web/.env.production @@ -0,0 +1,4 @@ +NODE_ENV=production +VITE_APP_TITLE='维护警权一键通' +VITE_APP_API_URL='https://53.1.194.59/wqapi/' +VITE_APP_BASE_PATH=mjwq \ No newline at end of file diff --git a/smart-flow-web/.env.test b/smart-flow-web/.env.test new file mode 100644 index 0000000..227ce97 --- /dev/null +++ b/smart-flow-web/.env.test @@ -0,0 +1,4 @@ +NODE_ENV=production +VITE_APP_TITLE='维护警权一键通' +VITE_APP_API_URL='https://8.148.25.165/wqapi' +VITE_APP_BASE_PATH=mjwq diff --git a/smart-flow-web/.eslintignore b/smart-flow-web/.eslintignore new file mode 100644 index 0000000..78e1638 --- /dev/null +++ b/smart-flow-web/.eslintignore @@ -0,0 +1,18 @@ + +*.sh +node_modules +lib +*.md +*.woff +*.ttf +.vscode +.idea +dist +public +/docs +.husky +.local +.localhost +/bin +Dockerfile +src/assets diff --git a/smart-flow-web/.eslintrc.cjs b/smart-flow-web/.eslintrc.cjs new file mode 100644 index 0000000..81e7acc --- /dev/null +++ b/smart-flow-web/.eslintrc.cjs @@ -0,0 +1,66 @@ +/* + * @Description: + * @Author: zhuoda + * @Date: 2021-11-05 + * @LastEditTime: 2022-07-05 + * @LastEditors: zhuoda + */ +module.exports = { + root: true, //此项是用来告诉eslint找当前配置文件不能往父级查找 + env: { + browser: true, + es2021: true, + node: true, + }, + parser: 'vue-eslint-parser', //使用vue-eslint-parser 来解析vue文件中的 template和script + parserOptions: { + ecmaVersion: 12, // 默认情况下,ESLint使用的是ECMAScript5语法,此处我们设置的选项是 es12 + sourceType: 'module', // 指定js导入的方式 + }, + extends: ['plugin:vue/vue3-essential', 'eslint:recommended', 'plugin:vue/base'], + globals: { + defineProps: 'readonly', + defineEmits: 'readonly', + defineExpose: 'readonly', + withDefaults: 'readonly', + }, + plugins: ['vue'], + rules: { + 'no-unused-vars': [ + 'error', + // we are only using this rule to check for unused arguments since TS + // catches unused variables but not args. + { varsIgnorePattern: '.*', args: 'none' }, + ], + 'space-before-function-paren': 'off', + + 'vue/attributes-order': 'off', + 'vue/one-component-per-file': 'off', + 'vue/html-closing-bracket-newline': 'off', + 'vue/max-attributes-per-line': 'off', + 'vue/multiline-html-element-content-newline': 'off', + 'vue/singleline-html-element-content-newline': 'off', + 'vue/attribute-hyphenation': 'off', + 'vue/require-default-prop': 'off', + 'vue/multi-word-component-names': [ + 'error', + { + ignores: ['index'], //需要忽略的组件名 + }, + ], + 'vue/html-self-closing': [ + 'error', + { + html: { + void: 'always', + normal: 'never', + component: 'always', + }, + svg: 'always', + math: 'always', + }, + ], + // Enable vue/script-setup-uses-vars rule + 'vue/script-setup-uses-vars': 'error', + }, +}; diff --git a/smart-flow-web/.gitignore b/smart-flow-web/.gitignore new file mode 100644 index 0000000..2456d73 --- /dev/null +++ b/smart-flow-web/.gitignore @@ -0,0 +1,8 @@ +node_modules +.DS_Store +**/.DS_Store +dist +dist-ssr +*.local +.idea +/mjwq/ diff --git a/smart-flow-web/.prettierrc.cjs b/smart-flow-web/.prettierrc.cjs new file mode 100644 index 0000000..d1c4ba8 --- /dev/null +++ b/smart-flow-web/.prettierrc.cjs @@ -0,0 +1,30 @@ +/* + * 代码格式化配置 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-12 14:44:18 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +module.exports = { + printWidth: 150, // 每行代码长度(默认80) + tabWidth: 2, // 缩进空格数 + useTabs: false, //不用tab缩进 + semi: true, //// 在语句末尾打印分号 + singleQuote: true, // 使用单引号而不是双引号 + vueIndentScriptAndStyle: true, //Vue文件脚本和样式标签缩进 + quoteProps: 'as-needed', // 更改引用对象属性的时间 可选值"" + jsxSingleQuote: true, // 在JSX中使用单引号而不是双引号 + trailingComma: 'es5', //多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"",默认none + bracketSpacing: true, // 在对象文字中的括号之间打印空格 + jsxBracketSameLine: false, //jsx 标签的反尖括号需要换行 + arrowParens: 'always', // 在单独的箭头函数参数周围包括括号 always:(x) => x \ avoid:x => x + rangeStart: 0, // 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码 + rangeEnd: Infinity, + requirePragma: false, // 指定要使用的解析器,不需要写文件开头的 @prettier + insertPragma: false, // 不需要自动在文件开头插入 @prettier + proseWrap: 'preserve', // 使用默认的折行标准 always\never\preserve + htmlWhitespaceSensitivity: 'css', // 指定HTML文件的全局空格敏感度 css\strict\ignore + endOfLine: 'auto', // 因为prettier的规范和eslint的换行规则不同,所以这个必须配置。要不然每次打开文件都会有一堆的警告;换行符使用 lf 结尾是 可选值" + + + + + + + + %VITE_APP_TITLE% + + + +
+ + + diff --git a/smart-flow-web/jsconfig.json b/smart-flow-web/jsconfig.json new file mode 100644 index 0000000..c97f579 --- /dev/null +++ b/smart-flow-web/jsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES6", + "jsx":"preserve", + "module": "commonjs", + "allowSyntheticDefaultImports": true, + "baseUrl": "./", + "paths": { + "/@/*": [ + "src/*" + ] + } + }, + "exclude": [ + "node_modules" + ] +} \ No newline at end of file diff --git a/smart-flow-web/package.json b/smart-flow-web/package.json new file mode 100644 index 0000000..d652d71 --- /dev/null +++ b/smart-flow-web/package.json @@ -0,0 +1,77 @@ +{ + "name": "mjwq", + "version": "3.0.0", + "author": { + "name": "lovefawn", + "email": "", + "url": "" + }, + "license": "MIT", + "homepage": "", + "scripts": { + "localhost": "vite --mode localhost", + "dev": "vite", + "build:test": "vite build --mode test", + "build:pre": "vite build --mode pre", + "build:prod": "vite build --mode production" + }, + "type": "module", + "dependencies": { + "@ant-design/icons-vue": "^7.0.1", + "@wangeditor-next/editor": "5.6.34", + "@wangeditor-next/editor-for-vue": "5.1.14", + "ant-design-vue": "4.2.5", + "axios": "1.6.8", + "clipboard": "2.0.11", + "crypto-js": "4.1.1", + "dayjs": "1.10.5", + "decimal.js": "10.3.1", + "diff": "5.2.0", + "diff2html": "3.4.47", + "echarts": "5.4.3", + "element-plus": "^2.13.3", + "exceljs": "^4.4.0", + "highlight.js": "11.8.0", + "jsencrypt": "^3.5.4", + "lodash": "4.17.21", + "lunar-javascript": "1.6.12", + "mitt": "3.0.1", + "nprogress": "0.2.0", + "pinia": "2.1.7", + "sm-crypto": "0.3.13", + "sortablejs": "1.15.0", + "ua-parser-js": "1.0.35", + "uuid": "^11.1.0", + "v-viewer": "~1.6.4", + "vue": "3.4.27", + "vue-i18n": "9.13.1", + "vue-router": "4.3.2", + "vue3-json-viewer": "2.2.2" + }, + "devDependencies": { + "@vitejs/plugin-vue": "5.0.4", + "@vue/compiler-sfc": "3.4.27", + "eslint": "^8.16.0", + "eslint-config-prettier": "~9.0.0", + "eslint-plugin-prettier": "~5.0.0", + "eslint-plugin-vue": "~9.17.0", + "less": "~4.2.0", + "less-loader": "~11.1.3", + "prettier": "~3.0.2", + "rimraf": "~5.0.1", + "stylelint": "~14.8.5", + "stylelint-config-prettier": "~9.0.3", + "stylelint-config-standard": "~25.0.0", + "stylelint-order": "~5.0.0", + "terser": "~5.29.2", + "vite": "5.2.12", + "vue-eslint-parser": "~9.4.2" + }, + "engines": { + "node": ">=18" + }, + "repository": { + "type": "git", + "url": "" + } +} diff --git a/smart-flow-web/postcss.config.cjs b/smart-flow-web/postcss.config.cjs new file mode 100644 index 0000000..2c7ab7c --- /dev/null +++ b/smart-flow-web/postcss.config.cjs @@ -0,0 +1,4 @@ +module.exports = { + plugins: { + }, +}; diff --git a/smart-flow-web/public/background.png b/smart-flow-web/public/background.png new file mode 100644 index 0000000..40d6a98 Binary files /dev/null and b/smart-flow-web/public/background.png differ diff --git a/smart-flow-web/public/favicon.ico b/smart-flow-web/public/favicon.ico new file mode 100644 index 0000000..5b38c41 Binary files /dev/null and b/smart-flow-web/public/favicon.ico differ diff --git a/smart-flow-web/src/App.vue b/smart-flow-web/src/App.vue new file mode 100644 index 0000000..dea07be --- /dev/null +++ b/smart-flow-web/src/App.vue @@ -0,0 +1,124 @@ + + + + + + + + diff --git a/smart-flow-web/src/api/business/category/category-api.js b/smart-flow-web/src/api/business/category/category-api.js new file mode 100644 index 0000000..5eb80d2 --- /dev/null +++ b/smart-flow-web/src/api/business/category/category-api.js @@ -0,0 +1,30 @@ +/* + * 类目api + * + * @Author: 卓大 + * @Date: 2022-09-03 21:35:00 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import { getRequest, postRequest } from '/@/lib/axios'; + +export const categoryApi = { + // 添加类目 @author 卓大 + addCategory: (param) => { + return postRequest('/category/add', param); + }, + // GET + // 删除类目 @author 卓大 + deleteCategoryById: (categoryId) => { + return getRequest(`/category/delete/${categoryId}`); + }, + // 查询类目层级树 @author 卓大 + queryCategoryTree: (param) => { + return postRequest('/category/tree', param); + }, + // 更新类目 @author 卓大 + updateCategory: (param) => { + return postRequest('/category/update', param); + }, +}; diff --git a/smart-flow-web/src/api/business/goods/goods-api.js b/smart-flow-web/src/api/business/goods/goods-api.js new file mode 100644 index 0000000..007029b --- /dev/null +++ b/smart-flow-web/src/api/business/goods/goods-api.js @@ -0,0 +1,41 @@ +/* + * @Description: + * @Author: zhuoda + * @Date: 2021-11-05 + * @LastEditTime: 2022-06-23 + * @LastEditors: zhuoda + */ +import {postRequest, getRequest, getDownload} from '/@/lib/axios'; + +export const goodsApi = { + // 添加商品 @author zhuoda + addGoods: (param) => { + return postRequest('/goods/add', param); + }, + // 删除 @author zhuoda + deleteGoods: (goodsId) => { + return getRequest(`/goods/delete/${goodsId}`); + }, + // 批量 @author zhuoda + batchDelete: (goodsIdList) => { + return postRequest('/goods/batchDelete', goodsIdList); + }, + // 分页查询 @author zhuoda + queryGoodsList: (param) => { + return postRequest('/goods/query', param); + }, + // 更新商品 @author zhuoda + updateGoods: (param) => { + return postRequest('/goods/update', param); + }, + + // 导入 @author 卓大 + importGoods : (file) =>{ + return postRequest('/goods/importGoods',file); + }, + + // 导出 @author 卓大 + exportGoods : () =>{ + return getDownload('/goods/exportGoods'); + } +}; diff --git a/smart-flow-web/src/api/business/oa/bank-api.js b/smart-flow-web/src/api/business/oa/bank-api.js new file mode 100644 index 0000000..7e9b627 --- /dev/null +++ b/smart-flow-web/src/api/business/oa/bank-api.js @@ -0,0 +1,42 @@ +/* + * 银行卡 + * + * @Author: 善逸 + * @Date: 2022-09-03 21:42:08 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import { postRequest, getRequest } from '/@/lib/axios'; + +export const bankApi = { + // 新建银行信息 @author 善逸 + create: (param) => { + return postRequest('/oa/bank/create', param); + }, + + // 删除银行信息 @author 善逸 + delete: (bankId) => { + return getRequest(`/oa/bank/delete/${bankId}`); + }, + + // 查询银行信息详情 @author 善逸 + detail: (bankId) => { + return getRequest(`/oa/bank/get/${bankId}`); + }, + + // 分页查询银行信息 @author 善逸 + pageQuery: (param) => { + return postRequest('/oa/bank/page/query', param); + }, + + // 编辑银行信息 @author 善逸 + update: (param) => { + return postRequest('/oa/bank/update', param); + }, + + // 根据企业ID查询不分页的银行列表 @author 善逸 + queryList: (enterpriseId) => { + return getRequest(`/oa/bank/query/list/${enterpriseId}`); + }, +}; diff --git a/smart-flow-web/src/api/business/oa/enterprise-api.js b/smart-flow-web/src/api/business/oa/enterprise-api.js new file mode 100644 index 0000000..5c2ad68 --- /dev/null +++ b/smart-flow-web/src/api/business/oa/enterprise-api.js @@ -0,0 +1,69 @@ +/* + * 企业信息 + * + * @Author: 开云 + * @Date: 2022-09-03 21:47:28 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import {postRequest, getRequest, postDownload} from '/@/lib/axios'; + +export const enterpriseApi = { + // 新建企业 @author 开云 + create: (param) => { + return postRequest('/oa/enterprise/create', param); + }, + + // 删除企业 @author 开云 + delete: (enterpriseId) => { + return getRequest(`/oa/enterprise/delete/${enterpriseId}`); + }, + + // 查询企业详情 @author 开云 + detail: (enterpriseId) => { + return getRequest(`/oa/enterprise/get/${enterpriseId}`); + }, + + // 分页查询企业模块 @author 开云 + pageQuery: (param) => { + return postRequest('/oa/enterprise/page/query', param); + }, + + // 导出企业数据excel @author 卓大 + exportExcel: (param) => { + return postDownload('/oa/enterprise/exportExcel', param); + }, + + //企业列表查询 含数据范围 @author 开云 + queryList: (type) => { + let query = ''; + if (type) { + query = `?type=${type}`; + } + return getRequest(`/oa/enterprise/query/list${query}`); + }, + + // 编辑企业 @author 开云 + update: (param) => { + return postRequest('/oa/enterprise/update', param); + }, + // 企业全部员工List @author yandy + employeeList: (param) => { + return postRequest('/oa/enterprise/employee/list', param); + }, + // 分页查询企业员工List @author 卓大 + queryPageEmployeeList: (param) => { + return postRequest('/oa/enterprise/employee/queryPage', param); + }, + // 添加员工 @author yandy + addEmployee: (param) => { + return postRequest('/oa/enterprise/employee/add', param); + }, + + // 删除员工 @author yandy + deleteEmployee: (param) => { + return postRequest('/oa/enterprise/employee/delete', param); + }, + +}; diff --git a/smart-flow-web/src/api/business/oa/invoice-api.js b/smart-flow-web/src/api/business/oa/invoice-api.js new file mode 100644 index 0000000..d347af2 --- /dev/null +++ b/smart-flow-web/src/api/business/oa/invoice-api.js @@ -0,0 +1,44 @@ +/* + * OA发票信息 + * + * @Author: 善逸 + * @Date: 2022-09-03 21:48:54 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import { postRequest, getRequest } from '/@/lib/axios'; + +export const invoiceApi = { + + // 新建发票信息 @author 善逸 + create: (param) => { + return postRequest('/oa/invoice/create', param); + }, + + // 删除发票信息 @author 善逸 + delete: (bankId) => { + return getRequest(`/oa/invoice/delete/${bankId}`); + }, + + // 查询发票信息详情 @author 善逸 + detail: (bankId) => { + return getRequest(`//oa/invoice/get/${bankId}`); + }, + + // 分页查询发票信息 @author 善逸 + pageQuery: (param) => { + return postRequest('/oa/invoice/page/query', param); + }, + + // 编辑发票信息 @author 善逸 + update: (param) => { + return postRequest('/oa/invoice/update', param); + }, + + // 查询发票列表 @author 善逸 + queryList: (enterpriseId) => { + return getRequest(`/oa/invoice/query/list/${enterpriseId}`); + }, + +}; diff --git a/smart-flow-web/src/api/business/oa/leave-api.js b/smart-flow-web/src/api/business/oa/leave-api.js new file mode 100644 index 0000000..31e0545 --- /dev/null +++ b/smart-flow-web/src/api/business/oa/leave-api.js @@ -0,0 +1,55 @@ +/** + * OA 请假申请表 api 封装 + * + * @Author: lf + * @Date: 2025-05-31 20:00:10 + * @Copyright lf + */ +import { postRequest, getRequest } from '/@/lib/axios'; + +export const leaveApi = { + + /** + * 分页查询 @author lf + */ + queryPage : (param) => { + return postRequest('/leave/queryPage', param); + }, + + /** + * 增加 @author lf + */ + add: (param) => { + return postRequest('/leave/add', param); + }, + + /** + * 修改 @author lf + */ + update: (param) => { + return postRequest('/leave/update', param); + }, + + + /** + * 删除 @author lf + */ + delete: (id) => { + return getRequest(`/leave/delete/${id}`); + }, + + /** + * 批量删除 @author lf + */ + batchDelete: (idList) => { + return postRequest('/leave/batchDelete', idList); + }, + + /** + * 删除 @author lf + */ + getDetail: (id) => { + return getRequest(`/leave/${id}`); + }, + +}; diff --git a/smart-flow-web/src/api/business/oa/notice-api.js b/smart-flow-web/src/api/business/oa/notice-api.js new file mode 100644 index 0000000..056eb2d --- /dev/null +++ b/smart-flow-web/src/api/business/oa/notice-api.js @@ -0,0 +1,74 @@ +/* + * @Description: 公告信息、企业动态 + * @version: + * @Author: zhuoda + * @Date: 2022-08-16 20:34:36 + */ +import { postRequest, getRequest } from '/@/lib/axios'; + +export const noticeApi = { + // ---------------- 通知公告类型 ----------------------- + + // 通知公告类型-获取全部 @author zhuoda + getAllNoticeTypeList() { + return getRequest('/oa/noticeType/getAll'); + }, + + // 通知公告类型-添加 @author zhuoda + addNoticeType(name) { + return getRequest(`/oa/noticeType/add/${name}`); + }, + + // 通知公告类型-修改 @author zhuoda + updateNoticeType(noticeTypeId, name) { + return getRequest(`/oa/noticeType/update/${noticeTypeId}/${name}`); + }, + // 通知公告类型-删除 @author zhuoda + deleteNoticeType(noticeTypeId) { + return getRequest(`/oa/noticeType/delete/${noticeTypeId}`); + }, + + // ---------------- 通知公告管理 ----------------------- + + // 通知公告-分页查询 @author zhuoda + queryNotice(param) { + return postRequest('/oa/notice/query', param); + }, + + // 通知公告-添加 @author zhuoda + addNotice(param) { + return postRequest('/oa/notice/add', param); + }, + + // 通知公告-更新 @author zhuoda + updateNotice(param) { + return postRequest('/oa/notice/update', param); + }, + + // 通知公告-删除 @author zhuoda + deleteNotice(noticeId) { + return getRequest(`/oa/notice/delete/${noticeId}`); + }, + + // 通知公告-更新详情 @author zhuoda + getUpdateNoticeInfo(noticeId) { + return getRequest(`/oa/notice/getUpdateVO/${noticeId}`); + }, + + // --------------------- 【员工】查看 通知公告 ------------------------- + + // 通知公告-员工-查看详情 @author zhuoda + view(noticeId) { + return getRequest(`/oa/notice/employee/view/${noticeId}`); + }, + + // 通知公告-员工-查询 @author zhuoda + queryEmployeeNotice(param) { + return postRequest('/oa/notice/employee/query', param); + }, + + // 【员工】通知公告-查询 查看记录 @author zhuoda + queryViewRecord(param) { + return postRequest('/oa/notice/employee/queryViewRecord', param); + }, +}; diff --git a/smart-flow-web/src/api/business/rights/claim-config-api.js b/smart-flow-web/src/api/business/rights/claim-config-api.js new file mode 100644 index 0000000..f290832 --- /dev/null +++ b/smart-flow-web/src/api/business/rights/claim-config-api.js @@ -0,0 +1,50 @@ +import { getRequest, postRequest } from '/@/lib/axios.js'; + +/** + * 保障诉求配置管理 API + */ +export const claimConfigApi = { + /** + * 分页查询保障诉求配置列表 + */ + queryPage: (params) => { + return getRequest('/mjwq/rights/claim-config/queryPage', params); + }, + + /** + * 查询所有保障诉求配置列表 + */ + queryList: () => { + return getRequest('/mjwq/rights/claim-config/queryList'); + }, + + /** + * 根据 ID 查询详情 + */ + getById: (configId) => { + return getRequest(`/mjwq/rights/claim-config/getById/${configId}`); + }, + + /** + * 新增保障诉求配置 + */ + add: (data) => { + return postRequest('/mjwq/rights/claim-config/add', data); + }, + + /** + * 更新保障诉求配置 + */ + update: (data) => { + return postRequest('/mjwq/rights/claim-config/update', data); + }, + + /** + * 删除保障诉求配置 + */ + delete: (configId) => { + return getRequest(`/mjwq/rights/claim-config/delete/${configId}`); + }, +}; + +export default claimConfigApi; diff --git a/smart-flow-web/src/api/business/rights/consultation-api.js b/smart-flow-web/src/api/business/rights/consultation-api.js new file mode 100644 index 0000000..733ce84 --- /dev/null +++ b/smart-flow-web/src/api/business/rights/consultation-api.js @@ -0,0 +1,78 @@ +import { getRequest, postRequest } from '/@/lib/axios.js'; + +/** + * 会商管理 API + */ +export const consultationApi = { + /** + * 分页查询会商列表 + */ + queryPage: (params) => { + return getRequest('/mjwq/rights/consultation/queryPage', params); + }, + + /** + * 查询所有会商列表 + */ + queryList: () => { + return getRequest('/mjwq/rights/consultation/queryList'); + }, + + /** + * 根据 ID 查询会商详情 + */ + getById: (consultationId) => { + return getRequest(`/mjwq/rights/consultation/getById/${consultationId}`); + }, + + /** + * 新增会商 + */ + add: (data) => { + return postRequest('/mjwq/rights/consultation/add', data); + }, + + /** + * 更新会商 + */ + update: (data) => { + return postRequest('/mjwq/rights/consultation/update', data); + }, + + /** + * 删除会商 + */ + delete: (consultationId) => { + return getRequest(`/mjwq/rights/consultation/delete/${consultationId}`); + }, + + /** + * 查询会商成员列表 + */ + queryMemberList: (consultationId) => { + return getRequest(`/mjwq/rights/consultation/member/list/${consultationId}`); + }, + + /** + * 添加会商成员 + */ + addMember: (data) => { + return postRequest('/mjwq/rights/consultation/member/add', data); + }, + + /** + * 更新会商成员 + */ + updateMember: (data) => { + return postRequest('/mjwq/rights/consultation/member/update', data); + }, + + /** + * 删除会商成员 + */ + deleteMember: (memberId) => { + return getRequest(`/mjwq/rights/consultation/member/delete/${memberId}`); + }, +}; + +export default consultationApi; diff --git a/smart-flow-web/src/api/business/rights/event-type-api.js b/smart-flow-web/src/api/business/rights/event-type-api.js new file mode 100644 index 0000000..455f0b1 --- /dev/null +++ b/smart-flow-web/src/api/business/rights/event-type-api.js @@ -0,0 +1,64 @@ +import { getRequest, postRequest } from '/@/lib/axios.js'; + +/** + * 事件性质管理 API + */ +export const eventTypeApi = { + /** + * 分页查询事件性质列表 + */ + queryPage: (params) => { + return getRequest('/mjwq/rights/event-type/queryPage', params); + }, + + /** + * 查询所有事件性质列表 + */ + queryList: () => { + return getRequest('/mjwq/rights/event-type/queryList'); + }, + + /** + * 根据 ID 查询详情 + */ + getById: (eventTypeId) => { + return getRequest(`/mjwq/rights/event-type/getById/${eventTypeId}`); + }, + + /** + * 根据 ID 查询详情(包含关联保障诉求) + */ + getDetailById: (eventTypeId) => { + return getRequest(`/mjwq/rights/event-type/getDetailById/${eventTypeId}`); + }, + + /** + * 根据 ID 查询详情(包含关联保障诉求) + */ + getClaimById: (eventTypeId) => { + return getRequest(`/mjwq/rights/event-type/getClaimById/${eventTypeId}`); + }, + + /** + * 新增事件性质 + */ + add: (data) => { + return postRequest('/mjwq/rights/event-type/add', data); + }, + + /** + * 更新事件性质 + */ + update: (data) => { + return postRequest('/mjwq/rights/event-type/update', data); + }, + + /** + * 删除事件性质 + */ + delete: (eventTypeId) => { + return getRequest(`/mjwq/rights/event-type/delete/${eventTypeId}`); + }, +}; + +export default eventTypeApi; diff --git a/smart-flow-web/src/api/business/rights/infringement-link-api.js b/smart-flow-web/src/api/business/rights/infringement-link-api.js new file mode 100644 index 0000000..24e59e5 --- /dev/null +++ b/smart-flow-web/src/api/business/rights/infringement-link-api.js @@ -0,0 +1,50 @@ +import { getRequest, postRequest } from '/@/lib/axios.js'; + +/** + * 受侵害执法环节管理 API + */ +export const infringementLinkApi = { + /** + * 分页查询受侵害执法环节列表 + */ + queryPage: (params) => { + return getRequest('/mjwq/rights/infringement-link/queryPage', params); + }, + + /** + * 查询所有受侵害执法环节列表 + */ + queryList: () => { + return getRequest('/mjwq/rights/infringement-link/queryList'); + }, + + /** + * 根据 ID 查询详情 + */ + getById: (linkId) => { + return getRequest(`/mjwq/rights/infringement-link/getById/${linkId}`); + }, + + /** + * 新增受侵害执法环节 + */ + add: (data) => { + return postRequest('/mjwq/rights/infringement-link/add', data); + }, + + /** + * 更新受侵害执法环节 + */ + update: (data) => { + return postRequest('/mjwq/rights/infringement-link/update', data); + }, + + /** + * 删除受侵害执法环节 + */ + delete: (linkId) => { + return getRequest(`/mjwq/rights/infringement-link/delete/${linkId}`); + }, +}; + +export default infringementLinkApi; diff --git a/smart-flow-web/src/api/business/rights/police-event-api.js b/smart-flow-web/src/api/business/rights/police-event-api.js new file mode 100644 index 0000000..f5b29c4 --- /dev/null +++ b/smart-flow-web/src/api/business/rights/police-event-api.js @@ -0,0 +1,31 @@ +/* + * 警情查询 API + * + * @Author: SmartFlow + * @Date: 2026-03-26 + */ + +import { getRequest, postRequest } from '/@/lib/axios.js'; + +/** + * 分页查询警情列表 + * @param params 查询参数 + * @returns Promise + */ +export function queryPage(params) { + return postRequest('/rights/policeEvent/queryPage', params); +} + +/** + * 获取警情详情 + * @param receiveId 接警单 ID + * @returns Promise + */ +export function getDetail(receiveId) { + return getRequest(`/rights/policeEvent/detail/${receiveId}`); +} + +export const policeEventApi = { + queryPage, + getDetail, +}; diff --git a/smart-flow-web/src/api/business/rights/rights-case-api.js b/smart-flow-web/src/api/business/rights/rights-case-api.js new file mode 100644 index 0000000..5f1c649 --- /dev/null +++ b/smart-flow-web/src/api/business/rights/rights-case-api.js @@ -0,0 +1,31 @@ +/* + * 案件查询 API + * + * @Author: SmartFlow + * @Date: 2026-03-26 + */ + +import { getRequest, postRequest } from '/@/lib/axios.js'; + +/** + * 分页查询案件列表 + * @param params 查询参数 + * @returns Promise + */ +export function queryPage(params) { + return postRequest('/rights/case/queryPage', params); +} + +/** + * 获取案件详情 + * @param caseId 案件 ID + * @returns Promise + */ +export function getDetail(caseId) { + return getRequest(`/rights/case/detail/${caseId}`); +} + +export const rightsCaseApi = { + queryPage, + getDetail, +}; diff --git a/smart-flow-web/src/api/business/rights/rights-defense-api.js b/smart-flow-web/src/api/business/rights/rights-defense-api.js new file mode 100644 index 0000000..beb71d4 --- /dev/null +++ b/smart-flow-web/src/api/business/rights/rights-defense-api.js @@ -0,0 +1,137 @@ +import { getRequest, postRequest, getDownload } from '/@/lib/axios.js'; + +/** + * 民警执法维权 API + */ +export const rightsDefenseApi = { + /** + * 分页查询维权申请列表 + */ + queryPage: (data) => { + return getRequest('/mjwq/rights/defense/queryPage', data); + }, + + /** + * 分页查询待办列表 + */ + queryTodoPage: (data) => { + return getRequest('/mjwq/rights/defense/queryTodoPage', data); + }, + + /** + * 分页查询已办列表 + */ + queryDonePage: (data) => { + return getRequest('/mjwq/rights/defense/queryDonePage', data); + }, + + /** + * 获取下一个审批人列表 + */ + getNextAppr: (rightsId, instanceId, applicantAreaCode) => { + return getRequest(`/mjwq/rights/defense/getNextAppr/${rightsId}`, { + instanceId, + applicantAreaCode, + }); + }, + + /** + * 获取下一步审批人列表(提交时) + */ + getNextApprovers: (instanceId) => { + return getRequest(`/mjwq/rights/defense/getNextApprovers/${instanceId}`); + }, + + /** + * 设置下一步审批人 + */ + setNextHandler: (instanceId, employeeId) => { + return postRequest(`/mjwq/rights/defense/setNextHandler`, { + instanceId, + employeeId + }); + }, + + /** + * 根据 ID 查询维权申请详情 + */ + getById: (rightsId) => { + return getRequest(`/mjwq/rights/defense/getById/${rightsId}`); + }, + + /** + * 保存草稿 + */ + saveDraft: (data) => { + return postRequest('/mjwq/rights/defense/saveDraft', data); + }, + + /** + * 提交申请 + */ + submitApplication: (data) => { + return postRequest('/mjwq/rights/defense/submit', data); + }, + + /** + * 更新申请 + */ + update: (data) => { + return postRequest('/mjwq/rights/defense/update', data); + }, + + /** + * 删除维权申请 + */ + delete: (rightsId) => { + return getRequest(`/mjwq/rights/defense/delete/${rightsId}`); + }, + + /** + * 受理维权申请 + */ + accept: (rightsId, taskId) => { + return getRequest(`/mjwq/rights/defense/accept/${rightsId}`, { taskId }); + }, + + /** + * 驳回维权申请 + */ + reject: (rightsId, taskId, reason) => { + return postRequest(`/mjwq/rights/defense/reject/${rightsId}`, null, { taskId, reason }); + }, + + doneList: (instanceId) => { + return getRequest(`/mjwq/rights/defense/doneList/${instanceId}`); + }, + + /** + * 提交评价 + */ + submitEvaluation: (data) => { + return postRequest('/mjwq/rights/defense/submitEvaluation', data); + }, + + /** + * 下载维权证明模板 + */ + downloadCertificateTemplate: () => { + return getDownload('/mjwq/rights/defense/downloadCertificateTemplate', {}); + }, + + /** + * 新增保障诉求 + */ + addClaim: (data) => { + return postRequest('/mjwq/rights/claim/add', data); + }, + + /** + * 删除保障诉求 + */ + deleteClaim: (claimId) => { + return getRequest(`/mjwq/rights/claim/delete/${claimId}`); + }, +}; + +export default rightsDefenseApi; diff --git a/smart-flow-web/src/api/business/rights/rights-statistic-api.js b/smart-flow-web/src/api/business/rights/rights-statistic-api.js new file mode 100644 index 0000000..1b17124 --- /dev/null +++ b/smart-flow-web/src/api/business/rights/rights-statistic-api.js @@ -0,0 +1,47 @@ +import { getRequest } from '/@/lib/axios.js'; + +export const rightsStatisticApi = { + getSummary: (params) => { + return getRequest('/mjwq/rights/statistic/summary', params); + }, + + getTrendData: (params) => { + return getRequest('/mjwq/rights/statistic/trend', params); + }, + + getLinkData: (params) => { + return getRequest('/mjwq/rights/statistic/link', params); + }, + + getTypeData: (params) => { + return getRequest('/mjwq/rights/statistic/type', params); + }, + + getDegreeData: (params) => { + return getRequest('/mjwq/rights/statistic/degree', params); + }, + + getVictimData: (params) => { + return getRequest('/mjwq/rights/statistic/victim', params); + }, + + getAreaData: (params) => { + return getRequest('/mjwq/rights/statistic/area', params); + }, + + getEfficiencyData: (params) => { + return getRequest('/mjwq/rights/statistic/efficiency', params); + }, + + // 综合统计月报表 + getMonthlyReport: (params) => { + return getRequest('/mjwq/rights/statistic/monthly-report', params); + }, + + // 导出综合统计月报表 + exportMonthlyReport: (params) => { + return '/mjwq/rights/statistic/monthly-report/export?' + new URLSearchParams(params).toString(); + }, +}; + +export default rightsStatisticApi; diff --git a/smart-flow-web/src/api/business/rights/rights-warning-api.js b/smart-flow-web/src/api/business/rights/rights-warning-api.js new file mode 100644 index 0000000..f4d01ad --- /dev/null +++ b/smart-flow-web/src/api/business/rights/rights-warning-api.js @@ -0,0 +1,22 @@ +import { getRequest, postRequest } from '/@/lib/axios.js'; + +/** + * 疑似被侵权案件预警 API + */ +export const rightsWarningApi = { + /** + * 分页查询预警列表 + */ + queryPage: (data) => { + return getRequest('/mjwq/rights/warning/queryPage', data); + }, + + /** + * 获取预警详情 + */ + getById: (warningId) => { + return getRequest(`/mjwq/rights/warning/getById/${warningId}`); + }, +}; + +export default rightsWarningApi; diff --git a/smart-flow-web/src/api/flow/definition-api.js b/smart-flow-web/src/api/flow/definition-api.js new file mode 100644 index 0000000..1662227 --- /dev/null +++ b/smart-flow-web/src/api/flow/definition-api.js @@ -0,0 +1,87 @@ +/** + * 流程定义 api 封装 + * + * @Author: lovefawn + * @Date: 2025-04-01 23:31:38 + */ +import { postRequest, getRequest, postDownload } from '/@/lib/axios'; + +export const definitionApi = { + /** + * 分页查询 @author lovefawn + */ + queryPage: (param) => { + return postRequest('/flow/definition/queryPage', param); + }, + + /** + * 增加 @author lovefawn + */ + add: (param) => { + return postRequest('/flow/definition/add', param); + }, + + /** + * 修改 @author lovefawn + */ + update: (param) => { + return postRequest('/flow/definition/update', param); + }, + + /** + * 删除 @author lovefawn + */ + delete: (id) => { + return getRequest(`/flow/definition/delete/${id}`); + }, + + /** + * 批量删除 @author lovefawn + */ + batchDelete: (idList) => { + return postRequest('/flow/definition/batchDelete', idList); + }, + + /** + * 查询列表 @author lovefawn + */ + queryList: () => { + return getRequest('/flow/definition/queryList'); + }, + + // 查看流程图 + chartDef: (definitionId) => { + return getRequest('/flow/definition/chartDef/' + definitionId); + }, + // 发布流程定义 + publish: (id) => { + return getRequest('/flow/definition/publish/' + id); + }, + + // 取消发布流程定义 + unPublish: (id) => { + return getRequest('/flow/definition/unPublish/' + id); + }, + + // 复制流程定义 + copyDef: (id) => { + return getRequest('/flow/definition/copyDef/' + id); + }, + // 激活流程 + active: (definitionId) => { + return getRequest('/flow/definition/active/' + definitionId); + }, + + // 挂起流程 + unActive: (definitionId) => { + return getRequest('/flow/definition/unActive/' + definitionId); + }, + // 导入流程定义 + importDefinition: (file) => { + return postRequest('/flow/definition/importDefinition', file); + }, + // 导出流程定义 + export: (record) => { + return postDownload('/flow/definition/exportDefinition/' + record.id, record.flowCode + '_' + record.version + '.json'); + } +}; diff --git a/smart-flow-web/src/api/flow/instance-api.js b/smart-flow-web/src/api/flow/instance-api.js new file mode 100644 index 0000000..2fc4813 --- /dev/null +++ b/smart-flow-web/src/api/flow/instance-api.js @@ -0,0 +1,95 @@ +import {postRequest, getRequest} from "/@/lib/axios.js"; + +export const instanceApi = { + /** + * 启动流程 + * @param data + * @returns {*} + */ +startWorkFlow : (data) => { + return postRequest('/flow/execute/startWorkFlow',data); +}, + /** + * 审批流程 + * @param data + * @returns {*} + */ + approveTaskByInstance : (data) => { + return postRequest('/flow/execute/approveTaskByInstance',data); + }, + +/** + * 办理流程 + * @param data + * @returns {*} + */ +completeTask :(data) => { + return postRequest('/flow/execute/completeTask', data); +}, + /** + * 查询待办任务列表 @author lovefawn + */ + toDoPage: (query) => { + return getRequest('/flow/execute/toDoPage', query); + }, + + /** + * 查询已办任务列表 @author lovefawn + */ + donePage: (query) => { + return getRequest('/flow/execute/donePage', query); + }, + + /** + * 查询抄送任务列表 @author lovefawn + */ + copyPage: (query) => { + return getRequest('/flow/execute/copyPage', query); + }, + + /** + * 查询实例已办列表 @author lovefawn + */ + doneList: (instanceId) => { + return getRequest(`/flow/execute/doneList/${instanceId}`); + }, + + /** + * 查询跳转节点列表 @author lovefawn + */ + anyNodeList: (instanceId) => { + return getRequest(`/flow/execute/anyNodeList/${instanceId}`); + }, + + /** + * 任务操作(转办/加签/委派/减签) @author lovefawn + */ + interactiveType: (params) => { + return postRequest('/flow/execute/interactiveType', params); + }, + + // 查询用户列表-转办|加签|委派|减签 + interactiveTypeEmployeeEntity:(query) => {return getRequest('/flow/execute/interactiveTypeEmployeeEntity', query); + }, + + /** + * 通过ID查询任务详情 @author lovefawn + */ + getTaskById: (taskId) => { + return getRequest(`/flow/execute/getTaskById/${taskId}`); + }, + + /** + * 激活流程实例 @author lovefawn + */ + active: (instanceId) => { + return getRequest(`/flow/execute/active/${instanceId}`); + }, + + /** + * 挂起流程实例 @author lovefawn + */ + unActive: (instanceId) => { + return getRequest(`/flow/execute/unActive/${instanceId}`); + }, +} \ No newline at end of file diff --git a/smart-flow-web/src/api/support/api-encrypt-api.js b/smart-flow-web/src/api/support/api-encrypt-api.js new file mode 100644 index 0000000..83f9ff4 --- /dev/null +++ b/smart-flow-web/src/api/support/api-encrypt-api.js @@ -0,0 +1,40 @@ +/** + * 接口:加密、解密 + * + * @Author: 1024创新实验室-主任-卓大 + * @Date: 2023-10-17 20:02:37 + * @Copyright 1024创新实验室 + */ +import { postRequest, postEncryptRequest } from '/@/lib/axios'; + +export const encryptApi = { + + /** + * 测试 请求加密 @author 1024创新实验室-主任-卓大 + */ + testRequestEncrypt: (param) => { + return postEncryptRequest('/support/apiEncrypt/testRequestEncrypt', param); + }, + + /** + * 测试 返回加密 @author 1024创新实验室-主任-卓大 + */ + testResponseEncrypt: (param) => { + return postRequest('/support/apiEncrypt/testResponseEncrypt', param); + }, + + /** + * 测试 请求参数加密和解密、返回数据加密和解密 @author 1024创新实验室-主任-卓大 + */ + testDecryptAndEncrypt: (param) => { + return postEncryptRequest('/support/apiEncrypt/testDecryptAndEncrypt', param); + }, + + /** + * 测试 数组加解密 @author 1024创新实验室-主任-卓大 + */ + testArray : (param) => { + return postEncryptRequest('/support/apiEncrypt/testArray', param); + }, + +}; diff --git a/smart-flow-web/src/api/support/cache-api.js b/smart-flow-web/src/api/support/cache-api.js new file mode 100644 index 0000000..5cb4d37 --- /dev/null +++ b/smart-flow-web/src/api/support/cache-api.js @@ -0,0 +1,25 @@ +/* + * 缓存 + * + * @Author: 罗伊 + * @Date: 2022-09-03 21:51:34 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import {getRequest} from '/@/lib/axios'; + +export const cacheApi = { + // 获取某个缓存的所有key @author 罗伊 + getKeys: (cacheName) => { + return getRequest(`/support/cache/keys/${cacheName}`); + }, + // 移除某个缓存 @author 罗伊 + remove: (cacheName) => { + return getRequest(`/support/cache/remove/${cacheName}`); + }, + // 获取所有缓存 @author 罗伊 + getAllCacheNames: () => { + return getRequest('/support/cache/names'); + }, +}; diff --git a/smart-flow-web/src/api/support/change-log-api.js b/smart-flow-web/src/api/support/change-log-api.js new file mode 100644 index 0000000..e5ce1cb --- /dev/null +++ b/smart-flow-web/src/api/support/change-log-api.js @@ -0,0 +1,45 @@ +/** + * 系统更新日志 api 封装 + * + * @Author: 卓大 + * @Date: 2022-09-26 14:53:50 + * @Copyright 1024创新实验室 + */ +import { postRequest, getRequest } from '/@/lib/axios'; + +export const changeLogApi = { + /** + * 分页查询 @author 卓大 + */ + queryPage: (param) => { + return postRequest('/support/changeLog/queryPage', param); + }, + + /** + * 增加 @author 卓大 + */ + add: (param) => { + return postRequest('/support/changeLog/add', param); + }, + + /** + * 修改 @author 卓大 + */ + update: (param) => { + return postRequest('/support/changeLog/update', param); + }, + + /** + * 删除 @author 卓大 + */ + delete: (id) => { + return getRequest(`/support/changeLog/delete/${id}`); + }, + + /** + * 批量删除 @author 卓大 + */ + batchDelete: (idList) => { + return postRequest('/support/changeLog/batchDelete', idList); + }, +}; diff --git a/smart-flow-web/src/api/support/code-generator-api.js b/smart-flow-web/src/api/support/code-generator-api.js new file mode 100644 index 0000000..7289b62 --- /dev/null +++ b/smart-flow-web/src/api/support/code-generator-api.js @@ -0,0 +1,46 @@ +/* + * 代码生成器 + * + * @Author: 卓大 + * @Date: 2022-09-03 21:51:54 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import { getRequest, postRequest, getDownload } from '/@/lib/axios'; + +export const codeGeneratorApi = { + // 查询数据库的表 @author 卓大 + queryTableList: (param) => { + return postRequest('/support/codeGenerator/table/queryTableList', param); + }, + + // 查询表的列 @author 卓大 + getTableColumns: (table) => { + return getRequest(`/support/codeGenerator/table/getTableColumns/${table}`); + }, + + // ------------------- 配置 ------------------- + + // 获取表的配置信息 @author 卓大 + getConfig: (table) => { + return getRequest(`/support/codeGenerator/table/getConfig/${table}`); + }, + + // 更新配置信息 @author 卓大 + updateConfig: (param) => { + return postRequest('/support/codeGenerator/table/updateConfig', param); + }, + + // ------------------- 生成 ------------------- + + // 预览代码 @author 卓大 + preview: (param) => { + return postRequest('/support/codeGenerator/code/preview', param); + }, + + // 下载代码 @author 卓大 + downloadCode: (tableName) => { + return getDownload(`/support/codeGenerator/code/download/${tableName}`); + }, +}; diff --git a/smart-flow-web/src/api/support/config-api.js b/smart-flow-web/src/api/support/config-api.js new file mode 100644 index 0000000..c5e5397 --- /dev/null +++ b/smart-flow-web/src/api/support/config-api.js @@ -0,0 +1,29 @@ +/* + * 配置 + * + * @Author: 卓大 + * @Date: 2022-09-03 21:51:54 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import { postRequest, getRequest } from '/@/lib/axios'; + +export const configApi = { + // 分页查询 @author 卓大 + queryList: (param) => { + return postRequest('/support/config/query', param); + }, + // 添加配置参数 @author 卓大 + addConfig: (param) => { + return postRequest('/support/config/add', param); + }, + // 修改配置参数 @author 卓大 + updateConfig: (param) => { + return postRequest('/support/config/update', param); + }, + // 查询配置详情 @author 卓大 + queryByKey: (param) => { + return getRequest(`/support/config/queryByKey?configKey=${param}`); + }, +}; diff --git a/smart-flow-web/src/api/support/data-masking-api.js b/smart-flow-web/src/api/support/data-masking-api.js new file mode 100644 index 0000000..8921186 --- /dev/null +++ b/smart-flow-web/src/api/support/data-masking-api.js @@ -0,0 +1,17 @@ +/** + * 数据脱敏api + * + * @Author: 1024创新实验室-主任-卓大 + * @Date: 2024-07-31 21:02:37 + * @Copyright 1024创新实验室 + */ +import { getRequest } from '/@/lib/axios'; + +export const dataMaskingApi = { + /** + * 查询脱敏数据 + */ + query: () => { + return getRequest('/support/dataMasking/demo/query'); + }, +}; diff --git a/smart-flow-web/src/api/support/data-tracer-api.js b/smart-flow-web/src/api/support/data-tracer-api.js new file mode 100644 index 0000000..afa75f8 --- /dev/null +++ b/smart-flow-web/src/api/support/data-tracer-api.js @@ -0,0 +1,18 @@ +/* + * 数据变动 + * + * @Author: 卓大 + * @Date: 2022-09-03 21:51:54 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import { postRequest } from '/@/lib/axios'; + +export const dataTracerApi = { + // 分页查询业务操作日志 - @author 卓大 + queryList: (param) => { + return postRequest('/support/dataTracer/query', param); + }, + +}; diff --git a/smart-flow-web/src/api/support/dict-api.js b/smart-flow-web/src/api/support/dict-api.js new file mode 100644 index 0000000..be56224 --- /dev/null +++ b/smart-flow-web/src/api/support/dict-api.js @@ -0,0 +1,75 @@ +/* + * 字典 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-03 21:55:25 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import { postRequest, getRequest } from '/@/lib/axios'; + +export const dictApi = { + + // 获取所有字典code @author 1024创新实验室-主任-卓大 + getAllDict: () => { + return getRequest('/support/dict/getAllDict'); + }, + + // 获取全部字典数据 @author 1024创新实验室-主任-卓大 + getAllDictData: () => { + return getRequest('/support/dict/getAllDictData'); + }, + + // 分页查询 @author 1024创新实验室-主任-卓大 + queryDict: (param) => { + return postRequest('/support/dict/queryPage', param); + }, + + // 添加 @author 1024创新实验室-主任-卓大 + addDict: (param) => { + return postRequest('/support/dict/add', param); + }, + + // 更新 @author 1024创新实验室-主任-卓大 + updateDict: (param) => { + return postRequest('/support/dict/update', param); + }, + + // 字典-删除- @author 卓大 + batchDeleteDict: (dictIdList) => { + return postRequest('/support/dict/batchDelete', dictIdList); + }, + + // 字典 启用 禁用 @author 1024创新实验室-主任-卓大 + updateDisabled: (dictId) => { + return getRequest(`/support/dict/updateDisabled/${dictId}`); + }, + + // ------------- 查询字典数据 ------------- + + // 字典数据 分页查询 @author 1024创新实验室-主任-卓大 + queryDictData: (dictId) => { + return getRequest(`/support/dict/dictData/queryDictData/${dictId}`); + }, + + // 字典数据 添加 - @author 卓大 + addDictData: (param) => { + return postRequest('/support/dict/dictData/add', param); + }, + + // 字典数据 更新- @author 卓大 + updateDictData: (param) => { + return postRequest('/support/dict/dictData/update', param); + }, + + // 字典数据-删除- @author 卓大 + batchDeleteDictData: (dictDataIdList) => { + return postRequest('/support/dict/dictData/batchDelete', dictDataIdList); + }, + + // 字典数据 启用 禁用 @author 1024创新实验室-主任-卓大 + updateDictDataDisabled: (dictDataId) => { + return getRequest(`/support/dict/dictData/updateDisabled/${dictDataId}`); + }, +}; diff --git a/smart-flow-web/src/api/support/feedback-api.js b/smart-flow-web/src/api/support/feedback-api.js new file mode 100644 index 0000000..c951b8a --- /dev/null +++ b/smart-flow-web/src/api/support/feedback-api.js @@ -0,0 +1,21 @@ +/* + * 意见反馈 + * + * @Author: 1024创新实验室:开云 + * @Date: 2022-09-03 21:56:31 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import { postRequest } from '/@/lib/axios'; + +export const feedbackApi = { + // 意见反馈-新增 + addFeedback: (params) => { + return postRequest('/support/feedback/add', params); + }, + // 意见反馈-分页查询 + queryFeedback: (params) => { + return postRequest('/support/feedback/query', params); + }, +}; diff --git a/smart-flow-web/src/api/support/file-api.js b/smart-flow-web/src/api/support/file-api.js new file mode 100644 index 0000000..5145ec7 --- /dev/null +++ b/smart-flow-web/src/api/support/file-api.js @@ -0,0 +1,83 @@ +/* + * 文件上传 + * + * @Author: 1024 创新实验室 - 主任:卓大 + * @Date: 2022-09-03 21:55:25 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024 创新实验室 ( https://1024lab.net ),Since 2012 + */ +import { postRequest, getRequest, getDownload } from '/@/lib/axios'; +import { message } from 'ant-design-vue'; + +export const fileApi = { + // 文件上传 @author 卓大 + uploadUrl: '/support/file/upload', + uploadFile: (param, folder) => { + return postRequest(`/support/file/upload?folder=${folder}`, param); + }, + + /** + * 分页查询 @author 卓大 + */ + queryPage: (param) => { + return postRequest('/support/file/queryPage', param); + }, + /** + * 获取文件URL:根据fileKey @author 胡克 + */ + getUrl: (fileKey) => { + return getRequest(`/support/file/getFileUrl?fileKey=${fileKey}`); + }, + + /** + * 下载文件流(根据fileKey) @author 胡克 + */ + downLoadFile: (fileKey) => { + return getDownload('/support/file/downLoad', { fileKey }); + }, + + /** + * 下载文件到本地 @author SmartFlow + * @param {Object} file - 文件对象 {fileId, fileName, fileUrl, fileKey} + */ + downloadFile: async (file) => { + try { + // 优先使用 fileKey 通过后端接口下载(避免混合内容问题) + if (file.fileKey) { + await fileApi.downLoadFile(file.fileKey); + } else if (file.fileUrl) { + // 如果有 fileUrl 但没有 fileKey,检查是否是 HTTP 协议 + const isHttpUrl = file.fileUrl.startsWith('http://'); + + if (isHttpUrl && window.location.protocol === 'https:') { + // HTTPS 页面不能直接访问 HTTP 资源,提示用户 + message.warning('该文件使用了不安全的HTTP协议,请联系管理员配置HTTPS'); + return; + } + + // 直接下载(仅适用于 HTTPS 或同协议) + const response = await fetch(file.fileUrl); + if (!response.ok) { + throw new Error('文件下载失败'); + } + const blob = await response.blob(); + + let fileName = file.fileName || 'download'; + const url = window.URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = fileName; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + window.URL.revokeObjectURL(url); + } else { + message.info('文件暂无法下载'); + } + } catch (error) { + console.error('下载文件失败:', error); + message.error('下载文件失败'); + } + }, +}; diff --git a/smart-flow-web/src/api/support/heart-beat-api.js b/smart-flow-web/src/api/support/heart-beat-api.js new file mode 100644 index 0000000..0a1eab4 --- /dev/null +++ b/smart-flow-web/src/api/support/heart-beat-api.js @@ -0,0 +1,17 @@ +/* + * 心跳 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-03 21:55:47 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import { postRequest } from '/@/lib/axios'; + +export const heartBeatApi = { + // 分页查询 @author 卓大 + queryList: (param) => { + return postRequest('/support/heartBeat/query', param); + }, +}; diff --git a/smart-flow-web/src/api/support/help-doc-api.js b/smart-flow-web/src/api/support/help-doc-api.js new file mode 100644 index 0000000..dc4fac4 --- /dev/null +++ b/smart-flow-web/src/api/support/help-doc-api.js @@ -0,0 +1,59 @@ +/* + * 帮助文档 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-03 21:56:31 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import { postRequest, getRequest } from '/@/lib/axios'; + +export const helpDocApi = { + // 【管理】帮助文档-分页查询 @author zhuoda + query: (param) => { + return postRequest('/support/helpDoc/query', param); + }, + + //【管理】帮助文档-更新 @author zhuoda + update: (param) => { + return postRequest('/support/helpDoc/update', param); + }, + + // 【管理】帮助文档-添加 @author zhuoda + add: (param) => { + return postRequest('/support/helpDoc/add', param); + }, + + //【管理】帮助文档-删除 @author zhuoda + delete: (helpDocId) => { + return getRequest(`/support/helpDoc/delete/${helpDocId}`); + }, + + //【管理】帮助文档-获取详情 @author zhuoda + getDetail: (helpDocId) => { + return getRequest(`/support/helpDoc/getDetail/${helpDocId}`); + }, + + //【管理】帮助文档-根据关联id查询 @author zhuoda + queryHelpDocByRelationId: (relationId) => { + return getRequest(`/support/helpDoc/queryHelpDocByRelationId/${relationId}`); + }, + + //----------------------- 用户相关 -------------------------------- + + //【用户】帮助文档-查询全部 @author zhuoda + getAllHelpDocList() { + return getRequest('/support/helpDoc/user/queryAllHelpDocList'); + }, + + //【用户】帮助文档-查询全部 @author zhuoda + view(helpDocId) { + return getRequest(`/support/helpDoc/user/view/${helpDocId}`); + }, + + //【用户】帮助文档-查询 查看记录 @author zhuoda + queryViewRecord(param) { + return postRequest('/support/helpDoc/user/queryViewRecord', param); + }, +}; diff --git a/smart-flow-web/src/api/support/help-doc-catalog-api.js b/smart-flow-web/src/api/support/help-doc-catalog-api.js new file mode 100644 index 0000000..a8fd6c6 --- /dev/null +++ b/smart-flow-web/src/api/support/help-doc-catalog-api.js @@ -0,0 +1,32 @@ +/* + * 帮助文档 目录 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-03 21:56:31 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import { postRequest, getRequest } from '/@/lib/axios'; + +export const helpDocCatalogApi = { + //帮助文档目录-获取全部 @author zhuoda + getAll: () => { + return getRequest('/support/helpDoc/helpDocCatalog/getAll'); + }, + + //帮助文档目录-添加 @author zhuoda + add: (param) => { + return postRequest('/support/helpDoc/helpDocCatalog/add', param); + }, + + //帮助文档目录-更新 @author zhuoda + update: (param) => { + return postRequest('/support/helpDoc/helpDocCatalog/update', param); + }, + + //帮助文档目录-删除 @author zhuoda + delete: (helpDocCatalogId) => { + return getRequest(`/support/helpDoc/helpDocCatalog/delete/${helpDocCatalogId}`); + }, +}; diff --git a/smart-flow-web/src/api/support/job-api.js b/smart-flow-web/src/api/support/job-api.js new file mode 100644 index 0000000..2d9e060 --- /dev/null +++ b/smart-flow-web/src/api/support/job-api.js @@ -0,0 +1,42 @@ +/* + * job api + * + * @Author: huke + * @Date: 2024/06/25 + */ +import { postRequest, getRequest } from '/@/lib/axios'; + +export const jobApi = { + // 分页查询 @huke + queryJob: (param) => { + return postRequest('/support/job/query', param); + }, + // 定时任务-查询详情 @huke + queryJobInfo: (param) => { + return getRequest(`/support/job/${param}`); + }, + // 执行任务 @huke + executeJob: (param) => { + return postRequest('/support/job/execute', param); + }, + // 定时任务-新增-任务信息 @huke + addJob: (param) => { + return postRequest('/support/job/add', param); + }, + // 定时任务-更新-任务信息 @huke + updateJob: (param) => { + return postRequest('/support/job/update', param); + }, + // 定时任务-更新-开启状态 @huke + updateJobEnabled: (param) => { + return postRequest('/support/job/update/enabled', param); + }, + // 定时任务-执行记录-分页查询 @huke + queryJobLog: (param) => { + return postRequest('/support/job/log/query', param); + }, + // 定时任务-删除 @zhuoda + deleteJob: (param) => { + return getRequest(`/support/job/delete?jobId=${param}`); + }, +}; diff --git a/smart-flow-web/src/api/support/level3-protect-api.js b/smart-flow-web/src/api/support/level3-protect-api.js new file mode 100644 index 0000000..2e99db2 --- /dev/null +++ b/smart-flow-web/src/api/support/level3-protect-api.js @@ -0,0 +1,24 @@ +/** + * 三级等保 api 封装 + * + * @Author: 1024创新实验室-主任-卓大 + * @Date: 2024-07-31 21:02:37 + * @Copyright 1024创新实验室 + */ +import { postRequest, getRequest } from '/@/lib/axios'; + +export const level3ProtectApi = { + /** + * 查询 三级等保配置 @author 1024创新实验室-主任-卓大 + */ + getConfig: () => { + return getRequest('/support/protect/level3protect/getConfig'); + }, + + /** + * 更新三级等保配置 @author 1024创新实验室-主任-卓大 + */ + updateConfig: (form) => { + return postRequest('/support/protect/level3protect/updateConfig', form); + }, +}; diff --git a/smart-flow-web/src/api/support/login-fail-api.js b/smart-flow-web/src/api/support/login-fail-api.js new file mode 100644 index 0000000..42888d3 --- /dev/null +++ b/smart-flow-web/src/api/support/login-fail-api.js @@ -0,0 +1,26 @@ +/** + * 登录锁定 api 封装 + * + * @Author: 1024创新实验室-主任-卓大 + * @Date: 2023-10-17 18:02:37 + * @Copyright 1024创新实验室 + */ +import { postRequest, getRequest } from '/@/lib/axios'; + +export const loginFailApi = { + + /** + * 分页查询 @author 1024创新实验室-主任-卓大 + */ + queryPage : (param) => { + return postRequest('/support/protect/loginFail/queryPage', param); + }, + + /** + * 批量删除 @author 1024创新实验室-主任-卓大 + */ + batchDelete: (idList) => { + return postRequest('/support/protect/loginFail/batchDelete', idList); + }, + +}; diff --git a/smart-flow-web/src/api/support/login-log-api.js b/smart-flow-web/src/api/support/login-log-api.js new file mode 100644 index 0000000..d447c0b --- /dev/null +++ b/smart-flow-web/src/api/support/login-log-api.js @@ -0,0 +1,21 @@ +/* + * 登录日志 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-03 21:56:31 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import { postRequest, getRequest } from '/@/lib/axios'; + +export const loginLogApi = { + // 分页查询 @author 卓大 + queryList: (param) => { + return postRequest('/support/loginLog/page/query', param); + }, + // 分页查询当前登录人信息 @author 善逸 + queryListLogin: (param) => { + return postRequest('/support/loginLog/page/query/login', param); + }, +}; diff --git a/smart-flow-web/src/api/support/message-api.js b/smart-flow-web/src/api/support/message-api.js new file mode 100644 index 0000000..4716e54 --- /dev/null +++ b/smart-flow-web/src/api/support/message-api.js @@ -0,0 +1,31 @@ +import { getRequest, postRequest } from '/@/lib/axios'; + +export const messageApi = { + // 通知消息-分页查询 + queryMessage: (param) => { + return postRequest('/support/message/queryMyMessage', param); + }, + // 通知消息-查询未读消息数 + queryUnreadCount: () => { + return getRequest('/support/message/getUnreadCount'); + }, + // 通知消息-标记已读 + updateReadFlag: (messageId) => { + return getRequest(`/support/message/read/${messageId}`); + }, + + //通知消息-分页查询 @author 卓大 + queryAdminMessage: (param) => { + return postRequest('/message/query', param); + }, + + //通知消息-新建 @author 卓大 + sendMessages: (param) => { + return postRequest('/message/sendMessages', param); + }, + + //通知消息-删除 @author 卓大 + deleteMessage: (messageId) => { + return getRequest(`/message/delete/${messageId}`); + }, +}; diff --git a/smart-flow-web/src/api/support/operate-log-api.js b/smart-flow-web/src/api/support/operate-log-api.js new file mode 100644 index 0000000..8058942 --- /dev/null +++ b/smart-flow-web/src/api/support/operate-log-api.js @@ -0,0 +1,25 @@ +/* + * 操作日志 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-03 21:56:45 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import { postRequest, getRequest } from '/@/lib/axios'; + +export const operateLogApi = { + // 分页查询 @author 卓大 + queryList: (param) => { + return postRequest('/support/operateLog/page/query', param); + }, + // 详情 @author 卓大 + detail: (id) => { + return getRequest(`/support/operateLog/detail/${id}`); + }, + // 分页查询当前登录人信息 @author 善逸 + queryListLogin: (param) => { + return postRequest('/support/operateLog/page/query/login', param); + }, +}; diff --git a/smart-flow-web/src/api/support/reload-api.js b/smart-flow-web/src/api/support/reload-api.js new file mode 100644 index 0000000..721d563 --- /dev/null +++ b/smart-flow-web/src/api/support/reload-api.js @@ -0,0 +1,25 @@ +/* + * reload (内存热加载、钩子等) + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-03 21:57:19 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import { postRequest, getRequest } from '/@/lib/axios'; + +export const reloadApi = { + // 查询reload列表 @author 卓大 + queryList: () => { + return getRequest('/support/reload/query'); + }, + // 获取reload result @author 卓大 + queryReloadResult: (tag) => { + return getRequest(`/support/reload/result/${tag}`); + }, + // 执行reload @author 卓大 + reload: (reloadForm) => { + return postRequest('/support/reload/update', reloadForm); + }, +}; diff --git a/smart-flow-web/src/api/support/serial-number-api.js b/smart-flow-web/src/api/support/serial-number-api.js new file mode 100644 index 0000000..b9ae35a --- /dev/null +++ b/smart-flow-web/src/api/support/serial-number-api.js @@ -0,0 +1,25 @@ +/* + * 单据序列号 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-03 21:57:52 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import { postRequest, getRequest } from '/@/lib/axios'; + +export const serialNumberApi = { + // 生成单号 @author 卓大 + generate: (generateForm) => { + return postRequest('/support/serialNumber/generate', generateForm); + }, + // 获取所有单号定义 @author 卓大 + getAll: () => { + return getRequest('/support/serialNumber/all'); + }, + // 获取生成记录 @author 卓大 + queryRecord: (form) => { + return postRequest('/support/serialNumber/queryRecord', form); + }, +}; diff --git a/smart-flow-web/src/api/support/table-column-api.js b/smart-flow-web/src/api/support/table-column-api.js new file mode 100644 index 0000000..7223ab9 --- /dev/null +++ b/smart-flow-web/src/api/support/table-column-api.js @@ -0,0 +1,26 @@ +/* + * @Description:表格自定义列 + * @version: + * @Author: zhuoda + * @Date: 2022-08-17 23:32:36 + * @LastEditors: zhuoda + * @LastEditTime: 2022-08-21 + */ +import { postRequest, getRequest } from '/@/lib/axios'; + +export const tableColumnApi = { + // 修改表格列 @author zhuoda + updateTableColumn: (param) => { + return postRequest('/support/tableColumn/update', param); + }, + + // 查询表格列 @author zhuoda + getColumns: (tableId) => { + return getRequest(`/support/tableColumn/getColumns/${tableId}`); + }, + + // 删除表格列 @author zhuoda + deleteColumns: (tableId) => { + return getRequest(`/support/tableColumn/delete/${tableId}`); + }, +}; diff --git a/smart-flow-web/src/api/support/work-book-api.js b/smart-flow-web/src/api/support/work-book-api.js new file mode 100644 index 0000000..959f703 --- /dev/null +++ b/smart-flow-web/src/api/support/work-book-api.js @@ -0,0 +1,46 @@ +/* + * 工作手册管理 + * + * @Author: 1024 创新实验室 + * @Date: 2026-03-24 + * @Copyright 1024 创新实验室( https://1024lab.net ),Since 2012 + */ +import { postRequest, getRequest, getDownload } from '/@/lib/axios'; +import { message } from 'ant-design-vue'; + +export const workBookApi = { + /** + * 分页查询工作手册 + */ + queryPage: (param) => { + return postRequest('/workBook/queryPage', param); + }, + + /** + * 上传工作手册 + */ + upload: (file, folderType) => { + const formData = new FormData(); + formData.append('file', file); + formData.append('folderType', folderType); + return postRequest('/workBook/upload', formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); + }, + + /** + * 下载工作手册 + */ + download: (fileKey) => { + return getDownload('/workBook/download', { fileKey }); + }, + + /** + * 删除工作手册 + */ + deleteWorkBook: (workBookId) => { + return getRequest(`/workBook/delete/${workBookId}`); + }, +}; diff --git a/smart-flow-web/src/api/system/department-api.js b/smart-flow-web/src/api/system/department-api.js new file mode 100644 index 0000000..c34994c --- /dev/null +++ b/smart-flow-web/src/api/system/department-api.js @@ -0,0 +1,45 @@ +/* + * 部门 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-03 21:58:50 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import { getRequest, postRequest } from '/@/lib/axios'; + +export const departmentApi = { + /** + * 查询部门列表 @author 卓大 + */ + queryAllDepartment: () => { + return getRequest('/department/listAll'); + }, + + /** + * 查询部门树形列表 @author 卓大 + */ + queryDepartmentTree: () => { + return getRequest('/department/treeList'); + }, + + /** + * 添加部门 @author 卓大 + */ + addDepartment: (param) => { + return postRequest('/department/add', param); + }, + /** + * 更新部门信息 @author 卓大 + */ + updateDepartment: (param) => { + return postRequest('/department/update', param); + }, + /** + * 删除 + */ + deleteDepartment: (departmentId) => { + return getRequest(`/department/delete/${departmentId}`); + }, +}; diff --git a/smart-flow-web/src/api/system/employee-api.js b/smart-flow-web/src/api/system/employee-api.js new file mode 100644 index 0000000..f59ac42 --- /dev/null +++ b/smart-flow-web/src/api/system/employee-api.js @@ -0,0 +1,110 @@ +/* + * 员工 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-03 21:59:15 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ + +import { getRequest, postEncryptRequest, postRequest } from '/@/lib/axios'; + +export const employeeApi = { + /** + * 查询所有员工 @author 卓大 + */ + queryAll: () => { + return getRequest('/employee/queryAll'); + }, + /** + * 员工管理查询 + */ + queryEmployee: (params) => { + return postRequest('/employee/query', params); + }, + /** + * 添加员工 + */ + addEmployee: (params) => { + return postRequest('/employee/add', params); + }, + /** + * 更新员工信息 + */ + updateEmployee: (params) => { + return postRequest('/employee/update', params); + }, + /** + * 更新员工个人中心信息 + */ + updateCenter: (params) => { + return postRequest('/employee/update/center', params); + }, + /** + * 更新登录人头像 + */ + updateAvatar: (params) => { + return postRequest('/employee/update/avatar', params); + }, + /** + * 删除员工 + */ + deleteEmployee: (employeeId) => { + return getRequest(`/employee/delete/${employeeId}`); + }, + /** + * 批量删除员工 + */ + batchDeleteEmployee: (employeeIdList) => { + return postRequest('/employee/update/batch/delete', employeeIdList); + }, + /** + * 批量调整员工部门 + */ + batchUpdateDepartmentEmployee: (updateParam) => { + return postRequest('/employee/update/batch/department', updateParam); + }, + /** + * 重置员工密码 + */ + resetPassword: (employeeId) => { + return getRequest(`/employee/update/password/reset/${employeeId}`); + }, + /** + * 修改密码 + */ + updateEmployeePassword: (param) => { + return postEncryptRequest('/employee/update/password', param); + }, + /** + * 获取密码复杂度 + */ + getPasswordComplexityEnabled: () => { + return getRequest('/employee/getPasswordComplexityEnabled'); + }, + /** + * 更新员工禁用状态 + */ + updateDisabled: (employeeId) => { + return getRequest(`/employee/update/disabled/${employeeId}`); + }, + /** + * 查询员工-根据部门id + */ + queryEmployeeByDeptId: (departmentId) => { + return getRequest(`/employee/getAllEmployeeByDepartmentId/${departmentId}`); + }, + /** + * 查询员工(用于被侵权人选择) + */ + queryUser: (params) => { + return postRequest('/employee/queryUser', params); + }, + /** + * 根据警号查询员工信息 + */ + getByPoliceNo: (policeNo) => { + return getRequest(`/employee/getByPoliceNo/${policeNo}`); + }, +}; diff --git a/smart-flow-web/src/api/system/home-api.js b/smart-flow-web/src/api/system/home-api.js new file mode 100644 index 0000000..2b71ecb --- /dev/null +++ b/smart-flow-web/src/api/system/home-api.js @@ -0,0 +1,25 @@ +/* + * 首页api + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-03 21:59:39 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import { getRequest } from '/@/lib/axios'; + +export const homeApi = { + /** + * @description: 首页-金额统计(业绩、收款、订单数等) @author 卓大 + */ + homeAmountStatistics: () => { + return getRequest('/home/amount/statistics'); + }, + /** + * @description: 首页-待办信息 @author 卓大 + */ + homeWaitHandle: () => { + return getRequest('home/wait/handle'); + }, +}; diff --git a/smart-flow-web/src/api/system/home-statistic-api.js b/smart-flow-web/src/api/system/home-statistic-api.js new file mode 100644 index 0000000..0552f67 --- /dev/null +++ b/smart-flow-web/src/api/system/home-statistic-api.js @@ -0,0 +1,20 @@ +/* + * 首页统计 API + */ +import { getRequest } from '/@/lib/axios'; + +export const homeStatisticApi = { + /** + * 获取7天内每日工单量 + */ + getDailyWorkOrderCount: () => { + return getRequest('/mjwq/rights/statistic/daily-work-order'); + }, + + /** + * 获取工单状态分布 + */ + getWorkOrderStatusDistribution: () => { + return getRequest('/mjwq/rights/statistic/status-distribution'); + }, +}; diff --git a/smart-flow-web/src/api/system/login-api.js b/smart-flow-web/src/api/system/login-api.js new file mode 100644 index 0000000..7cb5909 --- /dev/null +++ b/smart-flow-web/src/api/system/login-api.js @@ -0,0 +1,54 @@ +/* + * 登录 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-03 21:59:58 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import { getRequest, postRequest } from '/@/lib/axios'; + +export const loginApi = { + /** + * 登录 @author 卓大 + */ + login: (param) => { + return postRequest('/login', param); + }, + + /** + * 退出登录 @author 卓大 + */ + logout: () => { + return getRequest('/login/logout'); + }, + + /** + * 获取验证码 @author 卓大 + */ + getCaptcha: () => { + return getRequest('/login/getCaptcha'); + }, + + /** + * 获取登录信息 @author 卓大 + */ + getLoginInfo: () => { + return getRequest('/login/getLoginInfo'); + }, + + /** + * 获取邮箱登录验证码 @author 卓大 + */ + sendLoginEmailCode: (loginName) => { + return getRequest(`/login/sendEmailCode/${loginName}`); + }, + + /** + * 获取双因子登录标识 @author 卓大 + */ + getTwoFactorLoginFlag: () => { + return getRequest('/login/getTwoFactorLoginFlag'); + }, +}; diff --git a/smart-flow-web/src/api/system/menu-api.js b/smart-flow-web/src/api/system/menu-api.js new file mode 100644 index 0000000..43c8872 --- /dev/null +++ b/smart-flow-web/src/api/system/menu-api.js @@ -0,0 +1,54 @@ +/* + * 菜单 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-03 22:00:32 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import { getRequest, postRequest } from '/@/lib/axios'; + +export const menuApi = { + /** + * 添加菜单 + */ + addMenu: (param) => { + return postRequest('/menu/add', param); + }, + + /** + * 更新菜单 + */ + updateMenu: (param) => { + return postRequest('/menu/update', param); + }, + + /** + * 批量删除菜单 + */ + batchDeleteMenu: (menuIdList) => { + return getRequest(`/menu/batchDelete?menuIdList=${menuIdList}`); + }, + + /** + * 查询所有菜单列表 + */ + queryMenu: () => { + return getRequest('/menu/query'); + }, + + /** + * 查询菜单树 + */ + queryMenuTree: (onlyMenu) => { + return getRequest(`/menu/tree?onlyMenu=${onlyMenu}`); + }, + + /** + * 获取所有请求路径 + */ + getAuthUrl: () => { + return getRequest('/menu/auth/url'); + }, +}; diff --git a/smart-flow-web/src/api/system/position-api.js b/smart-flow-web/src/api/system/position-api.js new file mode 100644 index 0000000..26f3745 --- /dev/null +++ b/smart-flow-web/src/api/system/position-api.js @@ -0,0 +1,55 @@ +/** + * 职务表 api 封装 + * + * @Author: kaiyun + * @Date: 2024-06-23 23:31:38 + * @Copyright 1024创新实验室 + */ +import { postRequest, getRequest } from '/@/lib/axios'; + +export const positionApi = { + + /** + * 分页查询 @author kaiyun + */ + queryPage : (param) => { + return postRequest('/position/queryPage', param); + }, + + /** + * 增加 @author kaiyun + */ + add: (param) => { + return postRequest('/position/add', param); + }, + + /** + * 修改 @author kaiyun + */ + update: (param) => { + return postRequest('/position/update', param); + }, + + + /** + * 删除 @author kaiyun + */ + delete: (id) => { + return getRequest(`/position/delete/${id}`); + }, + + /** + * 批量删除 @author kaiyun + */ + batchDelete: (idList) => { + return postRequest('/position/batchDelete', idList); + }, + + /** + * 查询列表 @author kaiyun + */ + queryList: () => { + return getRequest('mjwq/rights/claim-config/queryPage?pageNum=1&pageSize=100'); + }, + +}; diff --git a/smart-flow-web/src/api/system/region-api.js b/smart-flow-web/src/api/system/region-api.js new file mode 100644 index 0000000..68d1ee0 --- /dev/null +++ b/smart-flow-web/src/api/system/region-api.js @@ -0,0 +1,47 @@ +/* + * 区域管理 + * + * @Author: 1024 创新实验室 + * @Date: 2026-03-19 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024 创新实验室(https://1024lab.net),Since 2012 + */ +import { getRequest, postRequest } from '/@/lib/axios'; + +export const regionApi = { + /** + * 查询所有区域列表 + */ + listAllRegions: () => { + return getRequest('/region/listAll'); + }, + + /** + * 查询区域树形列表 + */ + getRegionTree: () => { + return getRequest('/region/treeList'); + }, + + /** + * 添加区域 + */ + addRegion: (param) => { + return postRequest('/region/add', param); + }, + + /** + * 更新区域信息 + */ + updateRegion: (param) => { + return postRequest('/region/update', param); + }, + + /** + * 删除区域 + */ + deleteRegion: (id) => { + return getRequest(`/region/delete/${id}`); + }, +}; diff --git a/smart-flow-web/src/api/system/role-api.js b/smart-flow-web/src/api/system/role-api.js new file mode 100644 index 0000000..0b62bb6 --- /dev/null +++ b/smart-flow-web/src/api/system/role-api.js @@ -0,0 +1,85 @@ +/* + * 角色 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-03 22:00:41 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import { getRequest, postRequest } from '/@/lib/axios'; + +export const roleApi = { + /** + * @description: 获取所有角色 + */ + queryAll: () => { + return getRequest('/role/getAll'); + }, + /** + * @description:添加角色 + */ + addRole: (data) => { + return postRequest('/role/add', data); + }, + /** + * @description:更新角色 + */ + updateRole: (data) => { + return postRequest('/role/update', data); + }, + /** + * @description: 删除角色 + */ + deleteRole: (roleId) => { + return getRequest(`/role/delete/${roleId}`); + }, + /** + * @description: 批量设置某角色数据范围 + */ + updateDataScope: (data) => { + return postRequest('/role/dataScope/updateRoleDataScopeList', data); + }, + /** + * @description: 获取当前系统所配置的所有数据范围 + */ + getDataScopeList: () => { + return getRequest('/dataScope/list'); + }, + /** + * @description: 获取某角色所设置的数据范围 + */ + getDataScopeByRoleId: (roleId) => { + return getRequest(`/role/dataScope/getRoleDataScopeList/${roleId}`); + }, + /** + * @description: 获取角色成员-员工列表 + */ + queryRoleEmployee: (params) => { + return postRequest('/role/employee/queryEmployee', params); + }, + /** + * @description: 从角色成员列表中移除员工 + */ + deleteEmployeeRole: (employeeId, roleId) => { + return getRequest('/role/employee/removeEmployee?employeeId=' + employeeId + '&roleId=' + roleId); + }, + /** + * @description: 从角色成员列表中批量移除员工 + */ + batchRemoveRoleEmployee: (data) => { + return postRequest('/role/employee/batchRemoveRoleEmployee', data); + }, + /** + * @description: 根据角色id获取角色员工列表(无分页) + */ + getRoleAllEmployee: (roleId) => { + return getRequest(`/role/employee/getAllEmployeeByRoleId/${roleId}`); + }, + /** + * @description: 角色成员列表中批量添加员工 + */ + batchAddRoleEmployee: (data) => { + return postRequest('/role/employee/batchAddRoleEmployee', data); + }, +}; diff --git a/smart-flow-web/src/api/system/role-menu-api.js b/smart-flow-web/src/api/system/role-menu-api.js new file mode 100644 index 0000000..37096a9 --- /dev/null +++ b/smart-flow-web/src/api/system/role-menu-api.js @@ -0,0 +1,24 @@ +/* + * 角色菜单 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-03 22:00:49 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import { getRequest, postRequest } from '/@/lib/axios'; +export const roleMenuApi = { + /** + * @description: 获取角色关联菜单权限 + */ + getRoleSelectedMenu: (roleId) => { + return getRequest(`role/menu/getRoleSelectedMenu/${roleId}`); + }, + /** + * @description: 更新角色权限 + */ + updateRoleMenu: (data) => { + return postRequest('role/menu/updateRoleMenu', data); + }, +}; diff --git a/smart-flow-web/src/assets/images/1024lab/1024lab-gzh.jpg b/smart-flow-web/src/assets/images/1024lab/1024lab-gzh.jpg new file mode 100644 index 0000000..d6b03f5 Binary files /dev/null and b/smart-flow-web/src/assets/images/1024lab/1024lab-gzh.jpg differ diff --git a/smart-flow-web/src/assets/images/1024lab/gzh.jpg b/smart-flow-web/src/assets/images/1024lab/gzh.jpg new file mode 100644 index 0000000..a55e8d9 Binary files /dev/null and b/smart-flow-web/src/assets/images/1024lab/gzh.jpg differ diff --git a/smart-flow-web/src/assets/images/1024lab/xiaozhen-gzh.jpg b/smart-flow-web/src/assets/images/1024lab/xiaozhen-gzh.jpg new file mode 100644 index 0000000..9c6319e Binary files /dev/null and b/smart-flow-web/src/assets/images/1024lab/xiaozhen-gzh.jpg differ diff --git a/smart-flow-web/src/assets/images/1024lab/zhuoda-wechat.jpg b/smart-flow-web/src/assets/images/1024lab/zhuoda-wechat.jpg new file mode 100644 index 0000000..9e11778 Binary files /dev/null and b/smart-flow-web/src/assets/images/1024lab/zhuoda-wechat.jpg differ diff --git a/smart-flow-web/src/assets/images/login/ali-icon.png b/smart-flow-web/src/assets/images/login/ali-icon.png new file mode 100644 index 0000000..c81d647 Binary files /dev/null and b/smart-flow-web/src/assets/images/login/ali-icon.png differ diff --git a/smart-flow-web/src/assets/images/login/blue-login-bg.png b/smart-flow-web/src/assets/images/login/blue-login-bg.png new file mode 100644 index 0000000..706cb9c Binary files /dev/null and b/smart-flow-web/src/assets/images/login/blue-login-bg.png differ diff --git a/smart-flow-web/src/assets/images/login/douyin-icon.png b/smart-flow-web/src/assets/images/login/douyin-icon.png new file mode 100644 index 0000000..82525fe Binary files /dev/null and b/smart-flow-web/src/assets/images/login/douyin-icon.png differ diff --git a/smart-flow-web/src/assets/images/login/feishu-icon.png b/smart-flow-web/src/assets/images/login/feishu-icon.png new file mode 100644 index 0000000..dc8468c Binary files /dev/null and b/smart-flow-web/src/assets/images/login/feishu-icon.png differ diff --git a/smart-flow-web/src/assets/images/login/google-icon.png b/smart-flow-web/src/assets/images/login/google-icon.png new file mode 100644 index 0000000..465069c Binary files /dev/null and b/smart-flow-web/src/assets/images/login/google-icon.png differ diff --git a/smart-flow-web/src/assets/images/login/left-bg1.png b/smart-flow-web/src/assets/images/login/left-bg1.png new file mode 100644 index 0000000..83084aa Binary files /dev/null and b/smart-flow-web/src/assets/images/login/left-bg1.png differ diff --git a/smart-flow-web/src/assets/images/login/left-bg2.png b/smart-flow-web/src/assets/images/login/left-bg2.png new file mode 100644 index 0000000..ba4eaec Binary files /dev/null and b/smart-flow-web/src/assets/images/login/left-bg2.png differ diff --git a/smart-flow-web/src/assets/images/login/login-bg.png b/smart-flow-web/src/assets/images/login/login-bg.png new file mode 100644 index 0000000..16cf505 Binary files /dev/null and b/smart-flow-web/src/assets/images/login/login-bg.png differ diff --git a/smart-flow-web/src/assets/images/login/login-bg2.png b/smart-flow-web/src/assets/images/login/login-bg2.png new file mode 100644 index 0000000..fa33669 Binary files /dev/null and b/smart-flow-web/src/assets/images/login/login-bg2.png differ diff --git a/smart-flow-web/src/assets/images/login/login-bg3.png b/smart-flow-web/src/assets/images/login/login-bg3.png new file mode 100644 index 0000000..5533b14 Binary files /dev/null and b/smart-flow-web/src/assets/images/login/login-bg3.png differ diff --git a/smart-flow-web/src/assets/images/login/login-bg4 .png b/smart-flow-web/src/assets/images/login/login-bg4 .png new file mode 100644 index 0000000..cb51b61 Binary files /dev/null and b/smart-flow-web/src/assets/images/login/login-bg4 .png differ diff --git a/smart-flow-web/src/assets/images/login/login-form-open-eyes-close.png b/smart-flow-web/src/assets/images/login/login-form-open-eyes-close.png new file mode 100644 index 0000000..89f8530 Binary files /dev/null and b/smart-flow-web/src/assets/images/login/login-form-open-eyes-close.png differ diff --git a/smart-flow-web/src/assets/images/login/login-form-open-eyes.png b/smart-flow-web/src/assets/images/login/login-form-open-eyes.png new file mode 100644 index 0000000..f8eb3d7 Binary files /dev/null and b/smart-flow-web/src/assets/images/login/login-form-open-eyes.png differ diff --git a/smart-flow-web/src/assets/images/login/login-qr.png b/smart-flow-web/src/assets/images/login/login-qr.png new file mode 100644 index 0000000..9f00281 Binary files /dev/null and b/smart-flow-web/src/assets/images/login/login-qr.png differ diff --git a/smart-flow-web/src/assets/images/login/login.gif b/smart-flow-web/src/assets/images/login/login.gif new file mode 100644 index 0000000..15cb607 Binary files /dev/null and b/smart-flow-web/src/assets/images/login/login.gif differ diff --git a/smart-flow-web/src/assets/images/login/qq-icon.png b/smart-flow-web/src/assets/images/login/qq-icon.png new file mode 100644 index 0000000..2b1e45e Binary files /dev/null and b/smart-flow-web/src/assets/images/login/qq-icon.png differ diff --git a/smart-flow-web/src/assets/images/login/wechat-icon.png b/smart-flow-web/src/assets/images/login/wechat-icon.png new file mode 100644 index 0000000..644137b Binary files /dev/null and b/smart-flow-web/src/assets/images/login/wechat-icon.png differ diff --git a/smart-flow-web/src/assets/images/login/weibo-icon.png b/smart-flow-web/src/assets/images/login/weibo-icon.png new file mode 100644 index 0000000..e0ff440 Binary files /dev/null and b/smart-flow-web/src/assets/images/login/weibo-icon.png differ diff --git a/smart-flow-web/src/assets/images/logo/logo-min.png b/smart-flow-web/src/assets/images/logo/logo-min.png new file mode 100644 index 0000000..0691a0e Binary files /dev/null and b/smart-flow-web/src/assets/images/logo/logo-min.png differ diff --git a/smart-flow-web/src/assets/images/logo/logo.png b/smart-flow-web/src/assets/images/logo/logo.png new file mode 100644 index 0000000..442fb8a Binary files /dev/null and b/smart-flow-web/src/assets/images/logo/logo.png differ diff --git a/smart-flow-web/src/assets/images/logo/smart-admin-logo-white.png b/smart-flow-web/src/assets/images/logo/smart-admin-logo-white.png new file mode 100644 index 0000000..7efd997 Binary files /dev/null and b/smart-flow-web/src/assets/images/logo/smart-admin-logo-white.png differ diff --git a/smart-flow-web/src/assets/images/logo/smart-admin-logo.png b/smart-flow-web/src/assets/images/logo/smart-admin-logo.png new file mode 100644 index 0000000..48a7b16 Binary files /dev/null and b/smart-flow-web/src/assets/images/logo/smart-admin-logo.png differ diff --git a/smart-flow-web/src/assets/images/logo/szdc-logo.png b/smart-flow-web/src/assets/images/logo/szdc-logo.png new file mode 100644 index 0000000..f42f92a Binary files /dev/null and b/smart-flow-web/src/assets/images/logo/szdc-logo.png differ diff --git a/smart-flow-web/src/assets/images/nav/active_bg2.svg b/smart-flow-web/src/assets/images/nav/active_bg2.svg new file mode 100644 index 0000000..60a4b36 --- /dev/null +++ b/smart-flow-web/src/assets/images/nav/active_bg2.svg @@ -0,0 +1,3 @@ + + + diff --git a/smart-flow-web/src/assets/images/nav/active_bg2_default.svg b/smart-flow-web/src/assets/images/nav/active_bg2_default.svg new file mode 100644 index 0000000..5a55527 --- /dev/null +++ b/smart-flow-web/src/assets/images/nav/active_bg2_default.svg @@ -0,0 +1,3 @@ + + + diff --git a/smart-flow-web/src/assets/images/notice/edit_icon.png b/smart-flow-web/src/assets/images/notice/edit_icon.png new file mode 100644 index 0000000..6cb12f0 Binary files /dev/null and b/smart-flow-web/src/assets/images/notice/edit_icon.png differ diff --git a/smart-flow-web/src/assets/images/notice/file_icon.png b/smart-flow-web/src/assets/images/notice/file_icon.png new file mode 100644 index 0000000..70c88d8 Binary files /dev/null and b/smart-flow-web/src/assets/images/notice/file_icon.png differ diff --git a/smart-flow-web/src/components/business/category-tree-select/index.vue b/smart-flow-web/src/components/business/category-tree-select/index.vue new file mode 100644 index 0000000..0a7fdfe --- /dev/null +++ b/smart-flow-web/src/components/business/category-tree-select/index.vue @@ -0,0 +1,86 @@ + + + + diff --git a/smart-flow-web/src/components/business/oa/enterprise-bank-select/index.vue b/smart-flow-web/src/components/business/oa/enterprise-bank-select/index.vue new file mode 100644 index 0000000..3fe98ad --- /dev/null +++ b/smart-flow-web/src/components/business/oa/enterprise-bank-select/index.vue @@ -0,0 +1,109 @@ + + + + diff --git a/smart-flow-web/src/components/business/oa/enterprise-invoice-select/index.vue b/smart-flow-web/src/components/business/oa/enterprise-invoice-select/index.vue new file mode 100644 index 0000000..7d6c4ce --- /dev/null +++ b/smart-flow-web/src/components/business/oa/enterprise-invoice-select/index.vue @@ -0,0 +1,108 @@ + + + + diff --git a/smart-flow-web/src/components/business/oa/enterprise-select/index.vue b/smart-flow-web/src/components/business/oa/enterprise-select/index.vue new file mode 100644 index 0000000..97bf474 --- /dev/null +++ b/smart-flow-web/src/components/business/oa/enterprise-select/index.vue @@ -0,0 +1,85 @@ + + + + diff --git a/smart-flow-web/src/components/business/rights/approver-select-modal/index.vue b/smart-flow-web/src/components/business/rights/approver-select-modal/index.vue new file mode 100644 index 0000000..684ec2f --- /dev/null +++ b/smart-flow-web/src/components/business/rights/approver-select-modal/index.vue @@ -0,0 +1,135 @@ + + + + + + diff --git a/smart-flow-web/src/components/business/rights/evaluation-modal/index.vue b/smart-flow-web/src/components/business/rights/evaluation-modal/index.vue new file mode 100644 index 0000000..17f44a1 --- /dev/null +++ b/smart-flow-web/src/components/business/rights/evaluation-modal/index.vue @@ -0,0 +1,128 @@ + + + + + + diff --git a/smart-flow-web/src/components/business/rights/victim-select-modal/index.vue b/smart-flow-web/src/components/business/rights/victim-select-modal/index.vue new file mode 100644 index 0000000..5880d73 --- /dev/null +++ b/smart-flow-web/src/components/business/rights/victim-select-modal/index.vue @@ -0,0 +1,223 @@ + + + diff --git a/smart-flow-web/src/components/framework/area-cascader/index.vue b/smart-flow-web/src/components/framework/area-cascader/index.vue new file mode 100644 index 0000000..43e68fe --- /dev/null +++ b/smart-flow-web/src/components/framework/area-cascader/index.vue @@ -0,0 +1,87 @@ + + + + + diff --git a/smart-flow-web/src/components/framework/area-cascader/province-city-district.js b/smart-flow-web/src/components/framework/area-cascader/province-city-district.js new file mode 100644 index 0000000..5d8cbef --- /dev/null +++ b/smart-flow-web/src/components/framework/area-cascader/province-city-district.js @@ -0,0 +1,17546 @@ +export const PROVINCE_CITY_DISTRICT = [ + { + children: [ + { + children: [ + { + children: [], + label: '东城区', + value: 110101, + }, + { + children: [], + label: '西城区', + value: 110102, + }, + { + children: [], + label: '朝阳区', + value: 110105, + }, + { + children: [], + label: '丰台区', + value: 110106, + }, + { + children: [], + label: '石景山区', + value: 110107, + }, + { + children: [], + label: '海淀区', + value: 110108, + }, + { + children: [], + label: '门头沟区', + value: 110109, + }, + { + children: [], + label: '房山区', + value: 110111, + }, + { + children: [], + label: '通州区', + value: 110112, + }, + { + children: [], + label: '顺义区', + value: 110113, + }, + { + children: [], + label: '昌平区', + value: 110114, + }, + { + children: [], + label: '大兴区', + value: 110115, + }, + { + children: [], + label: '怀柔区', + value: 110116, + }, + { + children: [], + label: '平谷区', + value: 110117, + }, + { + children: [], + label: '密云区', + value: 110118, + }, + { + children: [], + label: '延庆区', + value: 110119, + }, + ], + label: '北京市', + value: 110100, + }, + ], + label: '北京市', + value: 110000, + }, + { + children: [ + { + children: [ + { + children: [], + label: '和平区', + value: 120101, + }, + { + children: [], + label: '河东区', + value: 120102, + }, + { + children: [], + label: '河西区', + value: 120103, + }, + { + children: [], + label: '南开区', + value: 120104, + }, + { + children: [], + label: '河北区', + value: 120105, + }, + { + children: [], + label: '红桥区', + value: 120106, + }, + { + children: [], + label: '东丽区', + value: 120110, + }, + { + children: [], + label: '西青区', + value: 120111, + }, + { + children: [], + label: '津南区', + value: 120112, + }, + { + children: [], + label: '北辰区', + value: 120113, + }, + { + children: [], + label: '武清区', + value: 120114, + }, + { + children: [], + label: '宝坻区', + value: 120115, + }, + { + children: [], + label: '滨海新区', + value: 120116, + }, + { + children: [], + label: '宁河区', + value: 120117, + }, + { + children: [], + label: '静海区', + value: 120118, + }, + { + children: [], + label: '蓟州区', + value: 120119, + }, + ], + label: '天津市', + value: 120100, + }, + ], + label: '天津市', + value: 120000, + }, + { + children: [ + { + children: [ + { + children: [], + label: '长安区', + value: 130102, + }, + { + children: [], + label: '桥西区', + value: 130104, + }, + { + children: [], + label: '新华区', + value: 130105, + }, + { + children: [], + label: '井陉矿区', + value: 130107, + }, + { + children: [], + label: '裕华区', + value: 130108, + }, + { + children: [], + label: '藁城区', + value: 130109, + }, + { + children: [], + label: '鹿泉区', + value: 130110, + }, + { + children: [], + label: '栾城区', + value: 130111, + }, + { + children: [], + label: '井陉县', + value: 130121, + }, + { + children: [], + label: '正定县', + value: 130123, + }, + { + children: [], + label: '行唐县', + value: 130125, + }, + { + children: [], + label: '灵寿县', + value: 130126, + }, + { + children: [], + label: '高邑县', + value: 130127, + }, + { + children: [], + label: '深泽县', + value: 130128, + }, + { + children: [], + label: '赞皇县', + value: 130129, + }, + { + children: [], + label: '无极县', + value: 130130, + }, + { + children: [], + label: '平山县', + value: 130131, + }, + { + children: [], + label: '元氏县', + value: 130132, + }, + { + children: [], + label: '赵县', + value: 130133, + }, + { + children: [], + label: '辛集市', + value: 130181, + }, + { + children: [], + label: '晋州市', + value: 130183, + }, + { + children: [], + label: '新乐市', + value: 130184, + }, + ], + label: '石家庄市', + value: 130100, + }, + { + children: [ + { + children: [], + label: '路南区', + value: 130202, + }, + { + children: [], + label: '路北区', + value: 130203, + }, + { + children: [], + label: '古冶区', + value: 130204, + }, + { + children: [], + label: '开平区', + value: 130205, + }, + { + children: [], + label: '丰南区', + value: 130207, + }, + { + children: [], + label: '丰润区', + value: 130208, + }, + { + children: [], + label: '曹妃甸区', + value: 130209, + }, + { + children: [], + label: '滦南县', + value: 130224, + }, + { + children: [], + label: '乐亭县', + value: 130225, + }, + { + children: [], + label: '迁西县', + value: 130227, + }, + { + children: [], + label: '玉田县', + value: 130229, + }, + { + children: [], + label: '遵化市', + value: 130281, + }, + { + children: [], + label: '迁安市', + value: 130283, + }, + { + children: [], + label: '滦州市', + value: 130284, + }, + ], + label: '唐山市', + value: 130200, + }, + { + children: [ + { + children: [], + label: '海港区', + value: 130302, + }, + { + children: [], + label: '山海关区', + value: 130303, + }, + { + children: [], + label: '北戴河区', + value: 130304, + }, + { + children: [], + label: '抚宁区', + value: 130306, + }, + { + children: [], + label: '青龙满族自治县', + value: 130321, + }, + { + children: [], + label: '昌黎县', + value: 130322, + }, + { + children: [], + label: '卢龙县', + value: 130324, + }, + ], + label: '秦皇岛市', + value: 130300, + }, + { + children: [ + { + children: [], + label: '邯山区', + value: 130402, + }, + { + children: [], + label: '丛台区', + value: 130403, + }, + { + children: [], + label: '复兴区', + value: 130404, + }, + { + children: [], + label: '峰峰矿区', + value: 130406, + }, + { + children: [], + label: '肥乡区', + value: 130407, + }, + { + children: [], + label: '永年区', + value: 130408, + }, + { + children: [], + label: '临漳县', + value: 130423, + }, + { + children: [], + label: '成安县', + value: 130424, + }, + { + children: [], + label: '大名县', + value: 130425, + }, + { + children: [], + label: '涉县', + value: 130426, + }, + { + children: [], + label: '磁县', + value: 130427, + }, + { + children: [], + label: '邱县', + value: 130430, + }, + { + children: [], + label: '鸡泽县', + value: 130431, + }, + { + children: [], + label: '广平县', + value: 130432, + }, + { + children: [], + label: '馆陶县', + value: 130433, + }, + { + children: [], + label: '魏县', + value: 130434, + }, + { + children: [], + label: '曲周县', + value: 130435, + }, + { + children: [], + label: '武安市', + value: 130481, + }, + ], + label: '邯郸市', + value: 130400, + }, + { + children: [ + { + children: [], + label: '桥东区', + value: 130502, + }, + { + children: [], + label: '桥西区', + value: 130503, + }, + { + children: [], + label: '邢台县', + value: 130521, + }, + { + children: [], + label: '临城县', + value: 130522, + }, + { + children: [], + label: '内丘县', + value: 130523, + }, + { + children: [], + label: '柏乡县', + value: 130524, + }, + { + children: [], + label: '隆尧县', + value: 130525, + }, + { + children: [], + label: '任县', + value: 130526, + }, + { + children: [], + label: '南和县', + value: 130527, + }, + { + children: [], + label: '宁晋县', + value: 130528, + }, + { + children: [], + label: '巨鹿县', + value: 130529, + }, + { + children: [], + label: '新河县', + value: 130530, + }, + { + children: [], + label: '广宗县', + value: 130531, + }, + { + children: [], + label: '平乡县', + value: 130532, + }, + { + children: [], + label: '威县', + value: 130533, + }, + { + children: [], + label: '清河县', + value: 130534, + }, + { + children: [], + label: '临西县', + value: 130535, + }, + { + children: [], + label: '南宫市', + value: 130581, + }, + { + children: [], + label: '沙河市', + value: 130582, + }, + ], + label: '邢台市', + value: 130500, + }, + { + children: [ + { + children: [], + label: '竞秀区', + value: 130602, + }, + { + children: [], + label: '莲池区', + value: 130606, + }, + { + children: [], + label: '满城区', + value: 130607, + }, + { + children: [], + label: '清苑区', + value: 130608, + }, + { + children: [], + label: '徐水区', + value: 130609, + }, + { + children: [], + label: '涞水县', + value: 130623, + }, + { + children: [], + label: '阜平县', + value: 130624, + }, + { + children: [], + label: '定兴县', + value: 130626, + }, + { + children: [], + label: '唐县', + value: 130627, + }, + { + children: [], + label: '高阳县', + value: 130628, + }, + { + children: [], + label: '容城县', + value: 130629, + }, + { + children: [], + label: '涞源县', + value: 130630, + }, + { + children: [], + label: '望都县', + value: 130631, + }, + { + children: [], + label: '安新县', + value: 130632, + }, + { + children: [], + label: '易县', + value: 130633, + }, + { + children: [], + label: '曲阳县', + value: 130634, + }, + { + children: [], + label: '蠡县', + value: 130635, + }, + { + children: [], + label: '顺平县', + value: 130636, + }, + { + children: [], + label: '博野县', + value: 130637, + }, + { + children: [], + label: '雄县', + value: 130638, + }, + { + children: [], + label: '涿州市', + value: 130681, + }, + { + children: [], + label: '定州市', + value: 130682, + }, + { + children: [], + label: '安国市', + value: 130683, + }, + { + children: [], + label: '高碑店市', + value: 130684, + }, + ], + label: '保定市', + value: 130600, + }, + { + children: [ + { + children: [], + label: '桥东区', + value: 130702, + }, + { + children: [], + label: '桥西区', + value: 130703, + }, + { + children: [], + label: '宣化区', + value: 130705, + }, + { + children: [], + label: '下花园区', + value: 130706, + }, + { + children: [], + label: '万全区', + value: 130708, + }, + { + children: [], + label: '崇礼区', + value: 130709, + }, + { + children: [], + label: '张北县', + value: 130722, + }, + { + children: [], + label: '康保县', + value: 130723, + }, + { + children: [], + label: '沽源县', + value: 130724, + }, + { + children: [], + label: '尚义县', + value: 130725, + }, + { + children: [], + label: '蔚县', + value: 130726, + }, + { + children: [], + label: '阳原县', + value: 130727, + }, + { + children: [], + label: '怀安县', + value: 130728, + }, + { + children: [], + label: '怀来县', + value: 130730, + }, + { + children: [], + label: '涿鹿县', + value: 130731, + }, + { + children: [], + label: '赤城县', + value: 130732, + }, + ], + label: '张家口市', + value: 130700, + }, + { + children: [ + { + children: [], + label: '双桥区', + value: 130802, + }, + { + children: [], + label: '双滦区', + value: 130803, + }, + { + children: [], + label: '鹰手营子矿区', + value: 130804, + }, + { + children: [], + label: '承德县', + value: 130821, + }, + { + children: [], + label: '兴隆县', + value: 130822, + }, + { + children: [], + label: '滦平县', + value: 130824, + }, + { + children: [], + label: '隆化县', + value: 130825, + }, + { + children: [], + label: '丰宁满族自治县', + value: 130826, + }, + { + children: [], + label: '宽城满族自治县', + value: 130827, + }, + { + children: [], + label: '围场满族蒙古族自治县', + value: 130828, + }, + { + children: [], + label: '平泉市', + value: 130881, + }, + ], + label: '承德市', + value: 130800, + }, + { + children: [ + { + children: [], + label: '新华区', + value: 130902, + }, + { + children: [], + label: '运河区', + value: 130903, + }, + { + children: [], + label: '沧县', + value: 130921, + }, + { + children: [], + label: '青县', + value: 130922, + }, + { + children: [], + label: '东光县', + value: 130923, + }, + { + children: [], + label: '海兴县', + value: 130924, + }, + { + children: [], + label: '盐山县', + value: 130925, + }, + { + children: [], + label: '肃宁县', + value: 130926, + }, + { + children: [], + label: '南皮县', + value: 130927, + }, + { + children: [], + label: '吴桥县', + value: 130928, + }, + { + children: [], + label: '献县', + value: 130929, + }, + { + children: [], + label: '孟村回族自治县', + value: 130930, + }, + { + children: [], + label: '泊头市', + value: 130981, + }, + { + children: [], + label: '任丘市', + value: 130982, + }, + { + children: [], + label: '黄骅市', + value: 130983, + }, + { + children: [], + label: '河间市', + value: 130984, + }, + ], + label: '沧州市', + value: 130900, + }, + { + children: [ + { + children: [], + label: '安次区', + value: 131002, + }, + { + children: [], + label: '广阳区', + value: 131003, + }, + { + children: [], + label: '固安县', + value: 131022, + }, + { + children: [], + label: '永清县', + value: 131023, + }, + { + children: [], + label: '香河县', + value: 131024, + }, + { + children: [], + label: '大城县', + value: 131025, + }, + { + children: [], + label: '文安县', + value: 131026, + }, + { + children: [], + label: '大厂回族自治县', + value: 131028, + }, + { + children: [], + label: '霸州市', + value: 131081, + }, + { + children: [], + label: '三河市', + value: 131082, + }, + ], + label: '廊坊市', + value: 131000, + }, + { + children: [ + { + children: [], + label: '桃城区', + value: 131102, + }, + { + children: [], + label: '冀州区', + value: 131103, + }, + { + children: [], + label: '枣强县', + value: 131121, + }, + { + children: [], + label: '武邑县', + value: 131122, + }, + { + children: [], + label: '武强县', + value: 131123, + }, + { + children: [], + label: '饶阳县', + value: 131124, + }, + { + children: [], + label: '安平县', + value: 131125, + }, + { + children: [], + label: '故城县', + value: 131126, + }, + { + children: [], + label: '景县', + value: 131127, + }, + { + children: [], + label: '阜城县', + value: 131128, + }, + { + children: [], + label: '深州市', + value: 131182, + }, + ], + label: '衡水市', + value: 131100, + }, + ], + label: '河北省', + value: 130000, + }, + { + children: [ + { + children: [ + { + children: [], + label: '小店区', + value: 140105, + }, + { + children: [], + label: '迎泽区', + value: 140106, + }, + { + children: [], + label: '杏花岭区', + value: 140107, + }, + { + children: [], + label: '尖草坪区', + value: 140108, + }, + { + children: [], + label: '万柏林区', + value: 140109, + }, + { + children: [], + label: '晋源区', + value: 140110, + }, + { + children: [], + label: '清徐县', + value: 140121, + }, + { + children: [], + label: '阳曲县', + value: 140122, + }, + { + children: [], + label: '娄烦县', + value: 140123, + }, + { + children: [], + label: '古交市', + value: 140181, + }, + ], + label: '太原市', + value: 140100, + }, + { + children: [ + { + children: [], + label: '新荣区', + value: 140212, + }, + { + children: [], + label: '平城区', + value: 140213, + }, + { + children: [], + label: '云冈区', + value: 140214, + }, + { + children: [], + label: '云州区', + value: 140215, + }, + { + children: [], + label: '阳高县', + value: 140221, + }, + { + children: [], + label: '天镇县', + value: 140222, + }, + { + children: [], + label: '广灵县', + value: 140223, + }, + { + children: [], + label: '灵丘县', + value: 140224, + }, + { + children: [], + label: '浑源县', + value: 140225, + }, + { + children: [], + label: '左云县', + value: 140226, + }, + ], + label: '大同市', + value: 140200, + }, + { + children: [ + { + children: [], + label: '城区', + value: 140302, + }, + { + children: [], + label: '矿区', + value: 140303, + }, + { + children: [], + label: '郊区', + value: 140311, + }, + { + children: [], + label: '平定县', + value: 140321, + }, + { + children: [], + label: '盂县', + value: 140322, + }, + ], + label: '阳泉市', + value: 140300, + }, + { + children: [ + { + children: [], + label: '潞州区', + value: 140403, + }, + { + children: [], + label: '上党区', + value: 140404, + }, + { + children: [], + label: '屯留区', + value: 140405, + }, + { + children: [], + label: '潞城区', + value: 140406, + }, + { + children: [], + label: '襄垣县', + value: 140423, + }, + { + children: [], + label: '平顺县', + value: 140425, + }, + { + children: [], + label: '黎城县', + value: 140426, + }, + { + children: [], + label: '壶关县', + value: 140427, + }, + { + children: [], + label: '长子县', + value: 140428, + }, + { + children: [], + label: '武乡县', + value: 140429, + }, + { + children: [], + label: '沁县', + value: 140430, + }, + { + children: [], + label: '沁源县', + value: 140431, + }, + ], + label: '长治市', + value: 140400, + }, + { + children: [ + { + children: [], + label: '城区', + value: 140502, + }, + { + children: [], + label: '沁水县', + value: 140521, + }, + { + children: [], + label: '阳城县', + value: 140522, + }, + { + children: [], + label: '陵川县', + value: 140524, + }, + { + children: [], + label: '泽州县', + value: 140525, + }, + { + children: [], + label: '高平市', + value: 140581, + }, + ], + label: '晋城市', + value: 140500, + }, + { + children: [ + { + children: [], + label: '朔城区', + value: 140602, + }, + { + children: [], + label: '平鲁区', + value: 140603, + }, + { + children: [], + label: '山阴县', + value: 140621, + }, + { + children: [], + label: '应县', + value: 140622, + }, + { + children: [], + label: '右玉县', + value: 140623, + }, + { + children: [], + label: '怀仁市', + value: 140681, + }, + ], + label: '朔州市', + value: 140600, + }, + { + children: [ + { + children: [], + label: '榆次区', + value: 140702, + }, + { + children: [], + label: '榆社县', + value: 140721, + }, + { + children: [], + label: '左权县', + value: 140722, + }, + { + children: [], + label: '和顺县', + value: 140723, + }, + { + children: [], + label: '昔阳县', + value: 140724, + }, + { + children: [], + label: '寿阳县', + value: 140725, + }, + { + children: [], + label: '太谷县', + value: 140726, + }, + { + children: [], + label: '祁县', + value: 140727, + }, + { + children: [], + label: '平遥县', + value: 140728, + }, + { + children: [], + label: '灵石县', + value: 140729, + }, + { + children: [], + label: '介休市', + value: 140781, + }, + ], + label: '晋中市', + value: 140700, + }, + { + children: [ + { + children: [], + label: '盐湖区', + value: 140802, + }, + { + children: [], + label: '临猗县', + value: 140821, + }, + { + children: [], + label: '万荣县', + value: 140822, + }, + { + children: [], + label: '闻喜县', + value: 140823, + }, + { + children: [], + label: '稷山县', + value: 140824, + }, + { + children: [], + label: '新绛县', + value: 140825, + }, + { + children: [], + label: '绛县', + value: 140826, + }, + { + children: [], + label: '垣曲县', + value: 140827, + }, + { + children: [], + label: '夏县', + value: 140828, + }, + { + children: [], + label: '平陆县', + value: 140829, + }, + { + children: [], + label: '芮城县', + value: 140830, + }, + { + children: [], + label: '永济市', + value: 140881, + }, + { + children: [], + label: '河津市', + value: 140882, + }, + ], + label: '运城市', + value: 140800, + }, + { + children: [ + { + children: [], + label: '忻府区', + value: 140902, + }, + { + children: [], + label: '定襄县', + value: 140921, + }, + { + children: [], + label: '五台县', + value: 140922, + }, + { + children: [], + label: '代县', + value: 140923, + }, + { + children: [], + label: '繁峙县', + value: 140924, + }, + { + children: [], + label: '宁武县', + value: 140925, + }, + { + children: [], + label: '静乐县', + value: 140926, + }, + { + children: [], + label: '神池县', + value: 140927, + }, + { + children: [], + label: '五寨县', + value: 140928, + }, + { + children: [], + label: '岢岚县', + value: 140929, + }, + { + children: [], + label: '河曲县', + value: 140930, + }, + { + children: [], + label: '保德县', + value: 140931, + }, + { + children: [], + label: '偏关县', + value: 140932, + }, + { + children: [], + label: '原平市', + value: 140981, + }, + ], + label: '忻州市', + value: 140900, + }, + { + children: [ + { + children: [], + label: '尧都区', + value: 141002, + }, + { + children: [], + label: '曲沃县', + value: 141021, + }, + { + children: [], + label: '翼城县', + value: 141022, + }, + { + children: [], + label: '襄汾县', + value: 141023, + }, + { + children: [], + label: '洪洞县', + value: 141024, + }, + { + children: [], + label: '古县', + value: 141025, + }, + { + children: [], + label: '安泽县', + value: 141026, + }, + { + children: [], + label: '浮山县', + value: 141027, + }, + { + children: [], + label: '吉县', + value: 141028, + }, + { + children: [], + label: '乡宁县', + value: 141029, + }, + { + children: [], + label: '大宁县', + value: 141030, + }, + { + children: [], + label: '隰县', + value: 141031, + }, + { + children: [], + label: '永和县', + value: 141032, + }, + { + children: [], + label: '蒲县', + value: 141033, + }, + { + children: [], + label: '汾西县', + value: 141034, + }, + { + children: [], + label: '侯马市', + value: 141081, + }, + { + children: [], + label: '霍州市', + value: 141082, + }, + ], + label: '临汾市', + value: 141000, + }, + { + children: [ + { + children: [], + label: '离石区', + value: 141102, + }, + { + children: [], + label: '文水县', + value: 141121, + }, + { + children: [], + label: '交城县', + value: 141122, + }, + { + children: [], + label: '兴县', + value: 141123, + }, + { + children: [], + label: '临县', + value: 141124, + }, + { + children: [], + label: '柳林县', + value: 141125, + }, + { + children: [], + label: '石楼县', + value: 141126, + }, + { + children: [], + label: '岚县', + value: 141127, + }, + { + children: [], + label: '方山县', + value: 141128, + }, + { + children: [], + label: '中阳县', + value: 141129, + }, + { + children: [], + label: '交口县', + value: 141130, + }, + { + children: [], + label: '孝义市', + value: 141181, + }, + { + children: [], + label: '汾阳市', + value: 141182, + }, + ], + label: '吕梁市', + value: 141100, + }, + ], + label: '山西省', + value: 140000, + }, + { + children: [ + { + children: [ + { + children: [], + label: '新城区', + value: 150102, + }, + { + children: [], + label: '回民区', + value: 150103, + }, + { + children: [], + label: '玉泉区', + value: 150104, + }, + { + children: [], + label: '赛罕区', + value: 150105, + }, + { + children: [], + label: '土默特左旗', + value: 150121, + }, + { + children: [], + label: '托克托县', + value: 150122, + }, + { + children: [], + label: '和林格尔县', + value: 150123, + }, + { + children: [], + label: '清水河县', + value: 150124, + }, + { + children: [], + label: '武川县', + value: 150125, + }, + ], + label: '呼和浩特市', + value: 150100, + }, + { + children: [ + { + children: [], + label: '东河区', + value: 150202, + }, + { + children: [], + label: '昆都仑区', + value: 150203, + }, + { + children: [], + label: '青山区', + value: 150204, + }, + { + children: [], + label: '石拐区', + value: 150205, + }, + { + children: [], + label: '白云鄂博矿区', + value: 150206, + }, + { + children: [], + label: '九原区', + value: 150207, + }, + { + children: [], + label: '土默特右旗', + value: 150221, + }, + { + children: [], + label: '固阳县', + value: 150222, + }, + { + children: [], + label: '达尔罕茂明安联合旗', + value: 150223, + }, + ], + label: '包头市', + value: 150200, + }, + { + children: [ + { + children: [], + label: '海勃湾区', + value: 150302, + }, + { + children: [], + label: '海南区', + value: 150303, + }, + { + children: [], + label: '乌达区', + value: 150304, + }, + ], + label: '乌海市', + value: 150300, + }, + { + children: [ + { + children: [], + label: '红山区', + value: 150402, + }, + { + children: [], + label: '元宝山区', + value: 150403, + }, + { + children: [], + label: '松山区', + value: 150404, + }, + { + children: [], + label: '阿鲁科尔沁旗', + value: 150421, + }, + { + children: [], + label: '巴林左旗', + value: 150422, + }, + { + children: [], + label: '巴林右旗', + value: 150423, + }, + { + children: [], + label: '林西县', + value: 150424, + }, + { + children: [], + label: '克什克腾旗', + value: 150425, + }, + { + children: [], + label: '翁牛特旗', + value: 150426, + }, + { + children: [], + label: '喀喇沁旗', + value: 150428, + }, + { + children: [], + label: '宁城县', + value: 150429, + }, + { + children: [], + label: '敖汉旗', + value: 150430, + }, + ], + label: '赤峰市', + value: 150400, + }, + { + children: [ + { + children: [], + label: '科尔沁区', + value: 150502, + }, + { + children: [], + label: '科尔沁左翼中旗', + value: 150521, + }, + { + children: [], + label: '科尔沁左翼后旗', + value: 150522, + }, + { + children: [], + label: '开鲁县', + value: 150523, + }, + { + children: [], + label: '库伦旗', + value: 150524, + }, + { + children: [], + label: '奈曼旗', + value: 150525, + }, + { + children: [], + label: '扎鲁特旗', + value: 150526, + }, + { + children: [], + label: '霍林郭勒市', + value: 150581, + }, + ], + label: '通辽市', + value: 150500, + }, + { + children: [ + { + children: [], + label: '东胜区', + value: 150602, + }, + { + children: [], + label: '康巴什区', + value: 150603, + }, + { + children: [], + label: '达拉特旗', + value: 150621, + }, + { + children: [], + label: '准格尔旗', + value: 150622, + }, + { + children: [], + label: '鄂托克前旗', + value: 150623, + }, + { + children: [], + label: '鄂托克旗', + value: 150624, + }, + { + children: [], + label: '杭锦旗', + value: 150625, + }, + { + children: [], + label: '乌审旗', + value: 150626, + }, + { + children: [], + label: '伊金霍洛旗', + value: 150627, + }, + ], + label: '鄂尔多斯市', + value: 150600, + }, + { + children: [ + { + children: [], + label: '海拉尔区', + value: 150702, + }, + { + children: [], + label: '扎赉诺尔区', + value: 150703, + }, + { + children: [], + label: '阿荣旗', + value: 150721, + }, + { + children: [], + label: '莫力达瓦达斡尔族自治旗', + value: 150722, + }, + { + children: [], + label: '鄂伦春自治旗', + value: 150723, + }, + { + children: [], + label: '鄂温克族自治旗', + value: 150724, + }, + { + children: [], + label: '陈巴尔虎旗', + value: 150725, + }, + { + children: [], + label: '新巴尔虎左旗', + value: 150726, + }, + { + children: [], + label: '新巴尔虎右旗', + value: 150727, + }, + { + children: [], + label: '满洲里市', + value: 150781, + }, + { + children: [], + label: '牙克石市', + value: 150782, + }, + { + children: [], + label: '扎兰屯市', + value: 150783, + }, + { + children: [], + label: '额尔古纳市', + value: 150784, + }, + { + children: [], + label: '根河市', + value: 150785, + }, + ], + label: '呼伦贝尔市', + value: 150700, + }, + { + children: [ + { + children: [], + label: '临河区', + value: 150802, + }, + { + children: [], + label: '五原县', + value: 150821, + }, + { + children: [], + label: '磴口县', + value: 150822, + }, + { + children: [], + label: '乌拉特前旗', + value: 150823, + }, + { + children: [], + label: '乌拉特中旗', + value: 150824, + }, + { + children: [], + label: '乌拉特后旗', + value: 150825, + }, + { + children: [], + label: '杭锦后旗', + value: 150826, + }, + ], + label: '巴彦淖尔市', + value: 150800, + }, + { + children: [ + { + children: [], + label: '集宁区', + value: 150902, + }, + { + children: [], + label: '卓资县', + value: 150921, + }, + { + children: [], + label: '化德县', + value: 150922, + }, + { + children: [], + label: '商都县', + value: 150923, + }, + { + children: [], + label: '兴和县', + value: 150924, + }, + { + children: [], + label: '凉城县', + value: 150925, + }, + { + children: [], + label: '察哈尔右翼前旗', + value: 150926, + }, + { + children: [], + label: '察哈尔右翼中旗', + value: 150927, + }, + { + children: [], + label: '察哈尔右翼后旗', + value: 150928, + }, + { + children: [], + label: '四子王旗', + value: 150929, + }, + { + children: [], + label: '丰镇市', + value: 150981, + }, + ], + label: '乌兰察布市', + value: 150900, + }, + { + children: [ + { + children: [], + label: '乌兰浩特市', + value: 152201, + }, + { + children: [], + label: '阿尔山市', + value: 152202, + }, + { + children: [], + label: '科尔沁右翼前旗', + value: 152221, + }, + { + children: [], + label: '科尔沁右翼中旗', + value: 152222, + }, + { + children: [], + label: '扎赉特旗', + value: 152223, + }, + { + children: [], + label: '突泉县', + value: 152224, + }, + ], + label: '兴安盟', + value: 152200, + }, + { + children: [ + { + children: [], + label: '二连浩特市', + value: 152501, + }, + { + children: [], + label: '锡林浩特市', + value: 152502, + }, + { + children: [], + label: '阿巴嘎旗', + value: 152522, + }, + { + children: [], + label: '苏尼特左旗', + value: 152523, + }, + { + children: [], + label: '苏尼特右旗', + value: 152524, + }, + { + children: [], + label: '东乌珠穆沁旗', + value: 152525, + }, + { + children: [], + label: '西乌珠穆沁旗', + value: 152526, + }, + { + children: [], + label: '太仆寺旗', + value: 152527, + }, + { + children: [], + label: '镶黄旗', + value: 152528, + }, + { + children: [], + label: '正镶白旗', + value: 152529, + }, + { + children: [], + label: '正蓝旗', + value: 152530, + }, + { + children: [], + label: '多伦县', + value: 152531, + }, + ], + label: '锡林郭勒盟', + value: 152500, + }, + { + children: [ + { + children: [], + label: '阿拉善左旗', + value: 152921, + }, + { + children: [], + label: '阿拉善右旗', + value: 152922, + }, + { + children: [], + label: '额济纳旗', + value: 152923, + }, + ], + label: '阿拉善盟', + value: 152900, + }, + ], + label: '内蒙古自治区', + value: 150000, + }, + { + children: [ + { + children: [ + { + children: [], + label: '和平区', + value: 210102, + }, + { + children: [], + label: '沈河区', + value: 210103, + }, + { + children: [], + label: '大东区', + value: 210104, + }, + { + children: [], + label: '皇姑区', + value: 210105, + }, + { + children: [], + label: '铁西区', + value: 210106, + }, + { + children: [], + label: '苏家屯区', + value: 210111, + }, + { + children: [], + label: '浑南区', + value: 210112, + }, + { + children: [], + label: '沈北新区', + value: 210113, + }, + { + children: [], + label: '于洪区', + value: 210114, + }, + { + children: [], + label: '辽中区', + value: 210115, + }, + { + children: [], + label: '康平县', + value: 210123, + }, + { + children: [], + label: '法库县', + value: 210124, + }, + { + children: [], + label: '新民市', + value: 210181, + }, + ], + label: '沈阳市', + value: 210100, + }, + { + children: [ + { + children: [], + label: '中山区', + value: 210202, + }, + { + children: [], + label: '西岗区', + value: 210203, + }, + { + children: [], + label: '沙河口区', + value: 210204, + }, + { + children: [], + label: '甘井子区', + value: 210211, + }, + { + children: [], + label: '旅顺口区', + value: 210212, + }, + { + children: [], + label: '金州区', + value: 210213, + }, + { + children: [], + label: '普兰店区', + value: 210214, + }, + { + children: [], + label: '长海县', + value: 210224, + }, + { + children: [], + label: '瓦房店市', + value: 210281, + }, + { + children: [], + label: '庄河市', + value: 210283, + }, + ], + label: '大连市', + value: 210200, + }, + { + children: [ + { + children: [], + label: '铁东区', + value: 210302, + }, + { + children: [], + label: '铁西区', + value: 210303, + }, + { + children: [], + label: '立山区', + value: 210304, + }, + { + children: [], + label: '千山区', + value: 210311, + }, + { + children: [], + label: '台安县', + value: 210321, + }, + { + children: [], + label: '岫岩满族自治县', + value: 210323, + }, + { + children: [], + label: '海城市', + value: 210381, + }, + ], + label: '鞍山市', + value: 210300, + }, + { + children: [ + { + children: [], + label: '新抚区', + value: 210402, + }, + { + children: [], + label: '东洲区', + value: 210403, + }, + { + children: [], + label: '望花区', + value: 210404, + }, + { + children: [], + label: '顺城区', + value: 210411, + }, + { + children: [], + label: '抚顺县', + value: 210421, + }, + { + children: [], + label: '新宾满族自治县', + value: 210422, + }, + { + children: [], + label: '清原满族自治县', + value: 210423, + }, + ], + label: '抚顺市', + value: 210400, + }, + { + children: [ + { + children: [], + label: '平山区', + value: 210502, + }, + { + children: [], + label: '溪湖区', + value: 210503, + }, + { + children: [], + label: '明山区', + value: 210504, + }, + { + children: [], + label: '南芬区', + value: 210505, + }, + { + children: [], + label: '本溪满族自治县', + value: 210521, + }, + { + children: [], + label: '桓仁满族自治县', + value: 210522, + }, + ], + label: '本溪市', + value: 210500, + }, + { + children: [ + { + children: [], + label: '元宝区', + value: 210602, + }, + { + children: [], + label: '振兴区', + value: 210603, + }, + { + children: [], + label: '振安区', + value: 210604, + }, + { + children: [], + label: '宽甸满族自治县', + value: 210624, + }, + { + children: [], + label: '东港市', + value: 210681, + }, + { + children: [], + label: '凤城市', + value: 210682, + }, + ], + label: '丹东市', + value: 210600, + }, + { + children: [ + { + children: [], + label: '古塔区', + value: 210702, + }, + { + children: [], + label: '凌河区', + value: 210703, + }, + { + children: [], + label: '太和区', + value: 210711, + }, + { + children: [], + label: '黑山县', + value: 210726, + }, + { + children: [], + label: '义县', + value: 210727, + }, + { + children: [], + label: '凌海市', + value: 210781, + }, + { + children: [], + label: '北镇市', + value: 210782, + }, + ], + label: '锦州市', + value: 210700, + }, + { + children: [ + { + children: [], + label: '站前区', + value: 210802, + }, + { + children: [], + label: '西市区', + value: 210803, + }, + { + children: [], + label: '鲅鱼圈区', + value: 210804, + }, + { + children: [], + label: '老边区', + value: 210811, + }, + { + children: [], + label: '盖州市', + value: 210881, + }, + { + children: [], + label: '大石桥市', + value: 210882, + }, + ], + label: '营口市', + value: 210800, + }, + { + children: [ + { + children: [], + label: '海州区', + value: 210902, + }, + { + children: [], + label: '新邱区', + value: 210903, + }, + { + children: [], + label: '太平区', + value: 210904, + }, + { + children: [], + label: '清河门区', + value: 210905, + }, + { + children: [], + label: '细河区', + value: 210911, + }, + { + children: [], + label: '阜新蒙古族自治县', + value: 210921, + }, + { + children: [], + label: '彰武县', + value: 210922, + }, + ], + label: '阜新市', + value: 210900, + }, + { + children: [ + { + children: [], + label: '白塔区', + value: 211002, + }, + { + children: [], + label: '文圣区', + value: 211003, + }, + { + children: [], + label: '宏伟区', + value: 211004, + }, + { + children: [], + label: '弓长岭区', + value: 211005, + }, + { + children: [], + label: '太子河区', + value: 211011, + }, + { + children: [], + label: '辽阳县', + value: 211021, + }, + { + children: [], + label: '灯塔市', + value: 211081, + }, + ], + label: '辽阳市', + value: 211000, + }, + { + children: [ + { + children: [], + label: '双台子区', + value: 211102, + }, + { + children: [], + label: '兴隆台区', + value: 211103, + }, + { + children: [], + label: '大洼区', + value: 211104, + }, + { + children: [], + label: '盘山县', + value: 211122, + }, + ], + label: '盘锦市', + value: 211100, + }, + { + children: [ + { + children: [], + label: '银州区', + value: 211202, + }, + { + children: [], + label: '清河区', + value: 211204, + }, + { + children: [], + label: '铁岭县', + value: 211221, + }, + { + children: [], + label: '西丰县', + value: 211223, + }, + { + children: [], + label: '昌图县', + value: 211224, + }, + { + children: [], + label: '调兵山市', + value: 211281, + }, + { + children: [], + label: '开原市', + value: 211282, + }, + ], + label: '铁岭市', + value: 211200, + }, + { + children: [ + { + children: [], + label: '双塔区', + value: 211302, + }, + { + children: [], + label: '龙城区', + value: 211303, + }, + { + children: [], + label: '朝阳县', + value: 211321, + }, + { + children: [], + label: '建平县', + value: 211322, + }, + { + children: [], + label: '喀喇沁左翼蒙古族自治县', + value: 211324, + }, + { + children: [], + label: '北票市', + value: 211381, + }, + { + children: [], + label: '凌源市', + value: 211382, + }, + ], + label: '朝阳市', + value: 211300, + }, + { + children: [ + { + children: [], + label: '连山区', + value: 211402, + }, + { + children: [], + label: '龙港区', + value: 211403, + }, + { + children: [], + label: '南票区', + value: 211404, + }, + { + children: [], + label: '绥中县', + value: 211421, + }, + { + children: [], + label: '建昌县', + value: 211422, + }, + { + children: [], + label: '兴城市', + value: 211481, + }, + ], + label: '葫芦岛市', + value: 211400, + }, + ], + label: '辽宁省', + value: 210000, + }, + { + children: [ + { + children: [ + { + children: [], + label: '南关区', + value: 220102, + }, + { + children: [], + label: '宽城区', + value: 220103, + }, + { + children: [], + label: '朝阳区', + value: 220104, + }, + { + children: [], + label: '二道区', + value: 220105, + }, + { + children: [], + label: '绿园区', + value: 220106, + }, + { + children: [], + label: '双阳区', + value: 220112, + }, + { + children: [], + label: '九台区', + value: 220113, + }, + { + children: [], + label: '农安县', + value: 220122, + }, + { + children: [], + label: '榆树市', + value: 220182, + }, + { + children: [], + label: '德惠市', + value: 220183, + }, + ], + label: '长春市', + value: 220100, + }, + { + children: [ + { + children: [], + label: '昌邑区', + value: 220202, + }, + { + children: [], + label: '龙潭区', + value: 220203, + }, + { + children: [], + label: '船营区', + value: 220204, + }, + { + children: [], + label: '丰满区', + value: 220211, + }, + { + children: [], + label: '永吉县', + value: 220221, + }, + { + children: [], + label: '蛟河市', + value: 220281, + }, + { + children: [], + label: '桦甸市', + value: 220282, + }, + { + children: [], + label: '舒兰市', + value: 220283, + }, + { + children: [], + label: '磐石市', + value: 220284, + }, + ], + label: '吉林市', + value: 220200, + }, + { + children: [ + { + children: [], + label: '铁西区', + value: 220302, + }, + { + children: [], + label: '铁东区', + value: 220303, + }, + { + children: [], + label: '梨树县', + value: 220322, + }, + { + children: [], + label: '伊通满族自治县', + value: 220323, + }, + { + children: [], + label: '公主岭市', + value: 220381, + }, + { + children: [], + label: '双辽市', + value: 220382, + }, + ], + label: '四平市', + value: 220300, + }, + { + children: [ + { + children: [], + label: '龙山区', + value: 220402, + }, + { + children: [], + label: '西安区', + value: 220403, + }, + { + children: [], + label: '东丰县', + value: 220421, + }, + { + children: [], + label: '东辽县', + value: 220422, + }, + ], + label: '辽源市', + value: 220400, + }, + { + children: [ + { + children: [], + label: '东昌区', + value: 220502, + }, + { + children: [], + label: '二道江区', + value: 220503, + }, + { + children: [], + label: '通化县', + value: 220521, + }, + { + children: [], + label: '辉南县', + value: 220523, + }, + { + children: [], + label: '柳河县', + value: 220524, + }, + { + children: [], + label: '梅河口市', + value: 220581, + }, + { + children: [], + label: '集安市', + value: 220582, + }, + ], + label: '通化市', + value: 220500, + }, + { + children: [ + { + children: [], + label: '浑江区', + value: 220602, + }, + { + children: [], + label: '江源区', + value: 220605, + }, + { + children: [], + label: '抚松县', + value: 220621, + }, + { + children: [], + label: '靖宇县', + value: 220622, + }, + { + children: [], + label: '长白朝鲜族自治县', + value: 220623, + }, + { + children: [], + label: '临江市', + value: 220681, + }, + ], + label: '白山市', + value: 220600, + }, + { + children: [ + { + children: [], + label: '宁江区', + value: 220702, + }, + { + children: [], + label: '前郭尔罗斯蒙古族自治县', + value: 220721, + }, + { + children: [], + label: '长岭县', + value: 220722, + }, + { + children: [], + label: '乾安县', + value: 220723, + }, + { + children: [], + label: '扶余市', + value: 220781, + }, + ], + label: '松原市', + value: 220700, + }, + { + children: [ + { + children: [], + label: '洮北区', + value: 220802, + }, + { + children: [], + label: '镇赉县', + value: 220821, + }, + { + children: [], + label: '通榆县', + value: 220822, + }, + { + children: [], + label: '洮南市', + value: 220881, + }, + { + children: [], + label: '大安市', + value: 220882, + }, + ], + label: '白城市', + value: 220800, + }, + { + children: [ + { + children: [], + label: '延吉市', + value: 222401, + }, + { + children: [], + label: '图们市', + value: 222402, + }, + { + children: [], + label: '敦化市', + value: 222403, + }, + { + children: [], + label: '珲春市', + value: 222404, + }, + { + children: [], + label: '龙井市', + value: 222405, + }, + { + children: [], + label: '和龙市', + value: 222406, + }, + { + children: [], + label: '汪清县', + value: 222424, + }, + { + children: [], + label: '安图县', + value: 222426, + }, + ], + label: '延边朝鲜族自治州', + value: 222400, + }, + ], + label: '吉林省', + value: 220000, + }, + { + children: [ + { + children: [ + { + children: [], + label: '道里区', + value: 230102, + }, + { + children: [], + label: '南岗区', + value: 230103, + }, + { + children: [], + label: '道外区', + value: 230104, + }, + { + children: [], + label: '平房区', + value: 230108, + }, + { + children: [], + label: '松北区', + value: 230109, + }, + { + children: [], + label: '香坊区', + value: 230110, + }, + { + children: [], + label: '呼兰区', + value: 230111, + }, + { + children: [], + label: '阿城区', + value: 230112, + }, + { + children: [], + label: '双城区', + value: 230113, + }, + { + children: [], + label: '依兰县', + value: 230123, + }, + { + children: [], + label: '方正县', + value: 230124, + }, + { + children: [], + label: '宾县', + value: 230125, + }, + { + children: [], + label: '巴彦县', + value: 230126, + }, + { + children: [], + label: '木兰县', + value: 230127, + }, + { + children: [], + label: '通河县', + value: 230128, + }, + { + children: [], + label: '延寿县', + value: 230129, + }, + { + children: [], + label: '尚志市', + value: 230183, + }, + { + children: [], + label: '五常市', + value: 230184, + }, + ], + label: '哈尔滨市', + value: 230100, + }, + { + children: [ + { + children: [], + label: '龙沙区', + value: 230202, + }, + { + children: [], + label: '建华区', + value: 230203, + }, + { + children: [], + label: '铁锋区', + value: 230204, + }, + { + children: [], + label: '昂昂溪区', + value: 230205, + }, + { + children: [], + label: '富拉尔基区', + value: 230206, + }, + { + children: [], + label: '碾子山区', + value: 230207, + }, + { + children: [], + label: '梅里斯达斡尔族区', + value: 230208, + }, + { + children: [], + label: '龙江县', + value: 230221, + }, + { + children: [], + label: '依安县', + value: 230223, + }, + { + children: [], + label: '泰来县', + value: 230224, + }, + { + children: [], + label: '甘南县', + value: 230225, + }, + { + children: [], + label: '富裕县', + value: 230227, + }, + { + children: [], + label: '克山县', + value: 230229, + }, + { + children: [], + label: '克东县', + value: 230230, + }, + { + children: [], + label: '拜泉县', + value: 230231, + }, + { + children: [], + label: '讷河市', + value: 230281, + }, + ], + label: '齐齐哈尔市', + value: 230200, + }, + { + children: [ + { + children: [], + label: '鸡冠区', + value: 230302, + }, + { + children: [], + label: '恒山区', + value: 230303, + }, + { + children: [], + label: '滴道区', + value: 230304, + }, + { + children: [], + label: '梨树区', + value: 230305, + }, + { + children: [], + label: '城子河区', + value: 230306, + }, + { + children: [], + label: '麻山区', + value: 230307, + }, + { + children: [], + label: '鸡东县', + value: 230321, + }, + { + children: [], + label: '虎林市', + value: 230381, + }, + { + children: [], + label: '密山市', + value: 230382, + }, + ], + label: '鸡西市', + value: 230300, + }, + { + children: [ + { + children: [], + label: '向阳区', + value: 230402, + }, + { + children: [], + label: '工农区', + value: 230403, + }, + { + children: [], + label: '南山区', + value: 230404, + }, + { + children: [], + label: '兴安区', + value: 230405, + }, + { + children: [], + label: '东山区', + value: 230406, + }, + { + children: [], + label: '兴山区', + value: 230407, + }, + { + children: [], + label: '萝北县', + value: 230421, + }, + { + children: [], + label: '绥滨县', + value: 230422, + }, + ], + label: '鹤岗市', + value: 230400, + }, + { + children: [ + { + children: [], + label: '尖山区', + value: 230502, + }, + { + children: [], + label: '岭东区', + value: 230503, + }, + { + children: [], + label: '四方台区', + value: 230505, + }, + { + children: [], + label: '宝山区', + value: 230506, + }, + { + children: [], + label: '集贤县', + value: 230521, + }, + { + children: [], + label: '友谊县', + value: 230522, + }, + { + children: [], + label: '宝清县', + value: 230523, + }, + { + children: [], + label: '饶河县', + value: 230524, + }, + ], + label: '双鸭山市', + value: 230500, + }, + { + children: [ + { + children: [], + label: '萨尔图区', + value: 230602, + }, + { + children: [], + label: '龙凤区', + value: 230603, + }, + { + children: [], + label: '让胡路区', + value: 230604, + }, + { + children: [], + label: '红岗区', + value: 230605, + }, + { + children: [], + label: '大同区', + value: 230606, + }, + { + children: [], + label: '肇州县', + value: 230621, + }, + { + children: [], + label: '肇源县', + value: 230622, + }, + { + children: [], + label: '林甸县', + value: 230623, + }, + { + children: [], + label: '杜尔伯特蒙古族自治县', + value: 230624, + }, + ], + label: '大庆市', + value: 230600, + }, + { + children: [ + { + children: [], + label: '伊春区', + value: 230702, + }, + { + children: [], + label: '南岔区', + value: 230703, + }, + { + children: [], + label: '友好区', + value: 230704, + }, + { + children: [], + label: '西林区', + value: 230705, + }, + { + children: [], + label: '翠峦区', + value: 230706, + }, + { + children: [], + label: '新青区', + value: 230707, + }, + { + children: [], + label: '美溪区', + value: 230708, + }, + { + children: [], + label: '金山屯区', + value: 230709, + }, + { + children: [], + label: '五营区', + value: 230710, + }, + { + children: [], + label: '乌马河区', + value: 230711, + }, + { + children: [], + label: '汤旺河区', + value: 230712, + }, + { + children: [], + label: '带岭区', + value: 230713, + }, + { + children: [], + label: '乌伊岭区', + value: 230714, + }, + { + children: [], + label: '红星区', + value: 230715, + }, + { + children: [], + label: '上甘岭区', + value: 230716, + }, + { + children: [], + label: '嘉荫县', + value: 230722, + }, + { + children: [], + label: '铁力市', + value: 230781, + }, + ], + label: '伊春市', + value: 230700, + }, + { + children: [ + { + children: [], + label: '向阳区', + value: 230803, + }, + { + children: [], + label: '前进区', + value: 230804, + }, + { + children: [], + label: '东风区', + value: 230805, + }, + { + children: [], + label: '郊区', + value: 230811, + }, + { + children: [], + label: '桦南县', + value: 230822, + }, + { + children: [], + label: '桦川县', + value: 230826, + }, + { + children: [], + label: '汤原县', + value: 230828, + }, + { + children: [], + label: '同江市', + value: 230881, + }, + { + children: [], + label: '富锦市', + value: 230882, + }, + { + children: [], + label: '抚远市', + value: 230883, + }, + ], + label: '佳木斯市', + value: 230800, + }, + { + children: [ + { + children: [], + label: '新兴区', + value: 230902, + }, + { + children: [], + label: '桃山区', + value: 230903, + }, + { + children: [], + label: '茄子河区', + value: 230904, + }, + { + children: [], + label: '勃利县', + value: 230921, + }, + ], + label: '七台河市', + value: 230900, + }, + { + children: [ + { + children: [], + label: '东安区', + value: 231002, + }, + { + children: [], + label: '阳明区', + value: 231003, + }, + { + children: [], + label: '爱民区', + value: 231004, + }, + { + children: [], + label: '西安区', + value: 231005, + }, + { + children: [], + label: '林口县', + value: 231025, + }, + { + children: [], + label: '绥芬河市', + value: 231081, + }, + { + children: [], + label: '海林市', + value: 231083, + }, + { + children: [], + label: '宁安市', + value: 231084, + }, + { + children: [], + label: '穆棱市', + value: 231085, + }, + { + children: [], + label: '东宁市', + value: 231086, + }, + ], + label: '牡丹江市', + value: 231000, + }, + { + children: [ + { + children: [], + label: '爱辉区', + value: 231102, + }, + { + children: [], + label: '嫩江县', + value: 231121, + }, + { + children: [], + label: '逊克县', + value: 231123, + }, + { + children: [], + label: '孙吴县', + value: 231124, + }, + { + children: [], + label: '北安市', + value: 231181, + }, + { + children: [], + label: '五大连池市', + value: 231182, + }, + ], + label: '黑河市', + value: 231100, + }, + { + children: [ + { + children: [], + label: '北林区', + value: 231202, + }, + { + children: [], + label: '望奎县', + value: 231221, + }, + { + children: [], + label: '兰西县', + value: 231222, + }, + { + children: [], + label: '青冈县', + value: 231223, + }, + { + children: [], + label: '庆安县', + value: 231224, + }, + { + children: [], + label: '明水县', + value: 231225, + }, + { + children: [], + label: '绥棱县', + value: 231226, + }, + { + children: [], + label: '安达市', + value: 231281, + }, + { + children: [], + label: '肇东市', + value: 231282, + }, + { + children: [], + label: '海伦市', + value: 231283, + }, + ], + label: '绥化市', + value: 231200, + }, + { + children: [ + { + children: [], + label: '漠河市', + value: 232701, + }, + { + children: [], + label: '呼玛县', + value: 232721, + }, + { + children: [], + label: '塔河县', + value: 232722, + }, + { + children: [], + label: '大兴安岭地区直辖', + value: 232723, + }, + ], + label: '大兴安岭地区', + value: 232700, + }, + ], + label: '黑龙江省', + value: 230000, + }, + { + children: [ + { + children: [ + { + children: [], + label: '黄浦区', + value: 310101, + }, + { + children: [], + label: '徐汇区', + value: 310104, + }, + { + children: [], + label: '长宁区', + value: 310105, + }, + { + children: [], + label: '静安区', + value: 310106, + }, + { + children: [], + label: '普陀区', + value: 310107, + }, + { + children: [], + label: '虹口区', + value: 310109, + }, + { + children: [], + label: '杨浦区', + value: 310110, + }, + { + children: [], + label: '闵行区', + value: 310112, + }, + { + children: [], + label: '宝山区', + value: 310113, + }, + { + children: [], + label: '嘉定区', + value: 310114, + }, + { + children: [], + label: '浦东新区', + value: 310115, + }, + { + children: [], + label: '金山区', + value: 310116, + }, + { + children: [], + label: '松江区', + value: 310117, + }, + { + children: [], + label: '青浦区', + value: 310118, + }, + { + children: [], + label: '奉贤区', + value: 310120, + }, + { + children: [], + label: '崇明区', + value: 310151, + }, + ], + label: '上海市', + value: 310100, + }, + ], + label: '上海市', + value: 310000, + }, + { + children: [ + { + children: [ + { + children: [], + label: '玄武区', + value: 320102, + }, + { + children: [], + label: '秦淮区', + value: 320104, + }, + { + children: [], + label: '建邺区', + value: 320105, + }, + { + children: [], + label: '鼓楼区', + value: 320106, + }, + { + children: [], + label: '浦口区', + value: 320111, + }, + { + children: [], + label: '栖霞区', + value: 320113, + }, + { + children: [], + label: '雨花台区', + value: 320114, + }, + { + children: [], + label: '江宁区', + value: 320115, + }, + { + children: [], + label: '六合区', + value: 320116, + }, + { + children: [], + label: '溧水区', + value: 320117, + }, + { + children: [], + label: '高淳区', + value: 320118, + }, + ], + label: '南京市', + value: 320100, + }, + { + children: [ + { + children: [], + label: '锡山区', + value: 320205, + }, + { + children: [], + label: '惠山区', + value: 320206, + }, + { + children: [], + label: '滨湖区', + value: 320211, + }, + { + children: [], + label: '梁溪区', + value: 320213, + }, + { + children: [], + label: '新吴区', + value: 320214, + }, + { + children: [], + label: '江阴市', + value: 320281, + }, + { + children: [], + label: '宜兴市', + value: 320282, + }, + ], + label: '无锡市', + value: 320200, + }, + { + children: [ + { + children: [], + label: '鼓楼区', + value: 320302, + }, + { + children: [], + label: '云龙区', + value: 320303, + }, + { + children: [], + label: '贾汪区', + value: 320305, + }, + { + children: [], + label: '泉山区', + value: 320311, + }, + { + children: [], + label: '铜山区', + value: 320312, + }, + { + children: [], + label: '丰县', + value: 320321, + }, + { + children: [], + label: '沛县', + value: 320322, + }, + { + children: [], + label: '睢宁县', + value: 320324, + }, + { + children: [], + label: '新沂市', + value: 320381, + }, + { + children: [], + label: '邳州市', + value: 320382, + }, + ], + label: '徐州市', + value: 320300, + }, + { + children: [ + { + children: [], + label: '天宁区', + value: 320402, + }, + { + children: [], + label: '钟楼区', + value: 320404, + }, + { + children: [], + label: '新北区', + value: 320411, + }, + { + children: [], + label: '武进区', + value: 320412, + }, + { + children: [], + label: '金坛区', + value: 320413, + }, + { + children: [], + label: '溧阳市', + value: 320481, + }, + ], + label: '常州市', + value: 320400, + }, + { + children: [ + { + children: [], + label: '虎丘区', + value: 320505, + }, + { + children: [], + label: '吴中区', + value: 320506, + }, + { + children: [], + label: '相城区', + value: 320507, + }, + { + children: [], + label: '姑苏区', + value: 320508, + }, + { + children: [], + label: '吴江区', + value: 320509, + }, + { + children: [], + label: '苏州工业园区', + value: 320571, + }, + { + children: [], + label: '常熟市', + value: 320581, + }, + { + children: [], + label: '张家港市', + value: 320582, + }, + { + children: [], + label: '昆山市', + value: 320583, + }, + { + children: [], + label: '太仓市', + value: 320585, + }, + ], + label: '苏州市', + value: 320500, + }, + { + children: [ + { + children: [], + label: '崇川区', + value: 320602, + }, + { + children: [], + label: '港闸区', + value: 320611, + }, + { + children: [], + label: '通州区', + value: 320612, + }, + { + children: [], + label: '如东县', + value: 320623, + }, + { + children: [], + label: '启东市', + value: 320681, + }, + { + children: [], + label: '如皋市', + value: 320682, + }, + { + children: [], + label: '海门市', + value: 320684, + }, + { + children: [], + label: '海安市', + value: 320685, + }, + ], + label: '南通市', + value: 320600, + }, + { + children: [ + { + children: [], + label: '连云区', + value: 320703, + }, + { + children: [], + label: '海州区', + value: 320706, + }, + { + children: [], + label: '赣榆区', + value: 320707, + }, + { + children: [], + label: '东海县', + value: 320722, + }, + { + children: [], + label: '灌云县', + value: 320723, + }, + { + children: [], + label: '灌南县', + value: 320724, + }, + ], + label: '连云港市', + value: 320700, + }, + { + children: [ + { + children: [], + label: '淮安区', + value: 320803, + }, + { + children: [], + label: '淮阴区', + value: 320804, + }, + { + children: [], + label: '清江浦区', + value: 320812, + }, + { + children: [], + label: '洪泽区', + value: 320813, + }, + { + children: [], + label: '涟水县', + value: 320826, + }, + { + children: [], + label: '盱眙县', + value: 320830, + }, + { + children: [], + label: '金湖县', + value: 320831, + }, + ], + label: '淮安市', + value: 320800, + }, + { + children: [ + { + children: [], + label: '亭湖区', + value: 320902, + }, + { + children: [], + label: '盐都区', + value: 320903, + }, + { + children: [], + label: '大丰区', + value: 320904, + }, + { + children: [], + label: '响水县', + value: 320921, + }, + { + children: [], + label: '滨海县', + value: 320922, + }, + { + children: [], + label: '阜宁县', + value: 320923, + }, + { + children: [], + label: '射阳县', + value: 320924, + }, + { + children: [], + label: '建湖县', + value: 320925, + }, + { + children: [], + label: '东台市', + value: 320981, + }, + ], + label: '盐城市', + value: 320900, + }, + { + children: [ + { + children: [], + label: '广陵区', + value: 321002, + }, + { + children: [], + label: '邗江区', + value: 321003, + }, + { + children: [], + label: '江都区', + value: 321012, + }, + { + children: [], + label: '宝应县', + value: 321023, + }, + { + children: [], + label: '仪征市', + value: 321081, + }, + { + children: [], + label: '高邮市', + value: 321084, + }, + ], + label: '扬州市', + value: 321000, + }, + { + children: [ + { + children: [], + label: '京口区', + value: 321102, + }, + { + children: [], + label: '润州区', + value: 321111, + }, + { + children: [], + label: '丹徒区', + value: 321112, + }, + { + children: [], + label: '丹阳市', + value: 321181, + }, + { + children: [], + label: '扬中市', + value: 321182, + }, + { + children: [], + label: '句容市', + value: 321183, + }, + ], + label: '镇江市', + value: 321100, + }, + { + children: [ + { + children: [], + label: '海陵区', + value: 321202, + }, + { + children: [], + label: '高港区', + value: 321203, + }, + { + children: [], + label: '姜堰区', + value: 321204, + }, + { + children: [], + label: '兴化市', + value: 321281, + }, + { + children: [], + label: '靖江市', + value: 321282, + }, + { + children: [], + label: '泰兴市', + value: 321283, + }, + ], + label: '泰州市', + value: 321200, + }, + { + children: [ + { + children: [], + label: '宿城区', + value: 321302, + }, + { + children: [], + label: '宿豫区', + value: 321311, + }, + { + children: [], + label: '沭阳县', + value: 321322, + }, + { + children: [], + label: '泗阳县', + value: 321323, + }, + { + children: [], + label: '泗洪县', + value: 321324, + }, + ], + label: '宿迁市', + value: 321300, + }, + ], + label: '江苏省', + value: 320000, + }, + { + children: [ + { + children: [ + { + children: [], + label: '上城区', + value: 330102, + }, + { + children: [], + label: '下城区', + value: 330103, + }, + { + children: [], + label: '江干区', + value: 330104, + }, + { + children: [], + label: '拱墅区', + value: 330105, + }, + { + children: [], + label: '西湖区', + value: 330106, + }, + { + children: [], + label: '滨江区', + value: 330108, + }, + { + children: [], + label: '萧山区', + value: 330109, + }, + { + children: [], + label: '余杭区', + value: 330110, + }, + { + children: [], + label: '富阳区', + value: 330111, + }, + { + children: [], + label: '临安区', + value: 330112, + }, + { + children: [], + label: '桐庐县', + value: 330122, + }, + { + children: [], + label: '淳安县', + value: 330127, + }, + { + children: [], + label: '建德市', + value: 330182, + }, + ], + label: '杭州市', + value: 330100, + }, + { + children: [ + { + children: [], + label: '海曙区', + value: 330203, + }, + { + children: [], + label: '江北区', + value: 330205, + }, + { + children: [], + label: '北仑区', + value: 330206, + }, + { + children: [], + label: '镇海区', + value: 330211, + }, + { + children: [], + label: '鄞州区', + value: 330212, + }, + { + children: [], + label: '奉化区', + value: 330213, + }, + { + children: [], + label: '象山县', + value: 330225, + }, + { + children: [], + label: '宁海县', + value: 330226, + }, + { + children: [], + label: '余姚市', + value: 330281, + }, + { + children: [], + label: '慈溪市', + value: 330282, + }, + ], + label: '宁波市', + value: 330200, + }, + { + children: [ + { + children: [], + label: '鹿城区', + value: 330302, + }, + { + children: [], + label: '龙湾区', + value: 330303, + }, + { + children: [], + label: '瓯海区', + value: 330304, + }, + { + children: [], + label: '洞头区', + value: 330305, + }, + { + children: [], + label: '永嘉县', + value: 330324, + }, + { + children: [], + label: '平阳县', + value: 330326, + }, + { + children: [], + label: '苍南县', + value: 330327, + }, + { + children: [], + label: '文成县', + value: 330328, + }, + { + children: [], + label: '泰顺县', + value: 330329, + }, + { + children: [], + label: '瑞安市', + value: 330381, + }, + { + children: [], + label: '乐清市', + value: 330382, + }, + ], + label: '温州市', + value: 330300, + }, + { + children: [ + { + children: [], + label: '南湖区', + value: 330402, + }, + { + children: [], + label: '秀洲区', + value: 330411, + }, + { + children: [], + label: '嘉善县', + value: 330421, + }, + { + children: [], + label: '海盐县', + value: 330424, + }, + { + children: [], + label: '海宁市', + value: 330481, + }, + { + children: [], + label: '平湖市', + value: 330482, + }, + { + children: [], + label: '桐乡市', + value: 330483, + }, + ], + label: '嘉兴市', + value: 330400, + }, + { + children: [ + { + children: [], + label: '吴兴区', + value: 330502, + }, + { + children: [], + label: '南浔区', + value: 330503, + }, + { + children: [], + label: '德清县', + value: 330521, + }, + { + children: [], + label: '长兴县', + value: 330522, + }, + { + children: [], + label: '安吉县', + value: 330523, + }, + ], + label: '湖州市', + value: 330500, + }, + { + children: [ + { + children: [], + label: '越城区', + value: 330602, + }, + { + children: [], + label: '柯桥区', + value: 330603, + }, + { + children: [], + label: '上虞区', + value: 330604, + }, + { + children: [], + label: '新昌县', + value: 330624, + }, + { + children: [], + label: '诸暨市', + value: 330681, + }, + { + children: [], + label: '嵊州市', + value: 330683, + }, + ], + label: '绍兴市', + value: 330600, + }, + { + children: [ + { + children: [], + label: '婺城区', + value: 330702, + }, + { + children: [], + label: '金东区', + value: 330703, + }, + { + children: [], + label: '武义县', + value: 330723, + }, + { + children: [], + label: '浦江县', + value: 330726, + }, + { + children: [], + label: '磐安县', + value: 330727, + }, + { + children: [], + label: '兰溪市', + value: 330781, + }, + { + children: [], + label: '义乌市', + value: 330782, + }, + { + children: [], + label: '东阳市', + value: 330783, + }, + { + children: [], + label: '永康市', + value: 330784, + }, + ], + label: '金华市', + value: 330700, + }, + { + children: [ + { + children: [], + label: '柯城区', + value: 330802, + }, + { + children: [], + label: '衢江区', + value: 330803, + }, + { + children: [], + label: '常山县', + value: 330822, + }, + { + children: [], + label: '开化县', + value: 330824, + }, + { + children: [], + label: '龙游县', + value: 330825, + }, + { + children: [], + label: '江山市', + value: 330881, + }, + ], + label: '衢州市', + value: 330800, + }, + { + children: [ + { + children: [], + label: '定海区', + value: 330902, + }, + { + children: [], + label: '普陀区', + value: 330903, + }, + { + children: [], + label: '岱山县', + value: 330921, + }, + { + children: [], + label: '嵊泗县', + value: 330922, + }, + ], + label: '舟山市', + value: 330900, + }, + { + children: [ + { + children: [], + label: '椒江区', + value: 331002, + }, + { + children: [], + label: '黄岩区', + value: 331003, + }, + { + children: [], + label: '路桥区', + value: 331004, + }, + { + children: [], + label: '三门县', + value: 331022, + }, + { + children: [], + label: '天台县', + value: 331023, + }, + { + children: [], + label: '仙居县', + value: 331024, + }, + { + children: [], + label: '温岭市', + value: 331081, + }, + { + children: [], + label: '临海市', + value: 331082, + }, + { + children: [], + label: '玉环市', + value: 331083, + }, + ], + label: '台州市', + value: 331000, + }, + { + children: [ + { + children: [], + label: '莲都区', + value: 331102, + }, + { + children: [], + label: '青田县', + value: 331121, + }, + { + children: [], + label: '缙云县', + value: 331122, + }, + { + children: [], + label: '遂昌县', + value: 331123, + }, + { + children: [], + label: '松阳县', + value: 331124, + }, + { + children: [], + label: '云和县', + value: 331125, + }, + { + children: [], + label: '庆元县', + value: 331126, + }, + { + children: [], + label: '景宁畲族自治县', + value: 331127, + }, + { + children: [], + label: '龙泉市', + value: 331181, + }, + ], + label: '丽水市', + value: 331100, + }, + ], + label: '浙江省', + value: 330000, + }, + { + children: [ + { + children: [ + { + children: [], + label: '瑶海区', + value: 340102, + }, + { + children: [], + label: '庐阳区', + value: 340103, + }, + { + children: [], + label: '蜀山区', + value: 340104, + }, + { + children: [], + label: '包河区', + value: 340111, + }, + { + children: [], + label: '长丰县', + value: 340121, + }, + { + children: [], + label: '肥东县', + value: 340122, + }, + { + children: [], + label: '肥西县', + value: 340123, + }, + { + children: [], + label: '庐江县', + value: 340124, + }, + { + children: [], + label: '巢湖市', + value: 340181, + }, + ], + label: '合肥市', + value: 340100, + }, + { + children: [ + { + children: [], + label: '镜湖区', + value: 340202, + }, + { + children: [], + label: '弋江区', + value: 340203, + }, + { + children: [], + label: '鸠江区', + value: 340207, + }, + { + children: [], + label: '三山区', + value: 340208, + }, + { + children: [], + label: '芜湖县', + value: 340221, + }, + { + children: [], + label: '繁昌县', + value: 340222, + }, + { + children: [], + label: '南陵县', + value: 340223, + }, + { + children: [], + label: '无为县', + value: 340225, + }, + ], + label: '芜湖市', + value: 340200, + }, + { + children: [ + { + children: [], + label: '龙子湖区', + value: 340302, + }, + { + children: [], + label: '蚌山区', + value: 340303, + }, + { + children: [], + label: '禹会区', + value: 340304, + }, + { + children: [], + label: '淮上区', + value: 340311, + }, + { + children: [], + label: '怀远县', + value: 340321, + }, + { + children: [], + label: '五河县', + value: 340322, + }, + { + children: [], + label: '固镇县', + value: 340323, + }, + ], + label: '蚌埠市', + value: 340300, + }, + { + children: [ + { + children: [], + label: '大通区', + value: 340402, + }, + { + children: [], + label: '田家庵区', + value: 340403, + }, + { + children: [], + label: '谢家集区', + value: 340404, + }, + { + children: [], + label: '八公山区', + value: 340405, + }, + { + children: [], + label: '潘集区', + value: 340406, + }, + { + children: [], + label: '凤台县', + value: 340421, + }, + { + children: [], + label: '寿县', + value: 340422, + }, + ], + label: '淮南市', + value: 340400, + }, + { + children: [ + { + children: [], + label: '花山区', + value: 340503, + }, + { + children: [], + label: '雨山区', + value: 340504, + }, + { + children: [], + label: '博望区', + value: 340506, + }, + { + children: [], + label: '当涂县', + value: 340521, + }, + { + children: [], + label: '含山县', + value: 340522, + }, + { + children: [], + label: '和县', + value: 340523, + }, + ], + label: '马鞍山市', + value: 340500, + }, + { + children: [ + { + children: [], + label: '杜集区', + value: 340602, + }, + { + children: [], + label: '相山区', + value: 340603, + }, + { + children: [], + label: '烈山区', + value: 340604, + }, + { + children: [], + label: '濉溪县', + value: 340621, + }, + ], + label: '淮北市', + value: 340600, + }, + { + children: [ + { + children: [], + label: '铜官区', + value: 340705, + }, + { + children: [], + label: '义安区', + value: 340706, + }, + { + children: [], + label: '郊区', + value: 340711, + }, + { + children: [], + label: '枞阳县', + value: 340722, + }, + ], + label: '铜陵市', + value: 340700, + }, + { + children: [ + { + children: [], + label: '迎江区', + value: 340802, + }, + { + children: [], + label: '大观区', + value: 340803, + }, + { + children: [], + label: '宜秀区', + value: 340811, + }, + { + children: [], + label: '怀宁县', + value: 340822, + }, + { + children: [], + label: '太湖县', + value: 340825, + }, + { + children: [], + label: '宿松县', + value: 340826, + }, + { + children: [], + label: '望江县', + value: 340827, + }, + { + children: [], + label: '岳西县', + value: 340828, + }, + { + children: [], + label: '桐城市', + value: 340881, + }, + { + children: [], + label: '潜山市', + value: 340882, + }, + ], + label: '安庆市', + value: 340800, + }, + { + children: [ + { + children: [], + label: '屯溪区', + value: 341002, + }, + { + children: [], + label: '黄山区', + value: 341003, + }, + { + children: [], + label: '徽州区', + value: 341004, + }, + { + children: [], + label: '歙县', + value: 341021, + }, + { + children: [], + label: '休宁县', + value: 341022, + }, + { + children: [], + label: '黟县', + value: 341023, + }, + { + children: [], + label: '祁门县', + value: 341024, + }, + ], + label: '黄山市', + value: 341000, + }, + { + children: [ + { + children: [], + label: '琅琊区', + value: 341102, + }, + { + children: [], + label: '南谯区', + value: 341103, + }, + { + children: [], + label: '来安县', + value: 341122, + }, + { + children: [], + label: '全椒县', + value: 341124, + }, + { + children: [], + label: '定远县', + value: 341125, + }, + { + children: [], + label: '凤阳县', + value: 341126, + }, + { + children: [], + label: '天长市', + value: 341181, + }, + { + children: [], + label: '明光市', + value: 341182, + }, + ], + label: '滁州市', + value: 341100, + }, + { + children: [ + { + children: [], + label: '颍州区', + value: 341202, + }, + { + children: [], + label: '颍东区', + value: 341203, + }, + { + children: [], + label: '颍泉区', + value: 341204, + }, + { + children: [], + label: '临泉县', + value: 341221, + }, + { + children: [], + label: '太和县', + value: 341222, + }, + { + children: [], + label: '阜南县', + value: 341225, + }, + { + children: [], + label: '颍上县', + value: 341226, + }, + { + children: [], + label: '界首市', + value: 341282, + }, + ], + label: '阜阳市', + value: 341200, + }, + { + children: [ + { + children: [], + label: '埇桥区', + value: 341302, + }, + { + children: [], + label: '砀山县', + value: 341321, + }, + { + children: [], + label: '萧县', + value: 341322, + }, + { + children: [], + label: '灵璧县', + value: 341323, + }, + { + children: [], + label: '泗县', + value: 341324, + }, + ], + label: '宿州市', + value: 341300, + }, + { + children: [ + { + children: [], + label: '金安区', + value: 341502, + }, + { + children: [], + label: '裕安区', + value: 341503, + }, + { + children: [], + label: '叶集区', + value: 341504, + }, + { + children: [], + label: '霍邱县', + value: 341522, + }, + { + children: [], + label: '舒城县', + value: 341523, + }, + { + children: [], + label: '金寨县', + value: 341524, + }, + { + children: [], + label: '霍山县', + value: 341525, + }, + ], + label: '六安市', + value: 341500, + }, + { + children: [ + { + children: [], + label: '谯城区', + value: 341602, + }, + { + children: [], + label: '涡阳县', + value: 341621, + }, + { + children: [], + label: '蒙城县', + value: 341622, + }, + { + children: [], + label: '利辛县', + value: 341623, + }, + ], + label: '亳州市', + value: 341600, + }, + { + children: [ + { + children: [], + label: '贵池区', + value: 341702, + }, + { + children: [], + label: '东至县', + value: 341721, + }, + { + children: [], + label: '石台县', + value: 341722, + }, + { + children: [], + label: '青阳县', + value: 341723, + }, + ], + label: '池州市', + value: 341700, + }, + { + children: [ + { + children: [], + label: '宣州区', + value: 341802, + }, + { + children: [], + label: '郎溪县', + value: 341821, + }, + { + children: [], + label: '广德县', + value: 341822, + }, + { + children: [], + label: '泾县', + value: 341823, + }, + { + children: [], + label: '绩溪县', + value: 341824, + }, + { + children: [], + label: '旌德县', + value: 341825, + }, + { + children: [], + label: '宁国市', + value: 341881, + }, + ], + label: '宣城市', + value: 341800, + }, + ], + label: '安徽省', + value: 340000, + }, + { + children: [ + { + children: [ + { + children: [], + label: '鼓楼区', + value: 350102, + }, + { + children: [], + label: '台江区', + value: 350103, + }, + { + children: [], + label: '仓山区', + value: 350104, + }, + { + children: [], + label: '马尾区', + value: 350105, + }, + { + children: [], + label: '晋安区', + value: 350111, + }, + { + children: [], + label: '长乐区', + value: 350112, + }, + { + children: [], + label: '闽侯县', + value: 350121, + }, + { + children: [], + label: '连江县', + value: 350122, + }, + { + children: [], + label: '罗源县', + value: 350123, + }, + { + children: [], + label: '闽清县', + value: 350124, + }, + { + children: [], + label: '永泰县', + value: 350125, + }, + { + children: [], + label: '平潭县', + value: 350128, + }, + { + children: [], + label: '福清市', + value: 350181, + }, + ], + label: '福州市', + value: 350100, + }, + { + children: [ + { + children: [], + label: '思明区', + value: 350203, + }, + { + children: [], + label: '海沧区', + value: 350205, + }, + { + children: [], + label: '湖里区', + value: 350206, + }, + { + children: [], + label: '集美区', + value: 350211, + }, + { + children: [], + label: '同安区', + value: 350212, + }, + { + children: [], + label: '翔安区', + value: 350213, + }, + ], + label: '厦门市', + value: 350200, + }, + { + children: [ + { + children: [], + label: '城厢区', + value: 350302, + }, + { + children: [], + label: '涵江区', + value: 350303, + }, + { + children: [], + label: '荔城区', + value: 350304, + }, + { + children: [], + label: '秀屿区', + value: 350305, + }, + { + children: [], + label: '仙游县', + value: 350322, + }, + ], + label: '莆田市', + value: 350300, + }, + { + children: [ + { + children: [], + label: '梅列区', + value: 350402, + }, + { + children: [], + label: '三元区', + value: 350403, + }, + { + children: [], + label: '明溪县', + value: 350421, + }, + { + children: [], + label: '清流县', + value: 350423, + }, + { + children: [], + label: '宁化县', + value: 350424, + }, + { + children: [], + label: '大田县', + value: 350425, + }, + { + children: [], + label: '尤溪县', + value: 350426, + }, + { + children: [], + label: '沙县', + value: 350427, + }, + { + children: [], + label: '将乐县', + value: 350428, + }, + { + children: [], + label: '泰宁县', + value: 350429, + }, + { + children: [], + label: '建宁县', + value: 350430, + }, + { + children: [], + label: '永安市', + value: 350481, + }, + ], + label: '三明市', + value: 350400, + }, + { + children: [ + { + children: [], + label: '鲤城区', + value: 350502, + }, + { + children: [], + label: '丰泽区', + value: 350503, + }, + { + children: [], + label: '洛江区', + value: 350504, + }, + { + children: [], + label: '泉港区', + value: 350505, + }, + { + children: [], + label: '惠安县', + value: 350521, + }, + { + children: [], + label: '安溪县', + value: 350524, + }, + { + children: [], + label: '永春县', + value: 350525, + }, + { + children: [], + label: '德化县', + value: 350526, + }, + { + children: [], + label: '金门县', + value: 350527, + }, + { + children: [], + label: '石狮市', + value: 350581, + }, + { + children: [], + label: '晋江市', + value: 350582, + }, + { + children: [], + label: '南安市', + value: 350583, + }, + ], + label: '泉州市', + value: 350500, + }, + { + children: [ + { + children: [], + label: '芗城区', + value: 350602, + }, + { + children: [], + label: '龙文区', + value: 350603, + }, + { + children: [], + label: '云霄县', + value: 350622, + }, + { + children: [], + label: '漳浦县', + value: 350623, + }, + { + children: [], + label: '诏安县', + value: 350624, + }, + { + children: [], + label: '长泰县', + value: 350625, + }, + { + children: [], + label: '东山县', + value: 350626, + }, + { + children: [], + label: '南靖县', + value: 350627, + }, + { + children: [], + label: '平和县', + value: 350628, + }, + { + children: [], + label: '华安县', + value: 350629, + }, + { + children: [], + label: '龙海市', + value: 350681, + }, + ], + label: '漳州市', + value: 350600, + }, + { + children: [ + { + children: [], + label: '延平区', + value: 350702, + }, + { + children: [], + label: '建阳区', + value: 350703, + }, + { + children: [], + label: '顺昌县', + value: 350721, + }, + { + children: [], + label: '浦城县', + value: 350722, + }, + { + children: [], + label: '光泽县', + value: 350723, + }, + { + children: [], + label: '松溪县', + value: 350724, + }, + { + children: [], + label: '政和县', + value: 350725, + }, + { + children: [], + label: '邵武市', + value: 350781, + }, + { + children: [], + label: '武夷山市', + value: 350782, + }, + { + children: [], + label: '建瓯市', + value: 350783, + }, + ], + label: '南平市', + value: 350700, + }, + { + children: [ + { + children: [], + label: '新罗区', + value: 350802, + }, + { + children: [], + label: '永定区', + value: 350803, + }, + { + children: [], + label: '长汀县', + value: 350821, + }, + { + children: [], + label: '上杭县', + value: 350823, + }, + { + children: [], + label: '武平县', + value: 350824, + }, + { + children: [], + label: '连城县', + value: 350825, + }, + { + children: [], + label: '漳平市', + value: 350881, + }, + ], + label: '龙岩市', + value: 350800, + }, + { + children: [ + { + children: [], + label: '蕉城区', + value: 350902, + }, + { + children: [], + label: '霞浦县', + value: 350921, + }, + { + children: [], + label: '古田县', + value: 350922, + }, + { + children: [], + label: '屏南县', + value: 350923, + }, + { + children: [], + label: '寿宁县', + value: 350924, + }, + { + children: [], + label: '周宁县', + value: 350925, + }, + { + children: [], + label: '柘荣县', + value: 350926, + }, + { + children: [], + label: '福安市', + value: 350981, + }, + { + children: [], + label: '福鼎市', + value: 350982, + }, + ], + label: '宁德市', + value: 350900, + }, + ], + label: '福建省', + value: 350000, + }, + { + children: [ + { + children: [ + { + children: [], + label: '东湖区', + value: 360102, + }, + { + children: [], + label: '西湖区', + value: 360103, + }, + { + children: [], + label: '青云谱区', + value: 360104, + }, + { + children: [], + label: '湾里区', + value: 360105, + }, + { + children: [], + label: '青山湖区', + value: 360111, + }, + { + children: [], + label: '新建区', + value: 360112, + }, + { + children: [], + label: '南昌县', + value: 360121, + }, + { + children: [], + label: '安义县', + value: 360123, + }, + { + children: [], + label: '进贤县', + value: 360124, + }, + ], + label: '南昌市', + value: 360100, + }, + { + children: [ + { + children: [], + label: '昌江区', + value: 360202, + }, + { + children: [], + label: '珠山区', + value: 360203, + }, + { + children: [], + label: '浮梁县', + value: 360222, + }, + { + children: [], + label: '乐平市', + value: 360281, + }, + ], + label: '景德镇市', + value: 360200, + }, + { + children: [ + { + children: [], + label: '安源区', + value: 360302, + }, + { + children: [], + label: '湘东区', + value: 360313, + }, + { + children: [], + label: '莲花县', + value: 360321, + }, + { + children: [], + label: '上栗县', + value: 360322, + }, + { + children: [], + label: '芦溪县', + value: 360323, + }, + ], + label: '萍乡市', + value: 360300, + }, + { + children: [ + { + children: [], + label: '濂溪区', + value: 360402, + }, + { + children: [], + label: '浔阳区', + value: 360403, + }, + { + children: [], + label: '柴桑区', + value: 360404, + }, + { + children: [], + label: '武宁县', + value: 360423, + }, + { + children: [], + label: '修水县', + value: 360424, + }, + { + children: [], + label: '永修县', + value: 360425, + }, + { + children: [], + label: '德安县', + value: 360426, + }, + { + children: [], + label: '都昌县', + value: 360428, + }, + { + children: [], + label: '湖口县', + value: 360429, + }, + { + children: [], + label: '彭泽县', + value: 360430, + }, + { + children: [], + label: '瑞昌市', + value: 360481, + }, + { + children: [], + label: '共青城市', + value: 360482, + }, + { + children: [], + label: '庐山市', + value: 360483, + }, + ], + label: '九江市', + value: 360400, + }, + { + children: [ + { + children: [], + label: '渝水区', + value: 360502, + }, + { + children: [], + label: '分宜县', + value: 360521, + }, + ], + label: '新余市', + value: 360500, + }, + { + children: [ + { + children: [], + label: '月湖区', + value: 360602, + }, + { + children: [], + label: '余江区', + value: 360603, + }, + { + children: [], + label: '贵溪市', + value: 360681, + }, + ], + label: '鹰潭市', + value: 360600, + }, + { + children: [ + { + children: [], + label: '章贡区', + value: 360702, + }, + { + children: [], + label: '南康区', + value: 360703, + }, + { + children: [], + label: '赣县区', + value: 360704, + }, + { + children: [], + label: '信丰县', + value: 360722, + }, + { + children: [], + label: '大余县', + value: 360723, + }, + { + children: [], + label: '上犹县', + value: 360724, + }, + { + children: [], + label: '崇义县', + value: 360725, + }, + { + children: [], + label: '安远县', + value: 360726, + }, + { + children: [], + label: '龙南县', + value: 360727, + }, + { + children: [], + label: '定南县', + value: 360728, + }, + { + children: [], + label: '全南县', + value: 360729, + }, + { + children: [], + label: '宁都县', + value: 360730, + }, + { + children: [], + label: '于都县', + value: 360731, + }, + { + children: [], + label: '兴国县', + value: 360732, + }, + { + children: [], + label: '会昌县', + value: 360733, + }, + { + children: [], + label: '寻乌县', + value: 360734, + }, + { + children: [], + label: '石城县', + value: 360735, + }, + { + children: [], + label: '瑞金市', + value: 360781, + }, + ], + label: '赣州市', + value: 360700, + }, + { + children: [ + { + children: [], + label: '吉州区', + value: 360802, + }, + { + children: [], + label: '青原区', + value: 360803, + }, + { + children: [], + label: '吉安县', + value: 360821, + }, + { + children: [], + label: '吉水县', + value: 360822, + }, + { + children: [], + label: '峡江县', + value: 360823, + }, + { + children: [], + label: '新干县', + value: 360824, + }, + { + children: [], + label: '永丰县', + value: 360825, + }, + { + children: [], + label: '泰和县', + value: 360826, + }, + { + children: [], + label: '遂川县', + value: 360827, + }, + { + children: [], + label: '万安县', + value: 360828, + }, + { + children: [], + label: '安福县', + value: 360829, + }, + { + children: [], + label: '永新县', + value: 360830, + }, + { + children: [], + label: '井冈山市', + value: 360881, + }, + ], + label: '吉安市', + value: 360800, + }, + { + children: [ + { + children: [], + label: '袁州区', + value: 360902, + }, + { + children: [], + label: '奉新县', + value: 360921, + }, + { + children: [], + label: '万载县', + value: 360922, + }, + { + children: [], + label: '上高县', + value: 360923, + }, + { + children: [], + label: '宜丰县', + value: 360924, + }, + { + children: [], + label: '靖安县', + value: 360925, + }, + { + children: [], + label: '铜鼓县', + value: 360926, + }, + { + children: [], + label: '丰城市', + value: 360981, + }, + { + children: [], + label: '樟树市', + value: 360982, + }, + { + children: [], + label: '高安市', + value: 360983, + }, + ], + label: '宜春市', + value: 360900, + }, + { + children: [ + { + children: [], + label: '临川区', + value: 361002, + }, + { + children: [], + label: '东乡区', + value: 361003, + }, + { + children: [], + label: '南城县', + value: 361021, + }, + { + children: [], + label: '黎川县', + value: 361022, + }, + { + children: [], + label: '南丰县', + value: 361023, + }, + { + children: [], + label: '崇仁县', + value: 361024, + }, + { + children: [], + label: '乐安县', + value: 361025, + }, + { + children: [], + label: '宜黄县', + value: 361026, + }, + { + children: [], + label: '金溪县', + value: 361027, + }, + { + children: [], + label: '资溪县', + value: 361028, + }, + { + children: [], + label: '广昌县', + value: 361030, + }, + ], + label: '抚州市', + value: 361000, + }, + { + children: [ + { + children: [], + label: '信州区', + value: 361102, + }, + { + children: [], + label: '广丰区', + value: 361103, + }, + { + children: [], + label: '上饶县', + value: 361121, + }, + { + children: [], + label: '玉山县', + value: 361123, + }, + { + children: [], + label: '铅山县', + value: 361124, + }, + { + children: [], + label: '横峰县', + value: 361125, + }, + { + children: [], + label: '弋阳县', + value: 361126, + }, + { + children: [], + label: '余干县', + value: 361127, + }, + { + children: [], + label: '鄱阳县', + value: 361128, + }, + { + children: [], + label: '万年县', + value: 361129, + }, + { + children: [], + label: '婺源县', + value: 361130, + }, + { + children: [], + label: '德兴市', + value: 361181, + }, + ], + label: '上饶市', + value: 361100, + }, + ], + label: '江西省', + value: 360000, + }, + { + children: [ + { + children: [ + { + children: [], + label: '历下区', + value: 370102, + }, + { + children: [], + label: '市中区', + value: 370103, + }, + { + children: [], + label: '槐荫区', + value: 370104, + }, + { + children: [], + label: '天桥区', + value: 370105, + }, + { + children: [], + label: '历城区', + value: 370112, + }, + { + children: [], + label: '长清区', + value: 370113, + }, + { + children: [], + label: '章丘区', + value: 370114, + }, + { + children: [], + label: '济阳区', + value: 370115, + }, + { + children: [], + label: '莱芜区', + value: 370116, + }, + { + children: [], + label: '钢城区', + value: 370117, + }, + { + children: [], + label: '平阴县', + value: 370124, + }, + { + children: [], + label: '商河县', + value: 370126, + }, + ], + label: '济南市', + value: 370100, + }, + { + children: [ + { + children: [], + label: '市南区', + value: 370202, + }, + { + children: [], + label: '市北区', + value: 370203, + }, + { + children: [], + label: '黄岛区', + value: 370211, + }, + { + children: [], + label: '崂山区', + value: 370212, + }, + { + children: [], + label: '李沧区', + value: 370213, + }, + { + children: [], + label: '城阳区', + value: 370214, + }, + { + children: [], + label: '即墨区', + value: 370215, + }, + { + children: [], + label: '胶州市', + value: 370281, + }, + { + children: [], + label: '平度市', + value: 370283, + }, + { + children: [], + label: '莱西市', + value: 370285, + }, + ], + label: '青岛市', + value: 370200, + }, + { + children: [ + { + children: [], + label: '淄川区', + value: 370302, + }, + { + children: [], + label: '张店区', + value: 370303, + }, + { + children: [], + label: '博山区', + value: 370304, + }, + { + children: [], + label: '临淄区', + value: 370305, + }, + { + children: [], + label: '周村区', + value: 370306, + }, + { + children: [], + label: '桓台县', + value: 370321, + }, + { + children: [], + label: '高青县', + value: 370322, + }, + { + children: [], + label: '沂源县', + value: 370323, + }, + ], + label: '淄博市', + value: 370300, + }, + { + children: [ + { + children: [], + label: '市中区', + value: 370402, + }, + { + children: [], + label: '薛城区', + value: 370403, + }, + { + children: [], + label: '峄城区', + value: 370404, + }, + { + children: [], + label: '台儿庄区', + value: 370405, + }, + { + children: [], + label: '山亭区', + value: 370406, + }, + { + children: [], + label: '滕州市', + value: 370481, + }, + ], + label: '枣庄市', + value: 370400, + }, + { + children: [ + { + children: [], + label: '东营区', + value: 370502, + }, + { + children: [], + label: '河口区', + value: 370503, + }, + { + children: [], + label: '垦利区', + value: 370505, + }, + { + children: [], + label: '利津县', + value: 370522, + }, + { + children: [], + label: '广饶县', + value: 370523, + }, + ], + label: '东营市', + value: 370500, + }, + { + children: [ + { + children: [], + label: '芝罘区', + value: 370602, + }, + { + children: [], + label: '福山区', + value: 370611, + }, + { + children: [], + label: '牟平区', + value: 370612, + }, + { + children: [], + label: '莱山区', + value: 370613, + }, + { + children: [], + label: '长岛县', + value: 370634, + }, + { + children: [], + label: '龙口市', + value: 370681, + }, + { + children: [], + label: '莱阳市', + value: 370682, + }, + { + children: [], + label: '莱州市', + value: 370683, + }, + { + children: [], + label: '蓬莱市', + value: 370684, + }, + { + children: [], + label: '招远市', + value: 370685, + }, + { + children: [], + label: '栖霞市', + value: 370686, + }, + { + children: [], + label: '海阳市', + value: 370687, + }, + ], + label: '烟台市', + value: 370600, + }, + { + children: [ + { + children: [], + label: '潍城区', + value: 370702, + }, + { + children: [], + label: '寒亭区', + value: 370703, + }, + { + children: [], + label: '坊子区', + value: 370704, + }, + { + children: [], + label: '奎文区', + value: 370705, + }, + { + children: [], + label: '临朐县', + value: 370724, + }, + { + children: [], + label: '昌乐县', + value: 370725, + }, + { + children: [], + label: '青州市', + value: 370781, + }, + { + children: [], + label: '诸城市', + value: 370782, + }, + { + children: [], + label: '寿光市', + value: 370783, + }, + { + children: [], + label: '安丘市', + value: 370784, + }, + { + children: [], + label: '高密市', + value: 370785, + }, + { + children: [], + label: '昌邑市', + value: 370786, + }, + ], + label: '潍坊市', + value: 370700, + }, + { + children: [ + { + children: [], + label: '任城区', + value: 370811, + }, + { + children: [], + label: '兖州区', + value: 370812, + }, + { + children: [], + label: '微山县', + value: 370826, + }, + { + children: [], + label: '鱼台县', + value: 370827, + }, + { + children: [], + label: '金乡县', + value: 370828, + }, + { + children: [], + label: '嘉祥县', + value: 370829, + }, + { + children: [], + label: '汶上县', + value: 370830, + }, + { + children: [], + label: '泗水县', + value: 370831, + }, + { + children: [], + label: '梁山县', + value: 370832, + }, + { + children: [], + label: '曲阜市', + value: 370881, + }, + { + children: [], + label: '邹城市', + value: 370883, + }, + ], + label: '济宁市', + value: 370800, + }, + { + children: [ + { + children: [], + label: '泰山区', + value: 370902, + }, + { + children: [], + label: '岱岳区', + value: 370911, + }, + { + children: [], + label: '宁阳县', + value: 370921, + }, + { + children: [], + label: '东平县', + value: 370923, + }, + { + children: [], + label: '新泰市', + value: 370982, + }, + { + children: [], + label: '肥城市', + value: 370983, + }, + ], + label: '泰安市', + value: 370900, + }, + { + children: [ + { + children: [], + label: '环翠区', + value: 371002, + }, + { + children: [], + label: '文登区', + value: 371003, + }, + { + children: [], + label: '荣成市', + value: 371082, + }, + { + children: [], + label: '乳山市', + value: 371083, + }, + ], + label: '威海市', + value: 371000, + }, + { + children: [ + { + children: [], + label: '东港区', + value: 371102, + }, + { + children: [], + label: '岚山区', + value: 371103, + }, + { + children: [], + label: '五莲县', + value: 371121, + }, + { + children: [], + label: '莒县', + value: 371122, + }, + ], + label: '日照市', + value: 371100, + }, + { + children: [ + { + children: [], + label: '兰山区', + value: 371302, + }, + { + children: [], + label: '罗庄区', + value: 371311, + }, + { + children: [], + label: '河东区', + value: 371312, + }, + { + children: [], + label: '沂南县', + value: 371321, + }, + { + children: [], + label: '郯城县', + value: 371322, + }, + { + children: [], + label: '沂水县', + value: 371323, + }, + { + children: [], + label: '兰陵县', + value: 371324, + }, + { + children: [], + label: '费县', + value: 371325, + }, + { + children: [], + label: '平邑县', + value: 371326, + }, + { + children: [], + label: '莒南县', + value: 371327, + }, + { + children: [], + label: '蒙阴县', + value: 371328, + }, + { + children: [], + label: '临沭县', + value: 371329, + }, + ], + label: '临沂市', + value: 371300, + }, + { + children: [ + { + children: [], + label: '德城区', + value: 371402, + }, + { + children: [], + label: '陵城区', + value: 371403, + }, + { + children: [], + label: '宁津县', + value: 371422, + }, + { + children: [], + label: '庆云县', + value: 371423, + }, + { + children: [], + label: '临邑县', + value: 371424, + }, + { + children: [], + label: '齐河县', + value: 371425, + }, + { + children: [], + label: '平原县', + value: 371426, + }, + { + children: [], + label: '夏津县', + value: 371427, + }, + { + children: [], + label: '武城县', + value: 371428, + }, + { + children: [], + label: '乐陵市', + value: 371481, + }, + { + children: [], + label: '禹城市', + value: 371482, + }, + ], + label: '德州市', + value: 371400, + }, + { + children: [ + { + children: [], + label: '东昌府区', + value: 371502, + }, + { + children: [], + label: '阳谷县', + value: 371521, + }, + { + children: [], + label: '莘县', + value: 371522, + }, + { + children: [], + label: '茌平县', + value: 371523, + }, + { + children: [], + label: '东阿县', + value: 371524, + }, + { + children: [], + label: '冠县', + value: 371525, + }, + { + children: [], + label: '高唐县', + value: 371526, + }, + { + children: [], + label: '临清市', + value: 371581, + }, + ], + label: '聊城市', + value: 371500, + }, + { + children: [ + { + children: [], + label: '滨城区', + value: 371602, + }, + { + children: [], + label: '沾化区', + value: 371603, + }, + { + children: [], + label: '惠民县', + value: 371621, + }, + { + children: [], + label: '阳信县', + value: 371622, + }, + { + children: [], + label: '无棣县', + value: 371623, + }, + { + children: [], + label: '博兴县', + value: 371625, + }, + { + children: [], + label: '邹平市', + value: 371681, + }, + ], + label: '滨州市', + value: 371600, + }, + { + children: [ + { + children: [], + label: '牡丹区', + value: 371702, + }, + { + children: [], + label: '定陶区', + value: 371703, + }, + { + children: [], + label: '曹县', + value: 371721, + }, + { + children: [], + label: '单县', + value: 371722, + }, + { + children: [], + label: '成武县', + value: 371723, + }, + { + children: [], + label: '巨野县', + value: 371724, + }, + { + children: [], + label: '郓城县', + value: 371725, + }, + { + children: [], + label: '鄄城县', + value: 371726, + }, + { + children: [], + label: '东明县', + value: 371728, + }, + ], + label: '菏泽市', + value: 371700, + }, + ], + label: '山东省', + value: 370000, + }, + { + children: [ + { + children: [ + { + children: [], + label: '中原区', + value: 410102, + }, + { + children: [], + label: '二七区', + value: 410103, + }, + { + children: [], + label: '管城回族区', + value: 410104, + }, + { + children: [], + label: '金水区', + value: 410105, + }, + { + children: [], + label: '上街区', + value: 410106, + }, + { + children: [], + label: '惠济区', + value: 410108, + }, + { + children: [], + label: '中牟县', + value: 410122, + }, + { + children: [], + label: '巩义市', + value: 410181, + }, + { + children: [], + label: '荥阳市', + value: 410182, + }, + { + children: [], + label: '新密市', + value: 410183, + }, + { + children: [], + label: '新郑市', + value: 410184, + }, + { + children: [], + label: '登封市', + value: 410185, + }, + ], + label: '郑州市', + value: 410100, + }, + { + children: [ + { + children: [], + label: '龙亭区', + value: 410202, + }, + { + children: [], + label: '顺河回族区', + value: 410203, + }, + { + children: [], + label: '鼓楼区', + value: 410204, + }, + { + children: [], + label: '禹王台区', + value: 410205, + }, + { + children: [], + label: '祥符区', + value: 410212, + }, + { + children: [], + label: '杞县', + value: 410221, + }, + { + children: [], + label: '通许县', + value: 410222, + }, + { + children: [], + label: '尉氏县', + value: 410223, + }, + { + children: [], + label: '兰考县', + value: 410225, + }, + ], + label: '开封市', + value: 410200, + }, + { + children: [ + { + children: [], + label: '老城区', + value: 410302, + }, + { + children: [], + label: '西工区', + value: 410303, + }, + { + children: [], + label: '瀍河回族区', + value: 410304, + }, + { + children: [], + label: '涧西区', + value: 410305, + }, + { + children: [], + label: '吉利区', + value: 410306, + }, + { + children: [], + label: '洛龙区', + value: 410311, + }, + { + children: [], + label: '孟津县', + value: 410322, + }, + { + children: [], + label: '新安县', + value: 410323, + }, + { + children: [], + label: '栾川县', + value: 410324, + }, + { + children: [], + label: '嵩县', + value: 410325, + }, + { + children: [], + label: '汝阳县', + value: 410326, + }, + { + children: [], + label: '宜阳县', + value: 410327, + }, + { + children: [], + label: '洛宁县', + value: 410328, + }, + { + children: [], + label: '伊川县', + value: 410329, + }, + { + children: [], + label: '偃师市', + value: 410381, + }, + ], + label: '洛阳市', + value: 410300, + }, + { + children: [ + { + children: [], + label: '新华区', + value: 410402, + }, + { + children: [], + label: '卫东区', + value: 410403, + }, + { + children: [], + label: '石龙区', + value: 410404, + }, + { + children: [], + label: '湛河区', + value: 410411, + }, + { + children: [], + label: '宝丰县', + value: 410421, + }, + { + children: [], + label: '叶县', + value: 410422, + }, + { + children: [], + label: '鲁山县', + value: 410423, + }, + { + children: [], + label: '郏县', + value: 410425, + }, + { + children: [], + label: '舞钢市', + value: 410481, + }, + { + children: [], + label: '汝州市', + value: 410482, + }, + ], + label: '平顶山市', + value: 410400, + }, + { + children: [ + { + children: [], + label: '文峰区', + value: 410502, + }, + { + children: [], + label: '北关区', + value: 410503, + }, + { + children: [], + label: '殷都区', + value: 410505, + }, + { + children: [], + label: '龙安区', + value: 410506, + }, + { + children: [], + label: '安阳县', + value: 410522, + }, + { + children: [], + label: '汤阴县', + value: 410523, + }, + { + children: [], + label: '滑县', + value: 410526, + }, + { + children: [], + label: '内黄县', + value: 410527, + }, + { + children: [], + label: '林州市', + value: 410581, + }, + ], + label: '安阳市', + value: 410500, + }, + { + children: [ + { + children: [], + label: '鹤山区', + value: 410602, + }, + { + children: [], + label: '山城区', + value: 410603, + }, + { + children: [], + label: '淇滨区', + value: 410611, + }, + { + children: [], + label: '浚县', + value: 410621, + }, + { + children: [], + label: '淇县', + value: 410622, + }, + ], + label: '鹤壁市', + value: 410600, + }, + { + children: [ + { + children: [], + label: '红旗区', + value: 410702, + }, + { + children: [], + label: '卫滨区', + value: 410703, + }, + { + children: [], + label: '凤泉区', + value: 410704, + }, + { + children: [], + label: '牧野区', + value: 410711, + }, + { + children: [], + label: '新乡县', + value: 410721, + }, + { + children: [], + label: '获嘉县', + value: 410724, + }, + { + children: [], + label: '原阳县', + value: 410725, + }, + { + children: [], + label: '延津县', + value: 410726, + }, + { + children: [], + label: '封丘县', + value: 410727, + }, + { + children: [], + label: '长垣县', + value: 410728, + }, + { + children: [], + label: '卫辉市', + value: 410781, + }, + { + children: [], + label: '辉县市', + value: 410782, + }, + ], + label: '新乡市', + value: 410700, + }, + { + children: [ + { + children: [], + label: '解放区', + value: 410802, + }, + { + children: [], + label: '中站区', + value: 410803, + }, + { + children: [], + label: '马村区', + value: 410804, + }, + { + children: [], + label: '山阳区', + value: 410811, + }, + { + children: [], + label: '修武县', + value: 410821, + }, + { + children: [], + label: '博爱县', + value: 410822, + }, + { + children: [], + label: '武陟县', + value: 410823, + }, + { + children: [], + label: '温县', + value: 410825, + }, + { + children: [], + label: '沁阳市', + value: 410882, + }, + { + children: [], + label: '孟州市', + value: 410883, + }, + ], + label: '焦作市', + value: 410800, + }, + { + children: [ + { + children: [], + label: '华龙区', + value: 410902, + }, + { + children: [], + label: '清丰县', + value: 410922, + }, + { + children: [], + label: '南乐县', + value: 410923, + }, + { + children: [], + label: '范县', + value: 410926, + }, + { + children: [], + label: '台前县', + value: 410927, + }, + { + children: [], + label: '濮阳县', + value: 410928, + }, + ], + label: '濮阳市', + value: 410900, + }, + { + children: [ + { + children: [], + label: '魏都区', + value: 411002, + }, + { + children: [], + label: '建安区', + value: 411003, + }, + { + children: [], + label: '鄢陵县', + value: 411024, + }, + { + children: [], + label: '襄城县', + value: 411025, + }, + { + children: [], + label: '禹州市', + value: 411081, + }, + { + children: [], + label: '长葛市', + value: 411082, + }, + ], + label: '许昌市', + value: 411000, + }, + { + children: [ + { + children: [], + label: '源汇区', + value: 411102, + }, + { + children: [], + label: '郾城区', + value: 411103, + }, + { + children: [], + label: '召陵区', + value: 411104, + }, + { + children: [], + label: '舞阳县', + value: 411121, + }, + { + children: [], + label: '临颍县', + value: 411122, + }, + ], + label: '漯河市', + value: 411100, + }, + { + children: [ + { + children: [], + label: '湖滨区', + value: 411202, + }, + { + children: [], + label: '陕州区', + value: 411203, + }, + { + children: [], + label: '渑池县', + value: 411221, + }, + { + children: [], + label: '卢氏县', + value: 411224, + }, + { + children: [], + label: '义马市', + value: 411281, + }, + { + children: [], + label: '灵宝市', + value: 411282, + }, + ], + label: '三门峡市', + value: 411200, + }, + { + children: [ + { + children: [], + label: '宛城区', + value: 411302, + }, + { + children: [], + label: '卧龙区', + value: 411303, + }, + { + children: [], + label: '南召县', + value: 411321, + }, + { + children: [], + label: '方城县', + value: 411322, + }, + { + children: [], + label: '西峡县', + value: 411323, + }, + { + children: [], + label: '镇平县', + value: 411324, + }, + { + children: [], + label: '内乡县', + value: 411325, + }, + { + children: [], + label: '淅川县', + value: 411326, + }, + { + children: [], + label: '社旗县', + value: 411327, + }, + { + children: [], + label: '唐河县', + value: 411328, + }, + { + children: [], + label: '新野县', + value: 411329, + }, + { + children: [], + label: '桐柏县', + value: 411330, + }, + { + children: [], + label: '邓州市', + value: 411381, + }, + ], + label: '南阳市', + value: 411300, + }, + { + children: [ + { + children: [], + label: '梁园区', + value: 411402, + }, + { + children: [], + label: '睢阳区', + value: 411403, + }, + { + children: [], + label: '民权县', + value: 411421, + }, + { + children: [], + label: '睢县', + value: 411422, + }, + { + children: [], + label: '宁陵县', + value: 411423, + }, + { + children: [], + label: '柘城县', + value: 411424, + }, + { + children: [], + label: '虞城县', + value: 411425, + }, + { + children: [], + label: '夏邑县', + value: 411426, + }, + { + children: [], + label: '永城市', + value: 411481, + }, + ], + label: '商丘市', + value: 411400, + }, + { + children: [ + { + children: [], + label: '浉河区', + value: 411502, + }, + { + children: [], + label: '平桥区', + value: 411503, + }, + { + children: [], + label: '罗山县', + value: 411521, + }, + { + children: [], + label: '光山县', + value: 411522, + }, + { + children: [], + label: '新县', + value: 411523, + }, + { + children: [], + label: '商城县', + value: 411524, + }, + { + children: [], + label: '固始县', + value: 411525, + }, + { + children: [], + label: '潢川县', + value: 411526, + }, + { + children: [], + label: '淮滨县', + value: 411527, + }, + { + children: [], + label: '息县', + value: 411528, + }, + ], + label: '信阳市', + value: 411500, + }, + { + children: [ + { + children: [], + label: '川汇区', + value: 411602, + }, + { + children: [], + label: '扶沟县', + value: 411621, + }, + { + children: [], + label: '西华县', + value: 411622, + }, + { + children: [], + label: '商水县', + value: 411623, + }, + { + children: [], + label: '沈丘县', + value: 411624, + }, + { + children: [], + label: '郸城县', + value: 411625, + }, + { + children: [], + label: '淮阳县', + value: 411626, + }, + { + children: [], + label: '太康县', + value: 411627, + }, + { + children: [], + label: '鹿邑县', + value: 411628, + }, + { + children: [], + label: '项城市', + value: 411681, + }, + ], + label: '周口市', + value: 411600, + }, + { + children: [ + { + children: [], + label: '驿城区', + value: 411702, + }, + { + children: [], + label: '西平县', + value: 411721, + }, + { + children: [], + label: '上蔡县', + value: 411722, + }, + { + children: [], + label: '平舆县', + value: 411723, + }, + { + children: [], + label: '正阳县', + value: 411724, + }, + { + children: [], + label: '确山县', + value: 411725, + }, + { + children: [], + label: '泌阳县', + value: 411726, + }, + { + children: [], + label: '汝南县', + value: 411727, + }, + { + children: [], + label: '遂平县', + value: 411728, + }, + { + children: [], + label: '新蔡县', + value: 411729, + }, + ], + label: '驻马店市', + value: 411700, + }, + { + children: [], + label: '济源市', + value: 419001, + }, + ], + label: '河南省', + value: 410000, + }, + { + children: [ + { + children: [ + { + children: [], + label: '江岸区', + value: 420102, + }, + { + children: [], + label: '江汉区', + value: 420103, + }, + { + children: [], + label: '硚口区', + value: 420104, + }, + { + children: [], + label: '汉阳区', + value: 420105, + }, + { + children: [], + label: '武昌区', + value: 420106, + }, + { + children: [], + label: '青山区', + value: 420107, + }, + { + children: [], + label: '洪山区', + value: 420111, + }, + { + children: [], + label: '东西湖区', + value: 420112, + }, + { + children: [], + label: '汉南区', + value: 420113, + }, + { + children: [], + label: '蔡甸区', + value: 420114, + }, + { + children: [], + label: '江夏区', + value: 420115, + }, + { + children: [], + label: '黄陂区', + value: 420116, + }, + { + children: [], + label: '新洲区', + value: 420117, + }, + ], + label: '武汉市', + value: 420100, + }, + { + children: [ + { + children: [], + label: '黄石港区', + value: 420202, + }, + { + children: [], + label: '西塞山区', + value: 420203, + }, + { + children: [], + label: '下陆区', + value: 420204, + }, + { + children: [], + label: '铁山区', + value: 420205, + }, + { + children: [], + label: '阳新县', + value: 420222, + }, + { + children: [], + label: '大冶市', + value: 420281, + }, + ], + label: '黄石市', + value: 420200, + }, + { + children: [ + { + children: [], + label: '茅箭区', + value: 420302, + }, + { + children: [], + label: '张湾区', + value: 420303, + }, + { + children: [], + label: '郧阳区', + value: 420304, + }, + { + children: [], + label: '郧西县', + value: 420322, + }, + { + children: [], + label: '竹山县', + value: 420323, + }, + { + children: [], + label: '竹溪县', + value: 420324, + }, + { + children: [], + label: '房县', + value: 420325, + }, + { + children: [], + label: '丹江口市', + value: 420381, + }, + ], + label: '十堰市', + value: 420300, + }, + { + children: [ + { + children: [], + label: '西陵区', + value: 420502, + }, + { + children: [], + label: '伍家岗区', + value: 420503, + }, + { + children: [], + label: '点军区', + value: 420504, + }, + { + children: [], + label: '猇亭区', + value: 420505, + }, + { + children: [], + label: '夷陵区', + value: 420506, + }, + { + children: [], + label: '远安县', + value: 420525, + }, + { + children: [], + label: '兴山县', + value: 420526, + }, + { + children: [], + label: '秭归县', + value: 420527, + }, + { + children: [], + label: '长阳土家族自治县', + value: 420528, + }, + { + children: [], + label: '五峰土家族自治县', + value: 420529, + }, + { + children: [], + label: '宜都市', + value: 420581, + }, + { + children: [], + label: '当阳市', + value: 420582, + }, + { + children: [], + label: '枝江市', + value: 420583, + }, + ], + label: '宜昌市', + value: 420500, + }, + { + children: [ + { + children: [], + label: '襄城区', + value: 420602, + }, + { + children: [], + label: '樊城区', + value: 420606, + }, + { + children: [], + label: '襄州区', + value: 420607, + }, + { + children: [], + label: '南漳县', + value: 420624, + }, + { + children: [], + label: '谷城县', + value: 420625, + }, + { + children: [], + label: '保康县', + value: 420626, + }, + { + children: [], + label: '老河口市', + value: 420682, + }, + { + children: [], + label: '枣阳市', + value: 420683, + }, + { + children: [], + label: '宜城市', + value: 420684, + }, + ], + label: '襄阳市', + value: 420600, + }, + { + children: [ + { + children: [], + label: '梁子湖区', + value: 420702, + }, + { + children: [], + label: '华容区', + value: 420703, + }, + { + children: [], + label: '鄂城区', + value: 420704, + }, + ], + label: '鄂州市', + value: 420700, + }, + { + children: [ + { + children: [], + label: '东宝区', + value: 420802, + }, + { + children: [], + label: '掇刀区', + value: 420804, + }, + { + children: [], + label: '沙洋县', + value: 420822, + }, + { + children: [], + label: '钟祥市', + value: 420881, + }, + { + children: [], + label: '京山市', + value: 420882, + }, + ], + label: '荆门市', + value: 420800, + }, + { + children: [ + { + children: [], + label: '孝南区', + value: 420902, + }, + { + children: [], + label: '孝昌县', + value: 420921, + }, + { + children: [], + label: '大悟县', + value: 420922, + }, + { + children: [], + label: '云梦县', + value: 420923, + }, + { + children: [], + label: '应城市', + value: 420981, + }, + { + children: [], + label: '安陆市', + value: 420982, + }, + { + children: [], + label: '汉川市', + value: 420984, + }, + ], + label: '孝感市', + value: 420900, + }, + { + children: [ + { + children: [], + label: '沙市区', + value: 421002, + }, + { + children: [], + label: '荆州区', + value: 421003, + }, + { + children: [], + label: '公安县', + value: 421022, + }, + { + children: [], + label: '监利县', + value: 421023, + }, + { + children: [], + label: '江陵县', + value: 421024, + }, + { + children: [], + label: '石首市', + value: 421081, + }, + { + children: [], + label: '洪湖市', + value: 421083, + }, + { + children: [], + label: '松滋市', + value: 421087, + }, + ], + label: '荆州市', + value: 421000, + }, + { + children: [ + { + children: [], + label: '黄州区', + value: 421102, + }, + { + children: [], + label: '团风县', + value: 421121, + }, + { + children: [], + label: '红安县', + value: 421122, + }, + { + children: [], + label: '罗田县', + value: 421123, + }, + { + children: [], + label: '英山县', + value: 421124, + }, + { + children: [], + label: '浠水县', + value: 421125, + }, + { + children: [], + label: '蕲春县', + value: 421126, + }, + { + children: [], + label: '黄梅县', + value: 421127, + }, + { + children: [], + label: '麻城市', + value: 421181, + }, + { + children: [], + label: '武穴市', + value: 421182, + }, + ], + label: '黄冈市', + value: 421100, + }, + { + children: [ + { + children: [], + label: '咸安区', + value: 421202, + }, + { + children: [], + label: '嘉鱼县', + value: 421221, + }, + { + children: [], + label: '通城县', + value: 421222, + }, + { + children: [], + label: '崇阳县', + value: 421223, + }, + { + children: [], + label: '通山县', + value: 421224, + }, + { + children: [], + label: '赤壁市', + value: 421281, + }, + ], + label: '咸宁市', + value: 421200, + }, + { + children: [ + { + children: [], + label: '曾都区', + value: 421303, + }, + { + children: [], + label: '随县', + value: 421321, + }, + { + children: [], + label: '广水市', + value: 421381, + }, + ], + label: '随州市', + value: 421300, + }, + { + children: [ + { + children: [], + label: '恩施市', + value: 422801, + }, + { + children: [], + label: '利川市', + value: 422802, + }, + { + children: [], + label: '建始县', + value: 422822, + }, + { + children: [], + label: '巴东县', + value: 422823, + }, + { + children: [], + label: '宣恩县', + value: 422825, + }, + { + children: [], + label: '咸丰县', + value: 422826, + }, + { + children: [], + label: '来凤县', + value: 422827, + }, + { + children: [], + label: '鹤峰县', + value: 422828, + }, + ], + label: '恩施土家族苗族自治州', + value: 422800, + }, + { + children: [], + label: '仙桃市', + value: 429004, + }, + { + children: [], + label: '潜江市', + value: 429005, + }, + { + children: [], + label: '天门市', + value: 429006, + }, + { + children: [], + label: '神农架林区', + value: 429021, + }, + ], + label: '湖北省', + value: 420000, + }, + { + children: [ + { + children: [ + { + children: [], + label: '芙蓉区', + value: 430102, + }, + { + children: [], + label: '天心区', + value: 430103, + }, + { + children: [], + label: '岳麓区', + value: 430104, + }, + { + children: [], + label: '开福区', + value: 430105, + }, + { + children: [], + label: '雨花区', + value: 430111, + }, + { + children: [], + label: '望城区', + value: 430112, + }, + { + children: [], + label: '长沙县', + value: 430121, + }, + { + children: [], + label: '浏阳市', + value: 430181, + }, + { + children: [], + label: '宁乡市', + value: 430182, + }, + ], + label: '长沙市', + value: 430100, + }, + { + children: [ + { + children: [], + label: '荷塘区', + value: 430202, + }, + { + children: [], + label: '芦淞区', + value: 430203, + }, + { + children: [], + label: '石峰区', + value: 430204, + }, + { + children: [], + label: '天元区', + value: 430211, + }, + { + children: [], + label: '渌口区', + value: 430212, + }, + { + children: [], + label: '攸县', + value: 430223, + }, + { + children: [], + label: '茶陵县', + value: 430224, + }, + { + children: [], + label: '炎陵县', + value: 430225, + }, + { + children: [], + label: '醴陵市', + value: 430281, + }, + ], + label: '株洲市', + value: 430200, + }, + { + children: [ + { + children: [], + label: '雨湖区', + value: 430302, + }, + { + children: [], + label: '岳塘区', + value: 430304, + }, + { + children: [], + label: '湘潭县', + value: 430321, + }, + { + children: [], + label: '湘乡市', + value: 430381, + }, + { + children: [], + label: '韶山市', + value: 430382, + }, + ], + label: '湘潭市', + value: 430300, + }, + { + children: [ + { + children: [], + label: '珠晖区', + value: 430405, + }, + { + children: [], + label: '雁峰区', + value: 430406, + }, + { + children: [], + label: '石鼓区', + value: 430407, + }, + { + children: [], + label: '蒸湘区', + value: 430408, + }, + { + children: [], + label: '南岳区', + value: 430412, + }, + { + children: [], + label: '衡阳县', + value: 430421, + }, + { + children: [], + label: '衡南县', + value: 430422, + }, + { + children: [], + label: '衡山县', + value: 430423, + }, + { + children: [], + label: '衡东县', + value: 430424, + }, + { + children: [], + label: '祁东县', + value: 430426, + }, + { + children: [], + label: '耒阳市', + value: 430481, + }, + { + children: [], + label: '常宁市', + value: 430482, + }, + ], + label: '衡阳市', + value: 430400, + }, + { + children: [ + { + children: [], + label: '双清区', + value: 430502, + }, + { + children: [], + label: '大祥区', + value: 430503, + }, + { + children: [], + label: '北塔区', + value: 430511, + }, + { + children: [], + label: '邵东县', + value: 430521, + }, + { + children: [], + label: '新邵县', + value: 430522, + }, + { + children: [], + label: '邵阳县', + value: 430523, + }, + { + children: [], + label: '隆回县', + value: 430524, + }, + { + children: [], + label: '洞口县', + value: 430525, + }, + { + children: [], + label: '绥宁县', + value: 430527, + }, + { + children: [], + label: '新宁县', + value: 430528, + }, + { + children: [], + label: '城步苗族自治县', + value: 430529, + }, + { + children: [], + label: '武冈市', + value: 430581, + }, + ], + label: '邵阳市', + value: 430500, + }, + { + children: [ + { + children: [], + label: '岳阳楼区', + value: 430602, + }, + { + children: [], + label: '云溪区', + value: 430603, + }, + { + children: [], + label: '君山区', + value: 430611, + }, + { + children: [], + label: '岳阳县', + value: 430621, + }, + { + children: [], + label: '华容县', + value: 430623, + }, + { + children: [], + label: '湘阴县', + value: 430624, + }, + { + children: [], + label: '平江县', + value: 430626, + }, + { + children: [], + label: '汨罗市', + value: 430681, + }, + { + children: [], + label: '临湘市', + value: 430682, + }, + ], + label: '岳阳市', + value: 430600, + }, + { + children: [ + { + children: [], + label: '武陵区', + value: 430702, + }, + { + children: [], + label: '鼎城区', + value: 430703, + }, + { + children: [], + label: '安乡县', + value: 430721, + }, + { + children: [], + label: '汉寿县', + value: 430722, + }, + { + children: [], + label: '澧县', + value: 430723, + }, + { + children: [], + label: '临澧县', + value: 430724, + }, + { + children: [], + label: '桃源县', + value: 430725, + }, + { + children: [], + label: '石门县', + value: 430726, + }, + { + children: [], + label: '津市市', + value: 430781, + }, + ], + label: '常德市', + value: 430700, + }, + { + children: [ + { + children: [], + label: '永定区', + value: 430802, + }, + { + children: [], + label: '武陵源区', + value: 430811, + }, + { + children: [], + label: '慈利县', + value: 430821, + }, + { + children: [], + label: '桑植县', + value: 430822, + }, + ], + label: '张家界市', + value: 430800, + }, + { + children: [ + { + children: [], + label: '资阳区', + value: 430902, + }, + { + children: [], + label: '赫山区', + value: 430903, + }, + { + children: [], + label: '南县', + value: 430921, + }, + { + children: [], + label: '桃江县', + value: 430922, + }, + { + children: [], + label: '安化县', + value: 430923, + }, + { + children: [], + label: '沅江市', + value: 430981, + }, + ], + label: '益阳市', + value: 430900, + }, + { + children: [ + { + children: [], + label: '北湖区', + value: 431002, + }, + { + children: [], + label: '苏仙区', + value: 431003, + }, + { + children: [], + label: '桂阳县', + value: 431021, + }, + { + children: [], + label: '宜章县', + value: 431022, + }, + { + children: [], + label: '永兴县', + value: 431023, + }, + { + children: [], + label: '嘉禾县', + value: 431024, + }, + { + children: [], + label: '临武县', + value: 431025, + }, + { + children: [], + label: '汝城县', + value: 431026, + }, + { + children: [], + label: '桂东县', + value: 431027, + }, + { + children: [], + label: '安仁县', + value: 431028, + }, + { + children: [], + label: '资兴市', + value: 431081, + }, + ], + label: '郴州市', + value: 431000, + }, + { + children: [ + { + children: [], + label: '零陵区', + value: 431102, + }, + { + children: [], + label: '冷水滩区', + value: 431103, + }, + { + children: [], + label: '祁阳县', + value: 431121, + }, + { + children: [], + label: '东安县', + value: 431122, + }, + { + children: [], + label: '双牌县', + value: 431123, + }, + { + children: [], + label: '道县', + value: 431124, + }, + { + children: [], + label: '江永县', + value: 431125, + }, + { + children: [], + label: '宁远县', + value: 431126, + }, + { + children: [], + label: '蓝山县', + value: 431127, + }, + { + children: [], + label: '新田县', + value: 431128, + }, + { + children: [], + label: '江华瑶族自治县', + value: 431129, + }, + ], + label: '永州市', + value: 431100, + }, + { + children: [ + { + children: [], + label: '鹤城区', + value: 431202, + }, + { + children: [], + label: '中方县', + value: 431221, + }, + { + children: [], + label: '沅陵县', + value: 431222, + }, + { + children: [], + label: '辰溪县', + value: 431223, + }, + { + children: [], + label: '溆浦县', + value: 431224, + }, + { + children: [], + label: '会同县', + value: 431225, + }, + { + children: [], + label: '麻阳苗族自治县', + value: 431226, + }, + { + children: [], + label: '新晃侗族自治县', + value: 431227, + }, + { + children: [], + label: '芷江侗族自治县', + value: 431228, + }, + { + children: [], + label: '靖州苗族侗族自治县', + value: 431229, + }, + { + children: [], + label: '通道侗族自治县', + value: 431230, + }, + { + children: [], + label: '洪江市', + value: 431281, + }, + ], + label: '怀化市', + value: 431200, + }, + { + children: [ + { + children: [], + label: '娄星区', + value: 431302, + }, + { + children: [], + label: '双峰县', + value: 431321, + }, + { + children: [], + label: '新化县', + value: 431322, + }, + { + children: [], + label: '冷水江市', + value: 431381, + }, + { + children: [], + label: '涟源市', + value: 431382, + }, + ], + label: '娄底市', + value: 431300, + }, + { + children: [ + { + children: [], + label: '吉首市', + value: 433101, + }, + { + children: [], + label: '泸溪县', + value: 433122, + }, + { + children: [], + label: '凤凰县', + value: 433123, + }, + { + children: [], + label: '花垣县', + value: 433124, + }, + { + children: [], + label: '保靖县', + value: 433125, + }, + { + children: [], + label: '古丈县', + value: 433126, + }, + { + children: [], + label: '永顺县', + value: 433127, + }, + { + children: [], + label: '龙山县', + value: 433130, + }, + ], + label: '湘西土家族苗族自治州', + value: 433100, + }, + ], + label: '湖南省', + value: 430000, + }, + { + children: [ + { + children: [ + { + children: [], + label: '荔湾区', + value: 440103, + }, + { + children: [], + label: '越秀区', + value: 440104, + }, + { + children: [], + label: '海珠区', + value: 440105, + }, + { + children: [], + label: '天河区', + value: 440106, + }, + { + children: [], + label: '白云区', + value: 440111, + }, + { + children: [], + label: '黄埔区', + value: 440112, + }, + { + children: [], + label: '番禺区', + value: 440113, + }, + { + children: [], + label: '花都区', + value: 440114, + }, + { + children: [], + label: '南沙区', + value: 440115, + }, + { + children: [], + label: '从化区', + value: 440117, + }, + { + children: [], + label: '增城区', + value: 440118, + }, + ], + label: '广州市', + value: 440100, + }, + { + children: [ + { + children: [], + label: '武江区', + value: 440203, + }, + { + children: [], + label: '浈江区', + value: 440204, + }, + { + children: [], + label: '曲江区', + value: 440205, + }, + { + children: [], + label: '始兴县', + value: 440222, + }, + { + children: [], + label: '仁化县', + value: 440224, + }, + { + children: [], + label: '翁源县', + value: 440229, + }, + { + children: [], + label: '乳源瑶族自治县', + value: 440232, + }, + { + children: [], + label: '新丰县', + value: 440233, + }, + { + children: [], + label: '乐昌市', + value: 440281, + }, + { + children: [], + label: '南雄市', + value: 440282, + }, + ], + label: '韶关市', + value: 440200, + }, + { + children: [ + { + children: [], + label: '罗湖区', + value: 440303, + }, + { + children: [], + label: '福田区', + value: 440304, + }, + { + children: [], + label: '南山区', + value: 440305, + }, + { + children: [], + label: '宝安区', + value: 440306, + }, + { + children: [], + label: '龙岗区', + value: 440307, + }, + { + children: [], + label: '盐田区', + value: 440308, + }, + { + children: [], + label: '龙华区', + value: 440309, + }, + { + children: [], + label: '坪山区', + value: 440310, + }, + { + children: [], + label: '光明区', + value: 440311, + }, + ], + label: '深圳市', + value: 440300, + }, + { + children: [ + { + children: [], + label: '香洲区', + value: 440402, + }, + { + children: [], + label: '斗门区', + value: 440403, + }, + { + children: [], + label: '金湾区', + value: 440404, + }, + ], + label: '珠海市', + value: 440400, + }, + { + children: [ + { + children: [], + label: '龙湖区', + value: 440507, + }, + { + children: [], + label: '金平区', + value: 440511, + }, + { + children: [], + label: '濠江区', + value: 440512, + }, + { + children: [], + label: '潮阳区', + value: 440513, + }, + { + children: [], + label: '潮南区', + value: 440514, + }, + { + children: [], + label: '澄海区', + value: 440515, + }, + { + children: [], + label: '南澳县', + value: 440523, + }, + ], + label: '汕头市', + value: 440500, + }, + { + children: [ + { + children: [], + label: '禅城区', + value: 440604, + }, + { + children: [], + label: '南海区', + value: 440605, + }, + { + children: [], + label: '顺德区', + value: 440606, + }, + { + children: [], + label: '三水区', + value: 440607, + }, + { + children: [], + label: '高明区', + value: 440608, + }, + ], + label: '佛山市', + value: 440600, + }, + { + children: [ + { + children: [], + label: '蓬江区', + value: 440703, + }, + { + children: [], + label: '江海区', + value: 440704, + }, + { + children: [], + label: '新会区', + value: 440705, + }, + { + children: [], + label: '台山市', + value: 440781, + }, + { + children: [], + label: '开平市', + value: 440783, + }, + { + children: [], + label: '鹤山市', + value: 440784, + }, + { + children: [], + label: '恩平市', + value: 440785, + }, + ], + label: '江门市', + value: 440700, + }, + { + children: [ + { + children: [], + label: '赤坎区', + value: 440802, + }, + { + children: [], + label: '霞山区', + value: 440803, + }, + { + children: [], + label: '坡头区', + value: 440804, + }, + { + children: [], + label: '麻章区', + value: 440811, + }, + { + children: [], + label: '遂溪县', + value: 440823, + }, + { + children: [], + label: '徐闻县', + value: 440825, + }, + { + children: [], + label: '廉江市', + value: 440881, + }, + { + children: [], + label: '雷州市', + value: 440882, + }, + { + children: [], + label: '吴川市', + value: 440883, + }, + ], + label: '湛江市', + value: 440800, + }, + { + children: [ + { + children: [], + label: '茂南区', + value: 440902, + }, + { + children: [], + label: '电白区', + value: 440904, + }, + { + children: [], + label: '高州市', + value: 440981, + }, + { + children: [], + label: '化州市', + value: 440982, + }, + { + children: [], + label: '信宜市', + value: 440983, + }, + ], + label: '茂名市', + value: 440900, + }, + { + children: [ + { + children: [], + label: '端州区', + value: 441202, + }, + { + children: [], + label: '鼎湖区', + value: 441203, + }, + { + children: [], + label: '高要区', + value: 441204, + }, + { + children: [], + label: '广宁县', + value: 441223, + }, + { + children: [], + label: '怀集县', + value: 441224, + }, + { + children: [], + label: '封开县', + value: 441225, + }, + { + children: [], + label: '德庆县', + value: 441226, + }, + { + children: [], + label: '四会市', + value: 441284, + }, + ], + label: '肇庆市', + value: 441200, + }, + { + children: [ + { + children: [], + label: '惠城区', + value: 441302, + }, + { + children: [], + label: '惠阳区', + value: 441303, + }, + { + children: [], + label: '博罗县', + value: 441322, + }, + { + children: [], + label: '惠东县', + value: 441323, + }, + { + children: [], + label: '龙门县', + value: 441324, + }, + ], + label: '惠州市', + value: 441300, + }, + { + children: [ + { + children: [], + label: '梅江区', + value: 441402, + }, + { + children: [], + label: '梅县区', + value: 441403, + }, + { + children: [], + label: '大埔县', + value: 441422, + }, + { + children: [], + label: '丰顺县', + value: 441423, + }, + { + children: [], + label: '五华县', + value: 441424, + }, + { + children: [], + label: '平远县', + value: 441426, + }, + { + children: [], + label: '蕉岭县', + value: 441427, + }, + { + children: [], + label: '兴宁市', + value: 441481, + }, + ], + label: '梅州市', + value: 441400, + }, + { + children: [ + { + children: [], + label: '城区', + value: 441502, + }, + { + children: [], + label: '海丰县', + value: 441521, + }, + { + children: [], + label: '陆河县', + value: 441523, + }, + { + children: [], + label: '陆丰市', + value: 441581, + }, + ], + label: '汕尾市', + value: 441500, + }, + { + children: [ + { + children: [], + label: '源城区', + value: 441602, + }, + { + children: [], + label: '紫金县', + value: 441621, + }, + { + children: [], + label: '龙川县', + value: 441622, + }, + { + children: [], + label: '连平县', + value: 441623, + }, + { + children: [], + label: '和平县', + value: 441624, + }, + { + children: [], + label: '东源县', + value: 441625, + }, + ], + label: '河源市', + value: 441600, + }, + { + children: [ + { + children: [], + label: '江城区', + value: 441702, + }, + { + children: [], + label: '阳东区', + value: 441704, + }, + { + children: [], + label: '阳西县', + value: 441721, + }, + { + children: [], + label: '阳春市', + value: 441781, + }, + ], + label: '阳江市', + value: 441700, + }, + { + children: [ + { + children: [], + label: '清城区', + value: 441802, + }, + { + children: [], + label: '清新区', + value: 441803, + }, + { + children: [], + label: '佛冈县', + value: 441821, + }, + { + children: [], + label: '阳山县', + value: 441823, + }, + { + children: [], + label: '连山壮族瑶族自治县', + value: 441825, + }, + { + children: [], + label: '连南瑶族自治县', + value: 441826, + }, + { + children: [], + label: '英德市', + value: 441881, + }, + { + children: [], + label: '连州市', + value: 441882, + }, + ], + label: '清远市', + value: 441800, + }, + { + children: [], + label: '东莞市', + value: 441900, + }, + { + children: [], + label: '中山市', + value: 442000, + }, + { + children: [ + { + children: [], + label: '湘桥区', + value: 445102, + }, + { + children: [], + label: '潮安区', + value: 445103, + }, + { + children: [], + label: '饶平县', + value: 445122, + }, + ], + label: '潮州市', + value: 445100, + }, + { + children: [ + { + children: [], + label: '榕城区', + value: 445202, + }, + { + children: [], + label: '揭东区', + value: 445203, + }, + { + children: [], + label: '揭西县', + value: 445222, + }, + { + children: [], + label: '惠来县', + value: 445224, + }, + { + children: [], + label: '普宁市', + value: 445281, + }, + ], + label: '揭阳市', + value: 445200, + }, + { + children: [ + { + children: [], + label: '云城区', + value: 445302, + }, + { + children: [], + label: '云安区', + value: 445303, + }, + { + children: [], + label: '新兴县', + value: 445321, + }, + { + children: [], + label: '郁南县', + value: 445322, + }, + { + children: [], + label: '罗定市', + value: 445381, + }, + ], + label: '云浮市', + value: 445300, + }, + ], + label: '广东省', + value: 440000, + }, + { + children: [ + { + children: [ + { + children: [], + label: '兴宁区', + value: 450102, + }, + { + children: [], + label: '青秀区', + value: 450103, + }, + { + children: [], + label: '江南区', + value: 450105, + }, + { + children: [], + label: '西乡塘区', + value: 450107, + }, + { + children: [], + label: '良庆区', + value: 450108, + }, + { + children: [], + label: '邕宁区', + value: 450109, + }, + { + children: [], + label: '武鸣区', + value: 450110, + }, + { + children: [], + label: '隆安县', + value: 450123, + }, + { + children: [], + label: '马山县', + value: 450124, + }, + { + children: [], + label: '上林县', + value: 450125, + }, + { + children: [], + label: '宾阳县', + value: 450126, + }, + { + children: [], + label: '横县', + value: 450127, + }, + ], + label: '南宁市', + value: 450100, + }, + { + children: [ + { + children: [], + label: '城中区', + value: 450202, + }, + { + children: [], + label: '鱼峰区', + value: 450203, + }, + { + children: [], + label: '柳南区', + value: 450204, + }, + { + children: [], + label: '柳北区', + value: 450205, + }, + { + children: [], + label: '柳江区', + value: 450206, + }, + { + children: [], + label: '柳城县', + value: 450222, + }, + { + children: [], + label: '鹿寨县', + value: 450223, + }, + { + children: [], + label: '融安县', + value: 450224, + }, + { + children: [], + label: '融水苗族自治县', + value: 450225, + }, + { + children: [], + label: '三江侗族自治县', + value: 450226, + }, + ], + label: '柳州市', + value: 450200, + }, + { + children: [ + { + children: [], + label: '秀峰区', + value: 450302, + }, + { + children: [], + label: '叠彩区', + value: 450303, + }, + { + children: [], + label: '象山区', + value: 450304, + }, + { + children: [], + label: '七星区', + value: 450305, + }, + { + children: [], + label: '雁山区', + value: 450311, + }, + { + children: [], + label: '临桂区', + value: 450312, + }, + { + children: [], + label: '阳朔县', + value: 450321, + }, + { + children: [], + label: '灵川县', + value: 450323, + }, + { + children: [], + label: '全州县', + value: 450324, + }, + { + children: [], + label: '兴安县', + value: 450325, + }, + { + children: [], + label: '永福县', + value: 450326, + }, + { + children: [], + label: '灌阳县', + value: 450327, + }, + { + children: [], + label: '龙胜各族自治县', + value: 450328, + }, + { + children: [], + label: '资源县', + value: 450329, + }, + { + children: [], + label: '平乐县', + value: 450330, + }, + { + children: [], + label: '恭城瑶族自治县', + value: 450332, + }, + { + children: [], + label: '荔浦市', + value: 450381, + }, + ], + label: '桂林市', + value: 450300, + }, + { + children: [ + { + children: [], + label: '万秀区', + value: 450403, + }, + { + children: [], + label: '长洲区', + value: 450405, + }, + { + children: [], + label: '龙圩区', + value: 450406, + }, + { + children: [], + label: '苍梧县', + value: 450421, + }, + { + children: [], + label: '藤县', + value: 450422, + }, + { + children: [], + label: '蒙山县', + value: 450423, + }, + { + children: [], + label: '岑溪市', + value: 450481, + }, + ], + label: '梧州市', + value: 450400, + }, + { + children: [ + { + children: [], + label: '海城区', + value: 450502, + }, + { + children: [], + label: '银海区', + value: 450503, + }, + { + children: [], + label: '铁山港区', + value: 450512, + }, + { + children: [], + label: '合浦县', + value: 450521, + }, + ], + label: '北海市', + value: 450500, + }, + { + children: [ + { + children: [], + label: '港口区', + value: 450602, + }, + { + children: [], + label: '防城区', + value: 450603, + }, + { + children: [], + label: '上思县', + value: 450621, + }, + { + children: [], + label: '东兴市', + value: 450681, + }, + ], + label: '防城港市', + value: 450600, + }, + { + children: [ + { + children: [], + label: '钦南区', + value: 450702, + }, + { + children: [], + label: '钦北区', + value: 450703, + }, + { + children: [], + label: '灵山县', + value: 450721, + }, + { + children: [], + label: '浦北县', + value: 450722, + }, + ], + label: '钦州市', + value: 450700, + }, + { + children: [ + { + children: [], + label: '港北区', + value: 450802, + }, + { + children: [], + label: '港南区', + value: 450803, + }, + { + children: [], + label: '覃塘区', + value: 450804, + }, + { + children: [], + label: '平南县', + value: 450821, + }, + { + children: [], + label: '桂平市', + value: 450881, + }, + ], + label: '贵港市', + value: 450800, + }, + { + children: [ + { + children: [], + label: '玉州区', + value: 450902, + }, + { + children: [], + label: '福绵区', + value: 450903, + }, + { + children: [], + label: '容县', + value: 450921, + }, + { + children: [], + label: '陆川县', + value: 450922, + }, + { + children: [], + label: '博白县', + value: 450923, + }, + { + children: [], + label: '兴业县', + value: 450924, + }, + { + children: [], + label: '北流市', + value: 450981, + }, + ], + label: '玉林市', + value: 450900, + }, + { + children: [ + { + children: [], + label: '右江区', + value: 451002, + }, + { + children: [], + label: '田阳县', + value: 451021, + }, + { + children: [], + label: '田东县', + value: 451022, + }, + { + children: [], + label: '平果县', + value: 451023, + }, + { + children: [], + label: '德保县', + value: 451024, + }, + { + children: [], + label: '那坡县', + value: 451026, + }, + { + children: [], + label: '凌云县', + value: 451027, + }, + { + children: [], + label: '乐业县', + value: 451028, + }, + { + children: [], + label: '田林县', + value: 451029, + }, + { + children: [], + label: '西林县', + value: 451030, + }, + { + children: [], + label: '隆林各族自治县', + value: 451031, + }, + { + children: [], + label: '靖西市', + value: 451081, + }, + ], + label: '百色市', + value: 451000, + }, + { + children: [ + { + children: [], + label: '八步区', + value: 451102, + }, + { + children: [], + label: '平桂区', + value: 451103, + }, + { + children: [], + label: '昭平县', + value: 451121, + }, + { + children: [], + label: '钟山县', + value: 451122, + }, + { + children: [], + label: '富川瑶族自治县', + value: 451123, + }, + ], + label: '贺州市', + value: 451100, + }, + { + children: [ + { + children: [], + label: '金城江区', + value: 451202, + }, + { + children: [], + label: '宜州区', + value: 451203, + }, + { + children: [], + label: '南丹县', + value: 451221, + }, + { + children: [], + label: '天峨县', + value: 451222, + }, + { + children: [], + label: '凤山县', + value: 451223, + }, + { + children: [], + label: '东兰县', + value: 451224, + }, + { + children: [], + label: '罗城仫佬族自治县', + value: 451225, + }, + { + children: [], + label: '环江毛南族自治县', + value: 451226, + }, + { + children: [], + label: '巴马瑶族自治县', + value: 451227, + }, + { + children: [], + label: '都安瑶族自治县', + value: 451228, + }, + { + children: [], + label: '大化瑶族自治县', + value: 451229, + }, + ], + label: '河池市', + value: 451200, + }, + { + children: [ + { + children: [], + label: '兴宾区', + value: 451302, + }, + { + children: [], + label: '忻城县', + value: 451321, + }, + { + children: [], + label: '象州县', + value: 451322, + }, + { + children: [], + label: '武宣县', + value: 451323, + }, + { + children: [], + label: '金秀瑶族自治县', + value: 451324, + }, + { + children: [], + label: '合山市', + value: 451381, + }, + ], + label: '来宾市', + value: 451300, + }, + { + children: [ + { + children: [], + label: '江州区', + value: 451402, + }, + { + children: [], + label: '扶绥县', + value: 451421, + }, + { + children: [], + label: '宁明县', + value: 451422, + }, + { + children: [], + label: '龙州县', + value: 451423, + }, + { + children: [], + label: '大新县', + value: 451424, + }, + { + children: [], + label: '天等县', + value: 451425, + }, + { + children: [], + label: '凭祥市', + value: 451481, + }, + ], + label: '崇左市', + value: 451400, + }, + ], + label: '广西壮族自治区', + value: 450000, + }, + { + children: [ + { + children: [ + { + children: [], + label: '秀英区', + value: 460105, + }, + { + children: [], + label: '龙华区', + value: 460106, + }, + { + children: [], + label: '琼山区', + value: 460107, + }, + { + children: [], + label: '美兰区', + value: 460108, + }, + ], + label: '海口市', + value: 460100, + }, + { + children: [ + { + children: [], + label: '海棠区', + value: 460202, + }, + { + children: [], + label: '吉阳区', + value: 460203, + }, + { + children: [], + label: '天涯区', + value: 460204, + }, + { + children: [], + label: '崖州区', + value: 460205, + }, + ], + label: '三亚市', + value: 460200, + }, + { + children: [ + { + children: [], + label: '西沙群岛', + value: 460321, + }, + { + children: [], + label: '南沙群岛', + value: 460322, + }, + { + children: [], + label: '中沙群岛', + value: 460323, + }, + ], + label: '三沙市', + value: 460300, + }, + { + children: [], + label: '儋州市', + value: 460400, + }, + { + children: [], + label: '五指山市', + value: 469001, + }, + { + children: [], + label: '琼海市', + value: 469002, + }, + { + children: [], + label: '文昌市', + value: 469005, + }, + { + children: [], + label: '万宁市', + value: 469006, + }, + { + children: [], + label: '东方市', + value: 469007, + }, + { + children: [], + label: '定安县', + value: 469021, + }, + { + children: [], + label: '屯昌县', + value: 469022, + }, + { + children: [], + label: '澄迈县', + value: 469023, + }, + { + children: [], + label: '临高县', + value: 469024, + }, + { + children: [], + label: '白沙黎族自治县', + value: 469025, + }, + { + children: [], + label: '昌江黎族自治县', + value: 469026, + }, + { + children: [], + label: '乐东黎族自治县', + value: 469027, + }, + { + children: [], + label: '陵水黎族自治县', + value: 469028, + }, + { + children: [], + label: '保亭黎族苗族自治县', + value: 469029, + }, + { + children: [], + label: '琼中黎族苗族自治县', + value: 469030, + }, + ], + label: '海南省', + value: 460000, + }, + { + children: [ + { + children: [ + { + children: [], + label: '万州区', + value: 500101, + }, + { + children: [], + label: '涪陵区', + value: 500102, + }, + { + children: [], + label: '渝中区', + value: 500103, + }, + { + children: [], + label: '大渡口区', + value: 500104, + }, + { + children: [], + label: '江北区', + value: 500105, + }, + { + children: [], + label: '沙坪坝区', + value: 500106, + }, + { + children: [], + label: '九龙坡区', + value: 500107, + }, + { + children: [], + label: '南岸区', + value: 500108, + }, + { + children: [], + label: '北碚区', + value: 500109, + }, + { + children: [], + label: '綦江区', + value: 500110, + }, + { + children: [], + label: '大足区', + value: 500111, + }, + { + children: [], + label: '渝北区', + value: 500112, + }, + { + children: [], + label: '巴南区', + value: 500113, + }, + { + children: [], + label: '黔江区', + value: 500114, + }, + { + children: [], + label: '长寿区', + value: 500115, + }, + { + children: [], + label: '江津区', + value: 500116, + }, + { + children: [], + label: '合川区', + value: 500117, + }, + { + children: [], + label: '永川区', + value: 500118, + }, + { + children: [], + label: '南川区', + value: 500119, + }, + { + children: [], + label: '璧山区', + value: 500120, + }, + { + children: [], + label: '铜梁区', + value: 500151, + }, + { + children: [], + label: '潼南区', + value: 500152, + }, + { + children: [], + label: '荣昌区', + value: 500153, + }, + { + children: [], + label: '开州区', + value: 500154, + }, + { + children: [], + label: '梁平区', + value: 500155, + }, + { + children: [], + label: '武隆区', + value: 500156, + }, + ], + label: '重庆市', + value: 500100, + }, + { + children: [ + { + children: [], + label: '城口县', + value: 500229, + }, + { + children: [], + label: '丰都县', + value: 500230, + }, + { + children: [], + label: '垫江县', + value: 500231, + }, + { + children: [], + label: '忠县', + value: 500233, + }, + { + children: [], + label: '云阳县', + value: 500235, + }, + { + children: [], + label: '奉节县', + value: 500236, + }, + { + children: [], + label: '巫山县', + value: 500237, + }, + { + children: [], + label: '巫溪县', + value: 500238, + }, + { + children: [], + label: '石柱土家族自治县', + value: 500240, + }, + { + children: [], + label: '秀山土家族苗族自治县', + value: 500241, + }, + { + children: [], + label: '酉阳土家族苗族自治县', + value: 500242, + }, + { + children: [], + label: '彭水苗族土家族自治县', + value: 500243, + }, + ], + label: '重庆市', + value: 500200, + }, + ], + label: '重庆市', + value: 500000, + }, + { + children: [ + { + children: [ + { + children: [], + label: '锦江区', + value: 510104, + }, + { + children: [], + label: '青羊区', + value: 510105, + }, + { + children: [], + label: '金牛区', + value: 510106, + }, + { + children: [], + label: '武侯区', + value: 510107, + }, + { + children: [], + label: '成华区', + value: 510108, + }, + { + children: [], + label: '龙泉驿区', + value: 510112, + }, + { + children: [], + label: '青白江区', + value: 510113, + }, + { + children: [], + label: '新都区', + value: 510114, + }, + { + children: [], + label: '温江区', + value: 510115, + }, + { + children: [], + label: '双流区', + value: 510116, + }, + { + children: [], + label: '郫都区', + value: 510117, + }, + { + children: [], + label: '金堂县', + value: 510121, + }, + { + children: [], + label: '大邑县', + value: 510129, + }, + { + children: [], + label: '蒲江县', + value: 510131, + }, + { + children: [], + label: '新津县', + value: 510132, + }, + { + children: [], + label: '都江堰市', + value: 510181, + }, + { + children: [], + label: '彭州市', + value: 510182, + }, + { + children: [], + label: '邛崃市', + value: 510183, + }, + { + children: [], + label: '崇州市', + value: 510184, + }, + { + children: [], + label: '简阳市', + value: 510185, + }, + ], + label: '成都市', + value: 510100, + }, + { + children: [ + { + children: [], + label: '自流井区', + value: 510302, + }, + { + children: [], + label: '贡井区', + value: 510303, + }, + { + children: [], + label: '大安区', + value: 510304, + }, + { + children: [], + label: '沿滩区', + value: 510311, + }, + { + children: [], + label: '荣县', + value: 510321, + }, + { + children: [], + label: '富顺县', + value: 510322, + }, + ], + label: '自贡市', + value: 510300, + }, + { + children: [ + { + children: [], + label: '东区', + value: 510402, + }, + { + children: [], + label: '西区', + value: 510403, + }, + { + children: [], + label: '仁和区', + value: 510411, + }, + { + children: [], + label: '米易县', + value: 510421, + }, + { + children: [], + label: '盐边县', + value: 510422, + }, + ], + label: '攀枝花市', + value: 510400, + }, + { + children: [ + { + children: [], + label: '江阳区', + value: 510502, + }, + { + children: [], + label: '纳溪区', + value: 510503, + }, + { + children: [], + label: '龙马潭区', + value: 510504, + }, + { + children: [], + label: '泸县', + value: 510521, + }, + { + children: [], + label: '合江县', + value: 510522, + }, + { + children: [], + label: '叙永县', + value: 510524, + }, + { + children: [], + label: '古蔺县', + value: 510525, + }, + ], + label: '泸州市', + value: 510500, + }, + { + children: [ + { + children: [], + label: '旌阳区', + value: 510603, + }, + { + children: [], + label: '罗江区', + value: 510604, + }, + { + children: [], + label: '中江县', + value: 510623, + }, + { + children: [], + label: '广汉市', + value: 510681, + }, + { + children: [], + label: '什邡市', + value: 510682, + }, + { + children: [], + label: '绵竹市', + value: 510683, + }, + ], + label: '德阳市', + value: 510600, + }, + { + children: [ + { + children: [], + label: '涪城区', + value: 510703, + }, + { + children: [], + label: '游仙区', + value: 510704, + }, + { + children: [], + label: '安州区', + value: 510705, + }, + { + children: [], + label: '三台县', + value: 510722, + }, + { + children: [], + label: '盐亭县', + value: 510723, + }, + { + children: [], + label: '梓潼县', + value: 510725, + }, + { + children: [], + label: '北川羌族自治县', + value: 510726, + }, + { + children: [], + label: '平武县', + value: 510727, + }, + { + children: [], + label: '江油市', + value: 510781, + }, + ], + label: '绵阳市', + value: 510700, + }, + { + children: [ + { + children: [], + label: '利州区', + value: 510802, + }, + { + children: [], + label: '昭化区', + value: 510811, + }, + { + children: [], + label: '朝天区', + value: 510812, + }, + { + children: [], + label: '旺苍县', + value: 510821, + }, + { + children: [], + label: '青川县', + value: 510822, + }, + { + children: [], + label: '剑阁县', + value: 510823, + }, + { + children: [], + label: '苍溪县', + value: 510824, + }, + ], + label: '广元市', + value: 510800, + }, + { + children: [ + { + children: [], + label: '船山区', + value: 510903, + }, + { + children: [], + label: '安居区', + value: 510904, + }, + { + children: [], + label: '蓬溪县', + value: 510921, + }, + { + children: [], + label: '射洪县', + value: 510922, + }, + { + children: [], + label: '大英县', + value: 510923, + }, + ], + label: '遂宁市', + value: 510900, + }, + { + children: [ + { + children: [], + label: '市中区', + value: 511002, + }, + { + children: [], + label: '东兴区', + value: 511011, + }, + { + children: [], + label: '威远县', + value: 511024, + }, + { + children: [], + label: '资中县', + value: 511025, + }, + { + children: [], + label: '隆昌市', + value: 511083, + }, + ], + label: '内江市', + value: 511000, + }, + { + children: [ + { + children: [], + label: '市中区', + value: 511102, + }, + { + children: [], + label: '沙湾区', + value: 511111, + }, + { + children: [], + label: '五通桥区', + value: 511112, + }, + { + children: [], + label: '金口河区', + value: 511113, + }, + { + children: [], + label: '犍为县', + value: 511123, + }, + { + children: [], + label: '井研县', + value: 511124, + }, + { + children: [], + label: '夹江县', + value: 511126, + }, + { + children: [], + label: '沐川县', + value: 511129, + }, + { + children: [], + label: '峨边彝族自治县', + value: 511132, + }, + { + children: [], + label: '马边彝族自治县', + value: 511133, + }, + { + children: [], + label: '峨眉山市', + value: 511181, + }, + ], + label: '乐山市', + value: 511100, + }, + { + children: [ + { + children: [], + label: '顺庆区', + value: 511302, + }, + { + children: [], + label: '高坪区', + value: 511303, + }, + { + children: [], + label: '嘉陵区', + value: 511304, + }, + { + children: [], + label: '南部县', + value: 511321, + }, + { + children: [], + label: '营山县', + value: 511322, + }, + { + children: [], + label: '蓬安县', + value: 511323, + }, + { + children: [], + label: '仪陇县', + value: 511324, + }, + { + children: [], + label: '西充县', + value: 511325, + }, + { + children: [], + label: '阆中市', + value: 511381, + }, + ], + label: '南充市', + value: 511300, + }, + { + children: [ + { + children: [], + label: '东坡区', + value: 511402, + }, + { + children: [], + label: '彭山区', + value: 511403, + }, + { + children: [], + label: '仁寿县', + value: 511421, + }, + { + children: [], + label: '洪雅县', + value: 511423, + }, + { + children: [], + label: '丹棱县', + value: 511424, + }, + { + children: [], + label: '青神县', + value: 511425, + }, + ], + label: '眉山市', + value: 511400, + }, + { + children: [ + { + children: [], + label: '翠屏区', + value: 511502, + }, + { + children: [], + label: '南溪区', + value: 511503, + }, + { + children: [], + label: '叙州区', + value: 511504, + }, + { + children: [], + label: '江安县', + value: 511523, + }, + { + children: [], + label: '长宁县', + value: 511524, + }, + { + children: [], + label: '高县', + value: 511525, + }, + { + children: [], + label: '珙县', + value: 511526, + }, + { + children: [], + label: '筠连县', + value: 511527, + }, + { + children: [], + label: '兴文县', + value: 511528, + }, + { + children: [], + label: '屏山县', + value: 511529, + }, + ], + label: '宜宾市', + value: 511500, + }, + { + children: [ + { + children: [], + label: '广安区', + value: 511602, + }, + { + children: [], + label: '前锋区', + value: 511603, + }, + { + children: [], + label: '岳池县', + value: 511621, + }, + { + children: [], + label: '武胜县', + value: 511622, + }, + { + children: [], + label: '邻水县', + value: 511623, + }, + { + children: [], + label: '华蓥市', + value: 511681, + }, + ], + label: '广安市', + value: 511600, + }, + { + children: [ + { + children: [], + label: '通川区', + value: 511702, + }, + { + children: [], + label: '达川区', + value: 511703, + }, + { + children: [], + label: '宣汉县', + value: 511722, + }, + { + children: [], + label: '开江县', + value: 511723, + }, + { + children: [], + label: '大竹县', + value: 511724, + }, + { + children: [], + label: '渠县', + value: 511725, + }, + { + children: [], + label: '万源市', + value: 511781, + }, + ], + label: '达州市', + value: 511700, + }, + { + children: [ + { + children: [], + label: '雨城区', + value: 511802, + }, + { + children: [], + label: '名山区', + value: 511803, + }, + { + children: [], + label: '荥经县', + value: 511822, + }, + { + children: [], + label: '汉源县', + value: 511823, + }, + { + children: [], + label: '石棉县', + value: 511824, + }, + { + children: [], + label: '天全县', + value: 511825, + }, + { + children: [], + label: '芦山县', + value: 511826, + }, + { + children: [], + label: '宝兴县', + value: 511827, + }, + ], + label: '雅安市', + value: 511800, + }, + { + children: [ + { + children: [], + label: '巴州区', + value: 511902, + }, + { + children: [], + label: '恩阳区', + value: 511903, + }, + { + children: [], + label: '通江县', + value: 511921, + }, + { + children: [], + label: '南江县', + value: 511922, + }, + { + children: [], + label: '平昌县', + value: 511923, + }, + ], + label: '巴中市', + value: 511900, + }, + { + children: [ + { + children: [], + label: '雁江区', + value: 512002, + }, + { + children: [], + label: '安岳县', + value: 512021, + }, + { + children: [], + label: '乐至县', + value: 512022, + }, + ], + label: '资阳市', + value: 512000, + }, + { + children: [ + { + children: [], + label: '马尔康市', + value: 513201, + }, + { + children: [], + label: '汶川县', + value: 513221, + }, + { + children: [], + label: '理县', + value: 513222, + }, + { + children: [], + label: '茂县', + value: 513223, + }, + { + children: [], + label: '松潘县', + value: 513224, + }, + { + children: [], + label: '九寨沟县', + value: 513225, + }, + { + children: [], + label: '金川县', + value: 513226, + }, + { + children: [], + label: '小金县', + value: 513227, + }, + { + children: [], + label: '黑水县', + value: 513228, + }, + { + children: [], + label: '壤塘县', + value: 513230, + }, + { + children: [], + label: '阿坝县', + value: 513231, + }, + { + children: [], + label: '若尔盖县', + value: 513232, + }, + { + children: [], + label: '红原县', + value: 513233, + }, + ], + label: '阿坝藏族羌族自治州', + value: 513200, + }, + { + children: [ + { + children: [], + label: '康定市', + value: 513301, + }, + { + children: [], + label: '泸定县', + value: 513322, + }, + { + children: [], + label: '丹巴县', + value: 513323, + }, + { + children: [], + label: '九龙县', + value: 513324, + }, + { + children: [], + label: '雅江县', + value: 513325, + }, + { + children: [], + label: '道孚县', + value: 513326, + }, + { + children: [], + label: '炉霍县', + value: 513327, + }, + { + children: [], + label: '甘孜县', + value: 513328, + }, + { + children: [], + label: '新龙县', + value: 513329, + }, + { + children: [], + label: '德格县', + value: 513330, + }, + { + children: [], + label: '白玉县', + value: 513331, + }, + { + children: [], + label: '石渠县', + value: 513332, + }, + { + children: [], + label: '色达县', + value: 513333, + }, + { + children: [], + label: '理塘县', + value: 513334, + }, + { + children: [], + label: '巴塘县', + value: 513335, + }, + { + children: [], + label: '乡城县', + value: 513336, + }, + { + children: [], + label: '稻城县', + value: 513337, + }, + { + children: [], + label: '得荣县', + value: 513338, + }, + ], + label: '甘孜藏族自治州', + value: 513300, + }, + { + children: [ + { + children: [], + label: '西昌市', + value: 513401, + }, + { + children: [], + label: '木里藏族自治县', + value: 513422, + }, + { + children: [], + label: '盐源县', + value: 513423, + }, + { + children: [], + label: '德昌县', + value: 513424, + }, + { + children: [], + label: '会理县', + value: 513425, + }, + { + children: [], + label: '会东县', + value: 513426, + }, + { + children: [], + label: '宁南县', + value: 513427, + }, + { + children: [], + label: '普格县', + value: 513428, + }, + { + children: [], + label: '布拖县', + value: 513429, + }, + { + children: [], + label: '金阳县', + value: 513430, + }, + { + children: [], + label: '昭觉县', + value: 513431, + }, + { + children: [], + label: '喜德县', + value: 513432, + }, + { + children: [], + label: '冕宁县', + value: 513433, + }, + { + children: [], + label: '越西县', + value: 513434, + }, + { + children: [], + label: '甘洛县', + value: 513435, + }, + { + children: [], + label: '美姑县', + value: 513436, + }, + { + children: [], + label: '雷波县', + value: 513437, + }, + ], + label: '凉山彝族自治州', + value: 513400, + }, + ], + label: '四川省', + value: 510000, + }, + { + children: [ + { + children: [ + { + children: [], + label: '南明区', + value: 520102, + }, + { + children: [], + label: '云岩区', + value: 520103, + }, + { + children: [], + label: '花溪区', + value: 520111, + }, + { + children: [], + label: '乌当区', + value: 520112, + }, + { + children: [], + label: '白云区', + value: 520113, + }, + { + children: [], + label: '观山湖区', + value: 520115, + }, + { + children: [], + label: '开阳县', + value: 520121, + }, + { + children: [], + label: '息烽县', + value: 520122, + }, + { + children: [], + label: '修文县', + value: 520123, + }, + { + children: [], + label: '清镇市', + value: 520181, + }, + ], + label: '贵阳市', + value: 520100, + }, + { + children: [ + { + children: [], + label: '钟山区', + value: 520201, + }, + { + children: [], + label: '六枝特区', + value: 520203, + }, + { + children: [], + label: '水城县', + value: 520221, + }, + { + children: [], + label: '盘州市', + value: 520281, + }, + ], + label: '六盘水市', + value: 520200, + }, + { + children: [ + { + children: [], + label: '红花岗区', + value: 520302, + }, + { + children: [], + label: '汇川区', + value: 520303, + }, + { + children: [], + label: '播州区', + value: 520304, + }, + { + children: [], + label: '桐梓县', + value: 520322, + }, + { + children: [], + label: '绥阳县', + value: 520323, + }, + { + children: [], + label: '正安县', + value: 520324, + }, + { + children: [], + label: '道真仡佬族苗族自治县', + value: 520325, + }, + { + children: [], + label: '务川仡佬族苗族自治县', + value: 520326, + }, + { + children: [], + label: '凤冈县', + value: 520327, + }, + { + children: [], + label: '湄潭县', + value: 520328, + }, + { + children: [], + label: '余庆县', + value: 520329, + }, + { + children: [], + label: '习水县', + value: 520330, + }, + { + children: [], + label: '赤水市', + value: 520381, + }, + { + children: [], + label: '仁怀市', + value: 520382, + }, + ], + label: '遵义市', + value: 520300, + }, + { + children: [ + { + children: [], + label: '西秀区', + value: 520402, + }, + { + children: [], + label: '平坝区', + value: 520403, + }, + { + children: [], + label: '普定县', + value: 520422, + }, + { + children: [], + label: '镇宁布依族苗族自治县', + value: 520423, + }, + { + children: [], + label: '关岭布依族苗族自治县', + value: 520424, + }, + { + children: [], + label: '紫云苗族布依族自治县', + value: 520425, + }, + ], + label: '安顺市', + value: 520400, + }, + { + children: [ + { + children: [], + label: '七星关区', + value: 520502, + }, + { + children: [], + label: '大方县', + value: 520521, + }, + { + children: [], + label: '黔西县', + value: 520522, + }, + { + children: [], + label: '金沙县', + value: 520523, + }, + { + children: [], + label: '织金县', + value: 520524, + }, + { + children: [], + label: '纳雍县', + value: 520525, + }, + { + children: [], + label: '威宁彝族回族苗族自治县', + value: 520526, + }, + { + children: [], + label: '赫章县', + value: 520527, + }, + ], + label: '毕节市', + value: 520500, + }, + { + children: [ + { + children: [], + label: '碧江区', + value: 520602, + }, + { + children: [], + label: '万山区', + value: 520603, + }, + { + children: [], + label: '江口县', + value: 520621, + }, + { + children: [], + label: '玉屏侗族自治县', + value: 520622, + }, + { + children: [], + label: '石阡县', + value: 520623, + }, + { + children: [], + label: '思南县', + value: 520624, + }, + { + children: [], + label: '印江土家族苗族自治县', + value: 520625, + }, + { + children: [], + label: '德江县', + value: 520626, + }, + { + children: [], + label: '沿河土家族自治县', + value: 520627, + }, + { + children: [], + label: '松桃苗族自治县', + value: 520628, + }, + ], + label: '铜仁市', + value: 520600, + }, + { + children: [ + { + children: [], + label: '兴义市', + value: 522301, + }, + { + children: [], + label: '兴仁市', + value: 522302, + }, + { + children: [], + label: '普安县', + value: 522323, + }, + { + children: [], + label: '晴隆县', + value: 522324, + }, + { + children: [], + label: '贞丰县', + value: 522325, + }, + { + children: [], + label: '望谟县', + value: 522326, + }, + { + children: [], + label: '册亨县', + value: 522327, + }, + { + children: [], + label: '安龙县', + value: 522328, + }, + ], + label: '黔西南布依族苗族自治州', + value: 522300, + }, + { + children: [ + { + children: [], + label: '凯里市', + value: 522601, + }, + { + children: [], + label: '黄平县', + value: 522622, + }, + { + children: [], + label: '施秉县', + value: 522623, + }, + { + children: [], + label: '三穗县', + value: 522624, + }, + { + children: [], + label: '镇远县', + value: 522625, + }, + { + children: [], + label: '岑巩县', + value: 522626, + }, + { + children: [], + label: '天柱县', + value: 522627, + }, + { + children: [], + label: '锦屏县', + value: 522628, + }, + { + children: [], + label: '剑河县', + value: 522629, + }, + { + children: [], + label: '台江县', + value: 522630, + }, + { + children: [], + label: '黎平县', + value: 522631, + }, + { + children: [], + label: '榕江县', + value: 522632, + }, + { + children: [], + label: '从江县', + value: 522633, + }, + { + children: [], + label: '雷山县', + value: 522634, + }, + { + children: [], + label: '麻江县', + value: 522635, + }, + { + children: [], + label: '丹寨县', + value: 522636, + }, + ], + label: '黔东南苗族侗族自治州', + value: 522600, + }, + { + children: [ + { + children: [], + label: '都匀市', + value: 522701, + }, + { + children: [], + label: '福泉市', + value: 522702, + }, + { + children: [], + label: '荔波县', + value: 522722, + }, + { + children: [], + label: '贵定县', + value: 522723, + }, + { + children: [], + label: '瓮安县', + value: 522725, + }, + { + children: [], + label: '独山县', + value: 522726, + }, + { + children: [], + label: '平塘县', + value: 522727, + }, + { + children: [], + label: '罗甸县', + value: 522728, + }, + { + children: [], + label: '长顺县', + value: 522729, + }, + { + children: [], + label: '龙里县', + value: 522730, + }, + { + children: [], + label: '惠水县', + value: 522731, + }, + { + children: [], + label: '三都水族自治县', + value: 522732, + }, + ], + label: '黔南布依族苗族自治州', + value: 522700, + }, + ], + label: '贵州省', + value: 520000, + }, + { + children: [ + { + children: [ + { + children: [], + label: '五华区', + value: 530102, + }, + { + children: [], + label: '盘龙区', + value: 530103, + }, + { + children: [], + label: '官渡区', + value: 530111, + }, + { + children: [], + label: '西山区', + value: 530112, + }, + { + children: [], + label: '东川区', + value: 530113, + }, + { + children: [], + label: '呈贡区', + value: 530114, + }, + { + children: [], + label: '晋宁区', + value: 530115, + }, + { + children: [], + label: '富民县', + value: 530124, + }, + { + children: [], + label: '宜良县', + value: 530125, + }, + { + children: [], + label: '石林彝族自治县', + value: 530126, + }, + { + children: [], + label: '嵩明县', + value: 530127, + }, + { + children: [], + label: '禄劝彝族苗族自治县', + value: 530128, + }, + { + children: [], + label: '寻甸回族彝族自治县', + value: 530129, + }, + { + children: [], + label: '安宁市', + value: 530181, + }, + ], + label: '昆明市', + value: 530100, + }, + { + children: [ + { + children: [], + label: '麒麟区', + value: 530302, + }, + { + children: [], + label: '沾益区', + value: 530303, + }, + { + children: [], + label: '马龙区', + value: 530304, + }, + { + children: [], + label: '陆良县', + value: 530322, + }, + { + children: [], + label: '师宗县', + value: 530323, + }, + { + children: [], + label: '罗平县', + value: 530324, + }, + { + children: [], + label: '富源县', + value: 530325, + }, + { + children: [], + label: '会泽县', + value: 530326, + }, + { + children: [], + label: '宣威市', + value: 530381, + }, + ], + label: '曲靖市', + value: 530300, + }, + { + children: [ + { + children: [], + label: '红塔区', + value: 530402, + }, + { + children: [], + label: '江川区', + value: 530403, + }, + { + children: [], + label: '澄江县', + value: 530422, + }, + { + children: [], + label: '通海县', + value: 530423, + }, + { + children: [], + label: '华宁县', + value: 530424, + }, + { + children: [], + label: '易门县', + value: 530425, + }, + { + children: [], + label: '峨山彝族自治县', + value: 530426, + }, + { + children: [], + label: '新平彝族傣族自治县', + value: 530427, + }, + { + children: [], + label: '元江哈尼族彝族傣族自治县', + value: 530428, + }, + ], + label: '玉溪市', + value: 530400, + }, + { + children: [ + { + children: [], + label: '隆阳区', + value: 530502, + }, + { + children: [], + label: '施甸县', + value: 530521, + }, + { + children: [], + label: '龙陵县', + value: 530523, + }, + { + children: [], + label: '昌宁县', + value: 530524, + }, + { + children: [], + label: '腾冲市', + value: 530581, + }, + ], + label: '保山市', + value: 530500, + }, + { + children: [ + { + children: [], + label: '昭阳区', + value: 530602, + }, + { + children: [], + label: '鲁甸县', + value: 530621, + }, + { + children: [], + label: '巧家县', + value: 530622, + }, + { + children: [], + label: '盐津县', + value: 530623, + }, + { + children: [], + label: '大关县', + value: 530624, + }, + { + children: [], + label: '永善县', + value: 530625, + }, + { + children: [], + label: '绥江县', + value: 530626, + }, + { + children: [], + label: '镇雄县', + value: 530627, + }, + { + children: [], + label: '彝良县', + value: 530628, + }, + { + children: [], + label: '威信县', + value: 530629, + }, + { + children: [], + label: '水富市', + value: 530681, + }, + ], + label: '昭通市', + value: 530600, + }, + { + children: [ + { + children: [], + label: '古城区', + value: 530702, + }, + { + children: [], + label: '玉龙纳西族自治县', + value: 530721, + }, + { + children: [], + label: '永胜县', + value: 530722, + }, + { + children: [], + label: '华坪县', + value: 530723, + }, + { + children: [], + label: '宁蒗彝族自治县', + value: 530724, + }, + ], + label: '丽江市', + value: 530700, + }, + { + children: [ + { + children: [], + label: '思茅区', + value: 530802, + }, + { + children: [], + label: '宁洱哈尼族彝族自治县', + value: 530821, + }, + { + children: [], + label: '墨江哈尼族自治县', + value: 530822, + }, + { + children: [], + label: '景东彝族自治县', + value: 530823, + }, + { + children: [], + label: '景谷傣族彝族自治县', + value: 530824, + }, + { + children: [], + label: '镇沅彝族哈尼族拉祜族自治县', + value: 530825, + }, + { + children: [], + label: '江城哈尼族彝族自治县', + value: 530826, + }, + { + children: [], + label: '孟连傣族拉祜族佤族自治县', + value: 530827, + }, + { + children: [], + label: '澜沧拉祜族自治县', + value: 530828, + }, + { + children: [], + label: '西盟佤族自治县', + value: 530829, + }, + ], + label: '普洱市', + value: 530800, + }, + { + children: [ + { + children: [], + label: '临翔区', + value: 530902, + }, + { + children: [], + label: '凤庆县', + value: 530921, + }, + { + children: [], + label: '云县', + value: 530922, + }, + { + children: [], + label: '永德县', + value: 530923, + }, + { + children: [], + label: '镇康县', + value: 530924, + }, + { + children: [], + label: '双江拉祜族佤族布朗族傣族自治县', + value: 530925, + }, + { + children: [], + label: '耿马傣族佤族自治县', + value: 530926, + }, + { + children: [], + label: '沧源佤族自治县', + value: 530927, + }, + ], + label: '临沧市', + value: 530900, + }, + { + children: [ + { + children: [], + label: '楚雄市', + value: 532301, + }, + { + children: [], + label: '双柏县', + value: 532322, + }, + { + children: [], + label: '牟定县', + value: 532323, + }, + { + children: [], + label: '南华县', + value: 532324, + }, + { + children: [], + label: '姚安县', + value: 532325, + }, + { + children: [], + label: '大姚县', + value: 532326, + }, + { + children: [], + label: '永仁县', + value: 532327, + }, + { + children: [], + label: '元谋县', + value: 532328, + }, + { + children: [], + label: '武定县', + value: 532329, + }, + { + children: [], + label: '禄丰县', + value: 532331, + }, + ], + label: '楚雄彝族自治州', + value: 532300, + }, + { + children: [ + { + children: [], + label: '个旧市', + value: 532501, + }, + { + children: [], + label: '开远市', + value: 532502, + }, + { + children: [], + label: '蒙自市', + value: 532503, + }, + { + children: [], + label: '弥勒市', + value: 532504, + }, + { + children: [], + label: '屏边苗族自治县', + value: 532523, + }, + { + children: [], + label: '建水县', + value: 532524, + }, + { + children: [], + label: '石屏县', + value: 532525, + }, + { + children: [], + label: '泸西县', + value: 532527, + }, + { + children: [], + label: '元阳县', + value: 532528, + }, + { + children: [], + label: '红河县', + value: 532529, + }, + { + children: [], + label: '金平苗族瑶族傣族自治县', + value: 532530, + }, + { + children: [], + label: '绿春县', + value: 532531, + }, + { + children: [], + label: '河口瑶族自治县', + value: 532532, + }, + ], + label: '红河哈尼族彝族自治州', + value: 532500, + }, + { + children: [ + { + children: [], + label: '文山市', + value: 532601, + }, + { + children: [], + label: '砚山县', + value: 532622, + }, + { + children: [], + label: '西畴县', + value: 532623, + }, + { + children: [], + label: '麻栗坡县', + value: 532624, + }, + { + children: [], + label: '马关县', + value: 532625, + }, + { + children: [], + label: '丘北县', + value: 532626, + }, + { + children: [], + label: '广南县', + value: 532627, + }, + { + children: [], + label: '富宁县', + value: 532628, + }, + ], + label: '文山壮族苗族自治州', + value: 532600, + }, + { + children: [ + { + children: [], + label: '景洪市', + value: 532801, + }, + { + children: [], + label: '勐海县', + value: 532822, + }, + { + children: [], + label: '勐腊县', + value: 532823, + }, + ], + label: '西双版纳傣族自治州', + value: 532800, + }, + { + children: [ + { + children: [], + label: '大理市', + value: 532901, + }, + { + children: [], + label: '漾濞彝族自治县', + value: 532922, + }, + { + children: [], + label: '祥云县', + value: 532923, + }, + { + children: [], + label: '宾川县', + value: 532924, + }, + { + children: [], + label: '弥渡县', + value: 532925, + }, + { + children: [], + label: '南涧彝族自治县', + value: 532926, + }, + { + children: [], + label: '巍山彝族回族自治县', + value: 532927, + }, + { + children: [], + label: '永平县', + value: 532928, + }, + { + children: [], + label: '云龙县', + value: 532929, + }, + { + children: [], + label: '洱源县', + value: 532930, + }, + { + children: [], + label: '剑川县', + value: 532931, + }, + { + children: [], + label: '鹤庆县', + value: 532932, + }, + ], + label: '大理白族自治州', + value: 532900, + }, + { + children: [ + { + children: [], + label: '瑞丽市', + value: 533102, + }, + { + children: [], + label: '芒市', + value: 533103, + }, + { + children: [], + label: '梁河县', + value: 533122, + }, + { + children: [], + label: '盈江县', + value: 533123, + }, + { + children: [], + label: '陇川县', + value: 533124, + }, + ], + label: '德宏傣族景颇族自治州', + value: 533100, + }, + { + children: [ + { + children: [], + label: '泸水市', + value: 533301, + }, + { + children: [], + label: '福贡县', + value: 533323, + }, + { + children: [], + label: '贡山独龙族怒族自治县', + value: 533324, + }, + { + children: [], + label: '兰坪白族普米族自治县', + value: 533325, + }, + ], + label: '怒江傈僳族自治州', + value: 533300, + }, + { + children: [ + { + children: [], + label: '香格里拉市', + value: 533401, + }, + { + children: [], + label: '德钦县', + value: 533422, + }, + { + children: [], + label: '维西傈僳族自治县', + value: 533423, + }, + ], + label: '迪庆藏族自治州', + value: 533400, + }, + ], + label: '云南省', + value: 530000, + }, + { + children: [ + { + children: [ + { + children: [], + label: '城关区', + value: 540102, + }, + { + children: [], + label: '堆龙德庆区', + value: 540103, + }, + { + children: [], + label: '达孜区', + value: 540104, + }, + { + children: [], + label: '林周县', + value: 540121, + }, + { + children: [], + label: '当雄县', + value: 540122, + }, + { + children: [], + label: '尼木县', + value: 540123, + }, + { + children: [], + label: '曲水县', + value: 540124, + }, + { + children: [], + label: '墨竹工卡县', + value: 540127, + }, + ], + label: '拉萨市', + value: 540100, + }, + { + children: [ + { + children: [], + label: '桑珠孜区', + value: 540202, + }, + { + children: [], + label: '南木林县', + value: 540221, + }, + { + children: [], + label: '江孜县', + value: 540222, + }, + { + children: [], + label: '定日县', + value: 540223, + }, + { + children: [], + label: '萨迦县', + value: 540224, + }, + { + children: [], + label: '拉孜县', + value: 540225, + }, + { + children: [], + label: '昂仁县', + value: 540226, + }, + { + children: [], + label: '谢通门县', + value: 540227, + }, + { + children: [], + label: '白朗县', + value: 540228, + }, + { + children: [], + label: '仁布县', + value: 540229, + }, + { + children: [], + label: '康马县', + value: 540230, + }, + { + children: [], + label: '定结县', + value: 540231, + }, + { + children: [], + label: '仲巴县', + value: 540232, + }, + { + children: [], + label: '亚东县', + value: 540233, + }, + { + children: [], + label: '吉隆县', + value: 540234, + }, + { + children: [], + label: '聂拉木县', + value: 540235, + }, + { + children: [], + label: '萨嘎县', + value: 540236, + }, + { + children: [], + label: '岗巴县', + value: 540237, + }, + ], + label: '日喀则市', + value: 540200, + }, + { + children: [ + { + children: [], + label: '卡若区', + value: 540302, + }, + { + children: [], + label: '江达县', + value: 540321, + }, + { + children: [], + label: '贡觉县', + value: 540322, + }, + { + children: [], + label: '类乌齐县', + value: 540323, + }, + { + children: [], + label: '丁青县', + value: 540324, + }, + { + children: [], + label: '察雅县', + value: 540325, + }, + { + children: [], + label: '八宿县', + value: 540326, + }, + { + children: [], + label: '左贡县', + value: 540327, + }, + { + children: [], + label: '芒康县', + value: 540328, + }, + { + children: [], + label: '洛隆县', + value: 540329, + }, + { + children: [], + label: '边坝县', + value: 540330, + }, + ], + label: '昌都市', + value: 540300, + }, + { + children: [ + { + children: [], + label: '巴宜区', + value: 540402, + }, + { + children: [], + label: '工布江达县', + value: 540421, + }, + { + children: [], + label: '米林县', + value: 540422, + }, + { + children: [], + label: '墨脱县', + value: 540423, + }, + { + children: [], + label: '波密县', + value: 540424, + }, + { + children: [], + label: '察隅县', + value: 540425, + }, + { + children: [], + label: '朗县', + value: 540426, + }, + ], + label: '林芝市', + value: 540400, + }, + { + children: [ + { + children: [], + label: '乃东区', + value: 540502, + }, + { + children: [], + label: '扎囊县', + value: 540521, + }, + { + children: [], + label: '贡嘎县', + value: 540522, + }, + { + children: [], + label: '桑日县', + value: 540523, + }, + { + children: [], + label: '琼结县', + value: 540524, + }, + { + children: [], + label: '曲松县', + value: 540525, + }, + { + children: [], + label: '措美县', + value: 540526, + }, + { + children: [], + label: '洛扎县', + value: 540527, + }, + { + children: [], + label: '加查县', + value: 540528, + }, + { + children: [], + label: '隆子县', + value: 540529, + }, + { + children: [], + label: '错那县', + value: 540530, + }, + { + children: [], + label: '浪卡子县', + value: 540531, + }, + ], + label: '山南市', + value: 540500, + }, + { + children: [ + { + children: [], + label: '色尼区', + value: 540602, + }, + { + children: [], + label: '嘉黎县', + value: 540621, + }, + { + children: [], + label: '比如县', + value: 540622, + }, + { + children: [], + label: '聂荣县', + value: 540623, + }, + { + children: [], + label: '安多县', + value: 540624, + }, + { + children: [], + label: '申扎县', + value: 540625, + }, + { + children: [], + label: '索县', + value: 540626, + }, + { + children: [], + label: '班戈县', + value: 540627, + }, + { + children: [], + label: '巴青县', + value: 540628, + }, + { + children: [], + label: '尼玛县', + value: 540629, + }, + { + children: [], + label: '双湖县', + value: 540630, + }, + ], + label: '那曲市', + value: 540600, + }, + { + children: [ + { + children: [], + label: '普兰县', + value: 542521, + }, + { + children: [], + label: '札达县', + value: 542522, + }, + { + children: [], + label: '噶尔县', + value: 542523, + }, + { + children: [], + label: '日土县', + value: 542524, + }, + { + children: [], + label: '革吉县', + value: 542525, + }, + { + children: [], + label: '改则县', + value: 542526, + }, + { + children: [], + label: '措勤县', + value: 542527, + }, + ], + label: '阿里地区', + value: 542500, + }, + ], + label: '西藏自治区', + value: 540000, + }, + { + children: [ + { + children: [ + { + children: [], + label: '新城区', + value: 610102, + }, + { + children: [], + label: '碑林区', + value: 610103, + }, + { + children: [], + label: '莲湖区', + value: 610104, + }, + { + children: [], + label: '灞桥区', + value: 610111, + }, + { + children: [], + label: '未央区', + value: 610112, + }, + { + children: [], + label: '雁塔区', + value: 610113, + }, + { + children: [], + label: '阎良区', + value: 610114, + }, + { + children: [], + label: '临潼区', + value: 610115, + }, + { + children: [], + label: '长安区', + value: 610116, + }, + { + children: [], + label: '高陵区', + value: 610117, + }, + { + children: [], + label: '鄠邑区', + value: 610118, + }, + { + children: [], + label: '蓝田县', + value: 610122, + }, + { + children: [], + label: '周至县', + value: 610124, + }, + ], + label: '西安市', + value: 610100, + }, + { + children: [ + { + children: [], + label: '王益区', + value: 610202, + }, + { + children: [], + label: '印台区', + value: 610203, + }, + { + children: [], + label: '耀州区', + value: 610204, + }, + { + children: [], + label: '宜君县', + value: 610222, + }, + ], + label: '铜川市', + value: 610200, + }, + { + children: [ + { + children: [], + label: '渭滨区', + value: 610302, + }, + { + children: [], + label: '金台区', + value: 610303, + }, + { + children: [], + label: '陈仓区', + value: 610304, + }, + { + children: [], + label: '凤翔县', + value: 610322, + }, + { + children: [], + label: '岐山县', + value: 610323, + }, + { + children: [], + label: '扶风县', + value: 610324, + }, + { + children: [], + label: '眉县', + value: 610326, + }, + { + children: [], + label: '陇县', + value: 610327, + }, + { + children: [], + label: '千阳县', + value: 610328, + }, + { + children: [], + label: '麟游县', + value: 610329, + }, + { + children: [], + label: '凤县', + value: 610330, + }, + { + children: [], + label: '太白县', + value: 610331, + }, + ], + label: '宝鸡市', + value: 610300, + }, + { + children: [ + { + children: [], + label: '秦都区', + value: 610402, + }, + { + children: [], + label: '杨陵区', + value: 610403, + }, + { + children: [], + label: '渭城区', + value: 610404, + }, + { + children: [], + label: '三原县', + value: 610422, + }, + { + children: [], + label: '泾阳县', + value: 610423, + }, + { + children: [], + label: '乾县', + value: 610424, + }, + { + children: [], + label: '礼泉县', + value: 610425, + }, + { + children: [], + label: '永寿县', + value: 610426, + }, + { + children: [], + label: '长武县', + value: 610428, + }, + { + children: [], + label: '旬邑县', + value: 610429, + }, + { + children: [], + label: '淳化县', + value: 610430, + }, + { + children: [], + label: '武功县', + value: 610431, + }, + { + children: [], + label: '兴平市', + value: 610481, + }, + { + children: [], + label: '彬州市', + value: 610482, + }, + ], + label: '咸阳市', + value: 610400, + }, + { + children: [ + { + children: [], + label: '临渭区', + value: 610502, + }, + { + children: [], + label: '华州区', + value: 610503, + }, + { + children: [], + label: '潼关县', + value: 610522, + }, + { + children: [], + label: '大荔县', + value: 610523, + }, + { + children: [], + label: '合阳县', + value: 610524, + }, + { + children: [], + label: '澄城县', + value: 610525, + }, + { + children: [], + label: '蒲城县', + value: 610526, + }, + { + children: [], + label: '白水县', + value: 610527, + }, + { + children: [], + label: '富平县', + value: 610528, + }, + { + children: [], + label: '韩城市', + value: 610581, + }, + { + children: [], + label: '华阴市', + value: 610582, + }, + ], + label: '渭南市', + value: 610500, + }, + { + children: [ + { + children: [], + label: '宝塔区', + value: 610602, + }, + { + children: [], + label: '安塞区', + value: 610603, + }, + { + children: [], + label: '延长县', + value: 610621, + }, + { + children: [], + label: '延川县', + value: 610622, + }, + { + children: [], + label: '子长县', + value: 610623, + }, + { + children: [], + label: '志丹县', + value: 610625, + }, + { + children: [], + label: '吴起县', + value: 610626, + }, + { + children: [], + label: '甘泉县', + value: 610627, + }, + { + children: [], + label: '富县', + value: 610628, + }, + { + children: [], + label: '洛川县', + value: 610629, + }, + { + children: [], + label: '宜川县', + value: 610630, + }, + { + children: [], + label: '黄龙县', + value: 610631, + }, + { + children: [], + label: '黄陵县', + value: 610632, + }, + ], + label: '延安市', + value: 610600, + }, + { + children: [ + { + children: [], + label: '汉台区', + value: 610702, + }, + { + children: [], + label: '南郑区', + value: 610703, + }, + { + children: [], + label: '城固县', + value: 610722, + }, + { + children: [], + label: '洋县', + value: 610723, + }, + { + children: [], + label: '西乡县', + value: 610724, + }, + { + children: [], + label: '勉县', + value: 610725, + }, + { + children: [], + label: '宁强县', + value: 610726, + }, + { + children: [], + label: '略阳县', + value: 610727, + }, + { + children: [], + label: '镇巴县', + value: 610728, + }, + { + children: [], + label: '留坝县', + value: 610729, + }, + { + children: [], + label: '佛坪县', + value: 610730, + }, + ], + label: '汉中市', + value: 610700, + }, + { + children: [ + { + children: [], + label: '榆阳区', + value: 610802, + }, + { + children: [], + label: '横山区', + value: 610803, + }, + { + children: [], + label: '府谷县', + value: 610822, + }, + { + children: [], + label: '靖边县', + value: 610824, + }, + { + children: [], + label: '定边县', + value: 610825, + }, + { + children: [], + label: '绥德县', + value: 610826, + }, + { + children: [], + label: '米脂县', + value: 610827, + }, + { + children: [], + label: '佳县', + value: 610828, + }, + { + children: [], + label: '吴堡县', + value: 610829, + }, + { + children: [], + label: '清涧县', + value: 610830, + }, + { + children: [], + label: '子洲县', + value: 610831, + }, + { + children: [], + label: '神木市', + value: 610881, + }, + ], + label: '榆林市', + value: 610800, + }, + { + children: [ + { + children: [], + label: '汉滨区', + value: 610902, + }, + { + children: [], + label: '汉阴县', + value: 610921, + }, + { + children: [], + label: '石泉县', + value: 610922, + }, + { + children: [], + label: '宁陕县', + value: 610923, + }, + { + children: [], + label: '紫阳县', + value: 610924, + }, + { + children: [], + label: '岚皋县', + value: 610925, + }, + { + children: [], + label: '平利县', + value: 610926, + }, + { + children: [], + label: '镇坪县', + value: 610927, + }, + { + children: [], + label: '旬阳县', + value: 610928, + }, + { + children: [], + label: '白河县', + value: 610929, + }, + ], + label: '安康市', + value: 610900, + }, + { + children: [ + { + children: [], + label: '商州区', + value: 611002, + }, + { + children: [], + label: '洛南县', + value: 611021, + }, + { + children: [], + label: '丹凤县', + value: 611022, + }, + { + children: [], + label: '商南县', + value: 611023, + }, + { + children: [], + label: '山阳县', + value: 611024, + }, + { + children: [], + label: '镇安县', + value: 611025, + }, + { + children: [], + label: '柞水县', + value: 611026, + }, + ], + label: '商洛市', + value: 611000, + }, + ], + label: '陕西省', + value: 610000, + }, + { + children: [ + { + children: [ + { + children: [], + label: '城关区', + value: 620102, + }, + { + children: [], + label: '七里河区', + value: 620103, + }, + { + children: [], + label: '西固区', + value: 620104, + }, + { + children: [], + label: '安宁区', + value: 620105, + }, + { + children: [], + label: '红古区', + value: 620111, + }, + { + children: [], + label: '永登县', + value: 620121, + }, + { + children: [], + label: '皋兰县', + value: 620122, + }, + { + children: [], + label: '榆中县', + value: 620123, + }, + ], + label: '兰州市', + value: 620100, + }, + { + children: [], + label: '嘉峪关市', + value: 620200, + }, + { + children: [ + { + children: [], + label: '金川区', + value: 620302, + }, + { + children: [], + label: '永昌县', + value: 620321, + }, + ], + label: '金昌市', + value: 620300, + }, + { + children: [ + { + children: [], + label: '白银区', + value: 620402, + }, + { + children: [], + label: '平川区', + value: 620403, + }, + { + children: [], + label: '靖远县', + value: 620421, + }, + { + children: [], + label: '会宁县', + value: 620422, + }, + { + children: [], + label: '景泰县', + value: 620423, + }, + ], + label: '白银市', + value: 620400, + }, + { + children: [ + { + children: [], + label: '秦州区', + value: 620502, + }, + { + children: [], + label: '麦积区', + value: 620503, + }, + { + children: [], + label: '清水县', + value: 620521, + }, + { + children: [], + label: '秦安县', + value: 620522, + }, + { + children: [], + label: '甘谷县', + value: 620523, + }, + { + children: [], + label: '武山县', + value: 620524, + }, + { + children: [], + label: '张家川回族自治县', + value: 620525, + }, + ], + label: '天水市', + value: 620500, + }, + { + children: [ + { + children: [], + label: '凉州区', + value: 620602, + }, + { + children: [], + label: '民勤县', + value: 620621, + }, + { + children: [], + label: '古浪县', + value: 620622, + }, + { + children: [], + label: '天祝藏族自治县', + value: 620623, + }, + ], + label: '武威市', + value: 620600, + }, + { + children: [ + { + children: [], + label: '甘州区', + value: 620702, + }, + { + children: [], + label: '肃南裕固族自治县', + value: 620721, + }, + { + children: [], + label: '民乐县', + value: 620722, + }, + { + children: [], + label: '临泽县', + value: 620723, + }, + { + children: [], + label: '高台县', + value: 620724, + }, + { + children: [], + label: '山丹县', + value: 620725, + }, + ], + label: '张掖市', + value: 620700, + }, + { + children: [ + { + children: [], + label: '崆峒区', + value: 620802, + }, + { + children: [], + label: '泾川县', + value: 620821, + }, + { + children: [], + label: '灵台县', + value: 620822, + }, + { + children: [], + label: '崇信县', + value: 620823, + }, + { + children: [], + label: '庄浪县', + value: 620825, + }, + { + children: [], + label: '静宁县', + value: 620826, + }, + { + children: [], + label: '华亭市', + value: 620881, + }, + ], + label: '平凉市', + value: 620800, + }, + { + children: [ + { + children: [], + label: '肃州区', + value: 620902, + }, + { + children: [], + label: '金塔县', + value: 620921, + }, + { + children: [], + label: '瓜州县', + value: 620922, + }, + { + children: [], + label: '肃北蒙古族自治县', + value: 620923, + }, + { + children: [], + label: '阿克塞哈萨克族自治县', + value: 620924, + }, + { + children: [], + label: '玉门市', + value: 620981, + }, + { + children: [], + label: '敦煌市', + value: 620982, + }, + ], + label: '酒泉市', + value: 620900, + }, + { + children: [ + { + children: [], + label: '西峰区', + value: 621002, + }, + { + children: [], + label: '庆城县', + value: 621021, + }, + { + children: [], + label: '环县', + value: 621022, + }, + { + children: [], + label: '华池县', + value: 621023, + }, + { + children: [], + label: '合水县', + value: 621024, + }, + { + children: [], + label: '正宁县', + value: 621025, + }, + { + children: [], + label: '宁县', + value: 621026, + }, + { + children: [], + label: '镇原县', + value: 621027, + }, + ], + label: '庆阳市', + value: 621000, + }, + { + children: [ + { + children: [], + label: '安定区', + value: 621102, + }, + { + children: [], + label: '通渭县', + value: 621121, + }, + { + children: [], + label: '陇西县', + value: 621122, + }, + { + children: [], + label: '渭源县', + value: 621123, + }, + { + children: [], + label: '临洮县', + value: 621124, + }, + { + children: [], + label: '漳县', + value: 621125, + }, + { + children: [], + label: '岷县', + value: 621126, + }, + ], + label: '定西市', + value: 621100, + }, + { + children: [ + { + children: [], + label: '武都区', + value: 621202, + }, + { + children: [], + label: '成县', + value: 621221, + }, + { + children: [], + label: '文县', + value: 621222, + }, + { + children: [], + label: '宕昌县', + value: 621223, + }, + { + children: [], + label: '康县', + value: 621224, + }, + { + children: [], + label: '西和县', + value: 621225, + }, + { + children: [], + label: '礼县', + value: 621226, + }, + { + children: [], + label: '徽县', + value: 621227, + }, + { + children: [], + label: '两当县', + value: 621228, + }, + ], + label: '陇南市', + value: 621200, + }, + { + children: [ + { + children: [], + label: '临夏市', + value: 622901, + }, + { + children: [], + label: '临夏县', + value: 622921, + }, + { + children: [], + label: '康乐县', + value: 622922, + }, + { + children: [], + label: '永靖县', + value: 622923, + }, + { + children: [], + label: '广河县', + value: 622924, + }, + { + children: [], + label: '和政县', + value: 622925, + }, + { + children: [], + label: '东乡族自治县', + value: 622926, + }, + { + children: [], + label: '积石山保安族东乡族撒拉族自治县', + value: 622927, + }, + ], + label: '临夏回族自治州', + value: 622900, + }, + { + children: [ + { + children: [], + label: '合作市', + value: 623001, + }, + { + children: [], + label: '临潭县', + value: 623021, + }, + { + children: [], + label: '卓尼县', + value: 623022, + }, + { + children: [], + label: '舟曲县', + value: 623023, + }, + { + children: [], + label: '迭部县', + value: 623024, + }, + { + children: [], + label: '玛曲县', + value: 623025, + }, + { + children: [], + label: '碌曲县', + value: 623026, + }, + { + children: [], + label: '夏河县', + value: 623027, + }, + ], + label: '甘南藏族自治州', + value: 623000, + }, + ], + label: '甘肃省', + value: 620000, + }, + { + children: [ + { + children: [ + { + children: [], + label: '城东区', + value: 630102, + }, + { + children: [], + label: '城中区', + value: 630103, + }, + { + children: [], + label: '城西区', + value: 630104, + }, + { + children: [], + label: '城北区', + value: 630105, + }, + { + children: [], + label: '大通回族土族自治县', + value: 630121, + }, + { + children: [], + label: '湟中县', + value: 630122, + }, + { + children: [], + label: '湟源县', + value: 630123, + }, + ], + label: '西宁市', + value: 630100, + }, + { + children: [ + { + children: [], + label: '乐都区', + value: 630202, + }, + { + children: [], + label: '平安区', + value: 630203, + }, + { + children: [], + label: '民和回族土族自治县', + value: 630222, + }, + { + children: [], + label: '互助土族自治县', + value: 630223, + }, + { + children: [], + label: '化隆回族自治县', + value: 630224, + }, + { + children: [], + label: '循化撒拉族自治县', + value: 630225, + }, + ], + label: '海东市', + value: 630200, + }, + { + children: [ + { + children: [], + label: '门源回族自治县', + value: 632221, + }, + { + children: [], + label: '祁连县', + value: 632222, + }, + { + children: [], + label: '海晏县', + value: 632223, + }, + { + children: [], + label: '刚察县', + value: 632224, + }, + ], + label: '海北藏族自治州', + value: 632200, + }, + { + children: [ + { + children: [], + label: '同仁县', + value: 632321, + }, + { + children: [], + label: '尖扎县', + value: 632322, + }, + { + children: [], + label: '泽库县', + value: 632323, + }, + { + children: [], + label: '河南蒙古族自治县', + value: 632324, + }, + ], + label: '黄南藏族自治州', + value: 632300, + }, + { + children: [ + { + children: [], + label: '共和县', + value: 632521, + }, + { + children: [], + label: '同德县', + value: 632522, + }, + { + children: [], + label: '贵德县', + value: 632523, + }, + { + children: [], + label: '兴海县', + value: 632524, + }, + { + children: [], + label: '贵南县', + value: 632525, + }, + ], + label: '海南藏族自治州', + value: 632500, + }, + { + children: [ + { + children: [], + label: '玛沁县', + value: 632621, + }, + { + children: [], + label: '班玛县', + value: 632622, + }, + { + children: [], + label: '甘德县', + value: 632623, + }, + { + children: [], + label: '达日县', + value: 632624, + }, + { + children: [], + label: '久治县', + value: 632625, + }, + { + children: [], + label: '玛多县', + value: 632626, + }, + ], + label: '果洛藏族自治州', + value: 632600, + }, + { + children: [ + { + children: [], + label: '玉树市', + value: 632701, + }, + { + children: [], + label: '杂多县', + value: 632722, + }, + { + children: [], + label: '称多县', + value: 632723, + }, + { + children: [], + label: '治多县', + value: 632724, + }, + { + children: [], + label: '囊谦县', + value: 632725, + }, + { + children: [], + label: '曲麻莱县', + value: 632726, + }, + ], + label: '玉树藏族自治州', + value: 632700, + }, + { + children: [ + { + children: [], + label: '格尔木市', + value: 632801, + }, + { + children: [], + label: '德令哈市', + value: 632802, + }, + { + children: [], + label: '茫崖市', + value: 632803, + }, + { + children: [], + label: '乌兰县', + value: 632821, + }, + { + children: [], + label: '都兰县', + value: 632822, + }, + { + children: [], + label: '天峻县', + value: 632823, + }, + { + children: [], + label: '海西蒙古族藏族自治州直辖', + value: 632824, + }, + ], + label: '海西蒙古族藏族自治州', + value: 632800, + }, + ], + label: '青海省', + value: 630000, + }, + { + children: [ + { + children: [ + { + children: [], + label: '兴庆区', + value: 640104, + }, + { + children: [], + label: '西夏区', + value: 640105, + }, + { + children: [], + label: '金凤区', + value: 640106, + }, + { + children: [], + label: '永宁县', + value: 640121, + }, + { + children: [], + label: '贺兰县', + value: 640122, + }, + { + children: [], + label: '灵武市', + value: 640181, + }, + ], + label: '银川市', + value: 640100, + }, + { + children: [ + { + children: [], + label: '大武口区', + value: 640202, + }, + { + children: [], + label: '惠农区', + value: 640205, + }, + { + children: [], + label: '平罗县', + value: 640221, + }, + ], + label: '石嘴山市', + value: 640200, + }, + { + children: [ + { + children: [], + label: '利通区', + value: 640302, + }, + { + children: [], + label: '红寺堡区', + value: 640303, + }, + { + children: [], + label: '盐池县', + value: 640323, + }, + { + children: [], + label: '同心县', + value: 640324, + }, + { + children: [], + label: '青铜峡市', + value: 640381, + }, + ], + label: '吴忠市', + value: 640300, + }, + { + children: [ + { + children: [], + label: '原州区', + value: 640402, + }, + { + children: [], + label: '西吉县', + value: 640422, + }, + { + children: [], + label: '隆德县', + value: 640423, + }, + { + children: [], + label: '泾源县', + value: 640424, + }, + { + children: [], + label: '彭阳县', + value: 640425, + }, + ], + label: '固原市', + value: 640400, + }, + { + children: [ + { + children: [], + label: '沙坡头区', + value: 640502, + }, + { + children: [], + label: '中宁县', + value: 640521, + }, + { + children: [], + label: '海原县', + value: 640522, + }, + ], + label: '中卫市', + value: 640500, + }, + ], + label: '宁夏回族自治区', + value: 640000, + }, + { + children: [ + { + children: [ + { + children: [], + label: '天山区', + value: 650102, + }, + { + children: [], + label: '沙依巴克区', + value: 650103, + }, + { + children: [], + label: '新市区', + value: 650104, + }, + { + children: [], + label: '水磨沟区', + value: 650105, + }, + { + children: [], + label: '头屯河区', + value: 650106, + }, + { + children: [], + label: '达坂城区', + value: 650107, + }, + { + children: [], + label: '米东区', + value: 650109, + }, + { + children: [], + label: '乌鲁木齐县', + value: 650121, + }, + ], + label: '乌鲁木齐市', + value: 650100, + }, + { + children: [ + { + children: [], + label: '独山子区', + value: 650202, + }, + { + children: [], + label: '克拉玛依区', + value: 650203, + }, + { + children: [], + label: '白碱滩区', + value: 650204, + }, + { + children: [], + label: '乌尔禾区', + value: 650205, + }, + ], + label: '克拉玛依市', + value: 650200, + }, + { + children: [ + { + children: [], + label: '高昌区', + value: 650402, + }, + { + children: [], + label: '鄯善县', + value: 650421, + }, + { + children: [], + label: '托克逊县', + value: 650422, + }, + ], + label: '吐鲁番市', + value: 650400, + }, + { + children: [ + { + children: [], + label: '伊州区', + value: 650502, + }, + { + children: [], + label: '巴里坤哈萨克自治县', + value: 650521, + }, + { + children: [], + label: '伊吾县', + value: 650522, + }, + ], + label: '哈密市', + value: 650500, + }, + { + children: [ + { + children: [], + label: '昌吉市', + value: 652301, + }, + { + children: [], + label: '阜康市', + value: 652302, + }, + { + children: [], + label: '呼图壁县', + value: 652323, + }, + { + children: [], + label: '玛纳斯县', + value: 652324, + }, + { + children: [], + label: '奇台县', + value: 652325, + }, + { + children: [], + label: '吉木萨尔县', + value: 652327, + }, + { + children: [], + label: '木垒哈萨克自治县', + value: 652328, + }, + ], + label: '昌吉回族自治州', + value: 652300, + }, + { + children: [ + { + children: [], + label: '博乐市', + value: 652701, + }, + { + children: [], + label: '阿拉山口市', + value: 652702, + }, + { + children: [], + label: '精河县', + value: 652722, + }, + { + children: [], + label: '温泉县', + value: 652723, + }, + ], + label: '博尔塔拉蒙古自治州', + value: 652700, + }, + { + children: [ + { + children: [], + label: '库尔勒市', + value: 652801, + }, + { + children: [], + label: '轮台县', + value: 652822, + }, + { + children: [], + label: '尉犁县', + value: 652823, + }, + { + children: [], + label: '若羌县', + value: 652824, + }, + { + children: [], + label: '且末县', + value: 652825, + }, + { + children: [], + label: '焉耆回族自治县', + value: 652826, + }, + { + children: [], + label: '和静县', + value: 652827, + }, + { + children: [], + label: '和硕县', + value: 652828, + }, + { + children: [], + label: '博湖县', + value: 652829, + }, + ], + label: '巴音郭楞蒙古自治州', + value: 652800, + }, + { + children: [ + { + children: [], + label: '阿克苏市', + value: 652901, + }, + { + children: [], + label: '温宿县', + value: 652922, + }, + { + children: [], + label: '库车县', + value: 652923, + }, + { + children: [], + label: '沙雅县', + value: 652924, + }, + { + children: [], + label: '新和县', + value: 652925, + }, + { + children: [], + label: '拜城县', + value: 652926, + }, + { + children: [], + label: '乌什县', + value: 652927, + }, + { + children: [], + label: '阿瓦提县', + value: 652928, + }, + { + children: [], + label: '柯坪县', + value: 652929, + }, + ], + label: '阿克苏地区', + value: 652900, + }, + { + children: [ + { + children: [], + label: '阿图什市', + value: 653001, + }, + { + children: [], + label: '阿克陶县', + value: 653022, + }, + { + children: [], + label: '阿合奇县', + value: 653023, + }, + { + children: [], + label: '乌恰县', + value: 653024, + }, + ], + label: '克孜勒苏柯尔克孜自治州', + value: 653000, + }, + { + children: [ + { + children: [], + label: '喀什市', + value: 653101, + }, + { + children: [], + label: '疏附县', + value: 653121, + }, + { + children: [], + label: '疏勒县', + value: 653122, + }, + { + children: [], + label: '英吉沙县', + value: 653123, + }, + { + children: [], + label: '泽普县', + value: 653124, + }, + { + children: [], + label: '莎车县', + value: 653125, + }, + { + children: [], + label: '叶城县', + value: 653126, + }, + { + children: [], + label: '麦盖提县', + value: 653127, + }, + { + children: [], + label: '岳普湖县', + value: 653128, + }, + { + children: [], + label: '伽师县', + value: 653129, + }, + { + children: [], + label: '巴楚县', + value: 653130, + }, + { + children: [], + label: '塔什库尔干塔吉克自治县', + value: 653131, + }, + ], + label: '喀什地区', + value: 653100, + }, + { + children: [ + { + children: [], + label: '和田市', + value: 653201, + }, + { + children: [], + label: '和田县', + value: 653221, + }, + { + children: [], + label: '墨玉县', + value: 653222, + }, + { + children: [], + label: '皮山县', + value: 653223, + }, + { + children: [], + label: '洛浦县', + value: 653224, + }, + { + children: [], + label: '策勒县', + value: 653225, + }, + { + children: [], + label: '于田县', + value: 653226, + }, + { + children: [], + label: '民丰县', + value: 653227, + }, + ], + label: '和田地区', + value: 653200, + }, + { + children: [ + { + children: [], + label: '伊宁市', + value: 654002, + }, + { + children: [], + label: '奎屯市', + value: 654003, + }, + { + children: [], + label: '霍尔果斯市', + value: 654004, + }, + { + children: [], + label: '伊宁县', + value: 654021, + }, + { + children: [], + label: '察布查尔锡伯自治县', + value: 654022, + }, + { + children: [], + label: '霍城县', + value: 654023, + }, + { + children: [], + label: '巩留县', + value: 654024, + }, + { + children: [], + label: '新源县', + value: 654025, + }, + { + children: [], + label: '昭苏县', + value: 654026, + }, + { + children: [], + label: '特克斯县', + value: 654027, + }, + { + children: [], + label: '尼勒克县', + value: 654028, + }, + ], + label: '伊犁哈萨克自治州', + value: 654000, + }, + { + children: [ + { + children: [], + label: '塔城市', + value: 654201, + }, + { + children: [], + label: '乌苏市', + value: 654202, + }, + { + children: [], + label: '额敏县', + value: 654221, + }, + { + children: [], + label: '沙湾县', + value: 654223, + }, + { + children: [], + label: '托里县', + value: 654224, + }, + { + children: [], + label: '裕民县', + value: 654225, + }, + { + children: [], + label: '和布克赛尔蒙古自治县', + value: 654226, + }, + ], + label: '塔城地区', + value: 654200, + }, + { + children: [ + { + children: [], + label: '阿勒泰市', + value: 654301, + }, + { + children: [], + label: '布尔津县', + value: 654321, + }, + { + children: [], + label: '富蕴县', + value: 654322, + }, + { + children: [], + label: '福海县', + value: 654323, + }, + { + children: [], + label: '哈巴河县', + value: 654324, + }, + { + children: [], + label: '青河县', + value: 654325, + }, + { + children: [], + label: '吉木乃县', + value: 654326, + }, + ], + label: '阿勒泰地区', + value: 654300, + }, + { + children: [], + label: '石河子市', + value: 659001, + }, + { + children: [], + label: '阿拉尔市', + value: 659002, + }, + { + children: [], + label: '图木舒克市', + value: 659003, + }, + { + children: [], + label: '五家渠市', + value: 659004, + }, + { + children: [], + label: '北屯市', + value: 659005, + }, + { + children: [], + label: '铁门关市', + value: 659006, + }, + { + children: [], + label: '双河市', + value: 659007, + }, + { + children: [], + label: '可克达拉市', + value: 659008, + }, + { + children: [], + label: '昆玉市', + value: 659009, + }, + ], + label: '新疆维吾尔自治区', + value: 650000, + }, + { + children: [ + { + children: [ + { + children: [], + label: '北投区', + value: 710101, + }, + { + children: [], + label: '松山区', + value: 710102, + }, + { + children: [], + label: '大同区', + value: 710103, + }, + { + children: [], + label: '文山区', + value: 710104, + }, + { + children: [], + label: '信义区', + value: 710105, + }, + { + children: [], + label: '内湖区', + value: 710106, + }, + { + children: [], + label: '中正区', + value: 710107, + }, + { + children: [], + label: '万华区', + value: 710108, + }, + { + children: [], + label: '中山区', + value: 710109, + }, + { + children: [], + label: '士林区', + value: 710110, + }, + { + children: [], + label: '南港区', + value: 710111, + }, + { + children: [], + label: '大安区', + value: 710112, + }, + ], + label: '台北市', + value: 710100, + }, + { + children: [ + { + children: [], + label: '小港区', + value: 710201, + }, + { + children: [], + label: '左营区', + value: 710202, + }, + { + children: [], + label: '大寮区', + value: 710203, + }, + { + children: [], + label: '那玛夏区', + value: 710204, + }, + { + children: [], + label: '旗津区', + value: 710205, + }, + { + children: [], + label: '弥陀区', + value: 710206, + }, + { + children: [], + label: '燕巢区', + value: 710207, + }, + { + children: [], + label: '大树区', + value: 710208, + }, + { + children: [], + label: '阿莲区', + value: 710209, + }, + { + children: [], + label: '楠梓区', + value: 710210, + }, + { + children: [], + label: '鸟松区', + value: 710211, + }, + { + children: [], + label: '苓雅区', + value: 710212, + }, + { + children: [], + label: '桥头区', + value: 710213, + }, + { + children: [], + label: '梓官区', + value: 710214, + }, + { + children: [], + label: '美浓区', + value: 710215, + }, + { + children: [], + label: '仁武区', + value: 710216, + }, + { + children: [], + label: '凤山区', + value: 710217, + }, + { + children: [], + label: '甲仙区', + value: 710218, + }, + { + children: [], + label: '茄萣区', + value: 710219, + }, + { + children: [], + label: '大社区', + value: 710220, + }, + { + children: [], + label: '前镇区', + value: 710221, + }, + { + children: [], + label: '茂林区', + value: 710222, + }, + { + children: [], + label: '湖内区', + value: 710223, + }, + { + children: [], + label: '六龟区', + value: 710224, + }, + { + children: [], + label: '林园区', + value: 710225, + }, + { + children: [], + label: '内门区', + value: 710226, + }, + { + children: [], + label: '鼓山区', + value: 710227, + }, + { + children: [], + label: '杉林区', + value: 710228, + }, + { + children: [], + label: '三民区', + value: 710229, + }, + { + children: [], + label: '前金区', + value: 710230, + }, + { + children: [], + label: '冈山区', + value: 710231, + }, + { + children: [], + label: '田寮区', + value: 710232, + }, + { + children: [], + label: '桃源区', + value: 710233, + }, + { + children: [], + label: '盐埕区', + value: 710234, + }, + { + children: [], + label: '旗山区', + value: 710235, + }, + { + children: [], + label: '永安区', + value: 710236, + }, + { + children: [], + label: '路竹区', + value: 710237, + }, + { + children: [], + label: '新兴区', + value: 710238, + }, + ], + label: '高雄市', + value: 710200, + }, + { + children: [ + { + children: [], + label: '八里区', + value: 710301, + }, + { + children: [], + label: '三峡区', + value: 710302, + }, + { + children: [], + label: '新庄区', + value: 710303, + }, + { + children: [], + label: '坪林区', + value: 710304, + }, + { + children: [], + label: '莺歌区', + value: 710305, + }, + { + children: [], + label: '汐止区', + value: 710306, + }, + { + children: [], + label: '深坑区', + value: 710307, + }, + { + children: [], + label: '芦洲区', + value: 710308, + }, + { + children: [], + label: '三芝区', + value: 710309, + }, + { + children: [], + label: '三重区', + value: 710310, + }, + { + children: [], + label: '平溪区', + value: 710311, + }, + { + children: [], + label: '中和区', + value: 710312, + }, + { + children: [], + label: '永和区', + value: 710313, + }, + { + children: [], + label: '泰山区', + value: 710314, + }, + { + children: [], + label: '土城区', + value: 710315, + }, + { + children: [], + label: '石碇区', + value: 710316, + }, + { + children: [], + label: '五股区', + value: 710317, + }, + { + children: [], + label: '林口区', + value: 710318, + }, + { + children: [], + label: '双溪区', + value: 710319, + }, + { + children: [], + label: '树林区', + value: 710320, + }, + { + children: [], + label: '板桥区', + value: 710321, + }, + { + children: [], + label: '新店区', + value: 710322, + }, + { + children: [], + label: '乌来区', + value: 710323, + }, + { + children: [], + label: '贡寮区', + value: 710324, + }, + { + children: [], + label: '金山区', + value: 710325, + }, + { + children: [], + label: '万里区', + value: 710326, + }, + { + children: [], + label: '淡水区', + value: 710327, + }, + { + children: [], + label: '瑞芳区', + value: 710328, + }, + { + children: [], + label: '石门区', + value: 710329, + }, + ], + label: '新北市', + value: 710300, + }, + { + children: [ + { + children: [], + label: '东区', + value: 710401, + }, + { + children: [], + label: '新社区', + value: 710402, + }, + { + children: [], + label: '中区', + value: 710403, + }, + { + children: [], + label: '南屯区', + value: 710404, + }, + { + children: [], + label: '神冈区', + value: 710405, + }, + { + children: [], + label: '沙鹿区', + value: 710406, + }, + { + children: [], + label: '丰原区', + value: 710407, + }, + { + children: [], + label: '大里区', + value: 710408, + }, + { + children: [], + label: '龙井区', + value: 710409, + }, + { + children: [], + label: '西区', + value: 710410, + }, + { + children: [], + label: '南区', + value: 710411, + }, + { + children: [], + label: '西屯区', + value: 710412, + }, + { + children: [], + label: '太平区', + value: 710413, + }, + { + children: [], + label: '北屯区', + value: 710414, + }, + { + children: [], + label: '大雅区', + value: 710415, + }, + { + children: [], + label: '石冈区', + value: 710416, + }, + { + children: [], + label: '北区', + value: 710417, + }, + { + children: [], + label: '大甲区', + value: 710418, + }, + { + children: [], + label: '梧栖区', + value: 710419, + }, + { + children: [], + label: '东势区', + value: 710420, + }, + { + children: [], + label: '和平区', + value: 710421, + }, + { + children: [], + label: '雾峰区', + value: 710422, + }, + { + children: [], + label: '乌日区', + value: 710423, + }, + { + children: [], + label: '后里区', + value: 710424, + }, + { + children: [], + label: '潭子区', + value: 710425, + }, + { + children: [], + label: '大肚区', + value: 710426, + }, + { + children: [], + label: '外埔区', + value: 710427, + }, + { + children: [], + label: '清水区', + value: 710428, + }, + { + children: [], + label: '大安区', + value: 710429, + }, + ], + label: '台中市', + value: 710400, + }, + { + children: [ + { + children: [], + label: '安定区', + value: 710501, + }, + { + children: [], + label: '大内区', + value: 710502, + }, + { + children: [], + label: '东山区', + value: 710503, + }, + { + children: [], + label: '下营区', + value: 710504, + }, + { + children: [], + label: '山上区', + value: 710505, + }, + { + children: [], + label: '永康区', + value: 710506, + }, + { + children: [], + label: '新营区', + value: 710507, + }, + { + children: [], + label: '白河区', + value: 710508, + }, + { + children: [], + label: '盐水区', + value: 710509, + }, + { + children: [], + label: '归仁区', + value: 710510, + }, + { + children: [], + label: '龙崎区', + value: 710511, + }, + { + children: [], + label: '关庙区', + value: 710512, + }, + { + children: [], + label: '仁德区', + value: 710513, + }, + { + children: [], + label: '学甲区', + value: 710514, + }, + { + children: [], + label: '善化区', + value: 710515, + }, + { + children: [], + label: '新市区', + value: 710516, + }, + { + children: [], + label: '佳里区', + value: 710517, + }, + { + children: [], + label: '新化区', + value: 710518, + }, + { + children: [], + label: '西港区', + value: 710519, + }, + { + children: [], + label: '麻豆区', + value: 710520, + }, + { + children: [], + label: '安南区', + value: 710521, + }, + { + children: [], + label: '后壁区', + value: 710522, + }, + { + children: [], + label: '柳营区', + value: 710523, + }, + { + children: [], + label: '玉井区', + value: 710524, + }, + { + children: [], + label: '官田区', + value: 710525, + }, + { + children: [], + label: '东区', + value: 710526, + }, + { + children: [], + label: '六甲区', + value: 710527, + }, + { + children: [], + label: '中西区', + value: 710528, + }, + { + children: [], + label: '北区', + value: 710529, + }, + { + children: [], + label: '楠西区', + value: 710530, + }, + { + children: [], + label: '南化区', + value: 710531, + }, + { + children: [], + label: '左镇区', + value: 710532, + }, + { + children: [], + label: '南区', + value: 710533, + }, + { + children: [], + label: '安平区', + value: 710534, + }, + { + children: [], + label: '北门区', + value: 710535, + }, + { + children: [], + label: '七股区', + value: 710536, + }, + { + children: [], + label: '将军区', + value: 710537, + }, + ], + label: '台南市', + value: 710500, + }, + { + children: [ + { + children: [], + label: '平镇市', + value: 710601, + }, + { + children: [], + label: '杨梅市', + value: 710602, + }, + { + children: [], + label: '八德市', + value: 710603, + }, + { + children: [], + label: '桃园区', + value: 710604, + }, + { + children: [], + label: '龟山乡', + value: 710605, + }, + { + children: [], + label: '复兴乡', + value: 710606, + }, + { + children: [], + label: '芦竹乡', + value: 710607, + }, + { + children: [], + label: '观音乡', + value: 710608, + }, + { + children: [], + label: '龙潭乡', + value: 710609, + }, + { + children: [], + label: '大溪镇', + value: 710610, + }, + { + children: [], + label: '大园乡', + value: 710611, + }, + { + children: [], + label: '新屋乡', + value: 710612, + }, + { + children: [], + label: '中坜市', + value: 710613, + }, + ], + label: '桃园市', + value: 710600, + }, + { + children: [], + label: '基隆市', + value: 719001, + }, + { + children: [], + label: '新竹市', + value: 719002, + }, + { + children: [], + label: '嘉义市', + value: 719003, + }, + { + children: [], + label: '新竹县', + value: 719004, + }, + { + children: [], + label: '宜兰县', + value: 719005, + }, + { + children: [], + label: '苗栗县', + value: 719006, + }, + { + children: [], + label: '彰化县', + value: 719007, + }, + { + children: [], + label: '云林县', + value: 719008, + }, + { + children: [], + label: '南投县', + value: 719009, + }, + { + children: [], + label: '嘉义县', + value: 719010, + }, + { + children: [], + label: '屏东县', + value: 719011, + }, + { + children: [], + label: '台东县', + value: 719012, + }, + { + children: [], + label: '花莲县', + value: 719013, + }, + { + children: [], + label: '澎湖县', + value: 719014, + }, + ], + label: '台湾省', + value: 710000, + }, + { + children: [ + { + children: [], + label: '中西区', + value: 810101, + }, + { + children: [], + label: '东区', + value: 810102, + }, + { + children: [], + label: '九龙城区', + value: 810103, + }, + { + children: [], + label: '观塘区', + value: 810104, + }, + { + children: [], + label: '南区', + value: 810105, + }, + { + children: [], + label: '深水埗区', + value: 810106, + }, + { + children: [], + label: '湾仔区', + value: 810107, + }, + { + children: [], + label: '黄大仙区', + value: 810108, + }, + { + children: [], + label: '油尖旺区', + value: 810109, + }, + { + children: [], + label: '离岛区', + value: 810110, + }, + { + children: [], + label: '葵青区', + value: 810111, + }, + { + children: [], + label: '北区', + value: 810112, + }, + { + children: [], + label: '西贡区', + value: 810113, + }, + { + children: [], + label: '沙田区', + value: 810114, + }, + { + children: [], + label: '屯门区', + value: 810115, + }, + { + children: [], + label: '大埔区', + value: 810116, + }, + { + children: [], + label: '荃湾区', + value: 810117, + }, + { + children: [], + label: '元朗区', + value: 810118, + }, + ], + label: '香港', + value: 810000, + }, + { + children: [ + { + children: [], + label: '花王堂区', + value: 820001, + }, + { + children: [], + label: '望德堂区', + value: 820002, + }, + { + children: [], + label: '风顺堂区', + value: 820003, + }, + { + children: [], + label: '圣方济各堂区', + value: 820004, + }, + { + children: [], + label: '嘉模堂区', + value: 820005, + }, + { + children: [], + label: '大堂区', + value: 820006, + }, + { + children: [], + label: '路氹填海区', + value: 820007, + }, + { + children: [], + label: '花地玛堂区', + value: 820008, + }, + { + children: [], + label: '澳门特别行政区直辖', + value: 820010, + }, + ], + label: '澳门', + value: 820000, + }, +]; diff --git a/smart-flow-web/src/components/framework/area-cascader/province-city.js b/smart-flow-web/src/components/framework/area-cascader/province-city.js new file mode 100644 index 0000000..50ba58d --- /dev/null +++ b/smart-flow-web/src/components/framework/area-cascader/province-city.js @@ -0,0 +1,1727 @@ +export const PROVINCE_CITY = [ + { + children: [ + { + value: 110100, + label: '北京', + }, + ], + value: 110000, + label: '北京市', + }, + { + children: [ + { + value: 120100, + label: '天津', + }, + ], + value: 120000, + label: '天津市', + }, + { + children: [ + { + value: 130100, + label: '石家庄', + }, + { + value: 130200, + label: '唐山', + }, + { + value: 130300, + label: '秦皇岛', + }, + { + value: 130400, + label: '邯郸', + }, + { + value: 130500, + label: '邢台', + }, + { + value: 130600, + label: '保定', + }, + { + value: 130700, + label: '张家口', + }, + { + value: 130800, + label: '承德', + }, + { + value: 130900, + label: '沧州', + }, + { + value: 131000, + label: '廊坊', + }, + { + value: 131100, + label: '衡水', + }, + ], + value: 130000, + label: '河北省', + }, + { + children: [ + { + value: 140100, + label: '太原', + }, + { + value: 140200, + label: '大同', + }, + { + value: 140300, + label: '阳泉', + }, + { + value: 140400, + label: '长治', + }, + { + value: 140500, + label: '晋城', + }, + { + value: 140600, + label: '朔州', + }, + { + value: 140700, + label: '晋中', + }, + { + value: 140800, + label: '运城', + }, + { + value: 140900, + label: '忻州', + }, + { + value: 141000, + label: '临汾', + }, + { + value: 141100, + label: '吕梁', + }, + ], + value: 140000, + label: '山西省', + }, + { + children: [ + { + value: 150100, + label: '呼和浩特', + }, + { + value: 150200, + label: '包头', + }, + { + value: 150300, + label: '乌海', + }, + { + value: 150400, + label: '赤峰', + }, + { + value: 150500, + label: '通辽', + }, + { + value: 150600, + label: '鄂尔多斯', + }, + { + value: 150700, + label: '呼伦贝尔', + }, + { + value: 150800, + label: '巴彦淖尔', + }, + { + value: 150900, + label: '乌兰察布', + }, + { + value: 152200, + label: '兴安盟', + }, + { + value: 152500, + label: '锡林郭勒盟', + }, + { + value: 152900, + label: '阿拉善盟', + }, + ], + value: 150000, + label: '内蒙古自治区', + }, + { + children: [ + { + value: 210100, + label: '沈阳', + }, + { + value: 210200, + label: '大连', + }, + { + value: 210300, + label: '鞍山', + }, + { + value: 210400, + label: '抚顺', + }, + { + value: 210500, + label: '本溪', + }, + { + value: 210600, + label: '丹东', + }, + { + value: 210700, + label: '锦州', + }, + { + value: 210800, + label: '营口', + }, + { + value: 210900, + label: '阜新', + }, + { + value: 211000, + label: '辽阳', + }, + { + value: 211100, + label: '盘锦', + }, + { + value: 211200, + label: '铁岭', + }, + { + value: 211300, + label: '朝阳', + }, + { + value: 211400, + label: '葫芦岛', + }, + ], + value: 210000, + label: '辽宁省', + }, + { + children: [ + { + value: 220100, + label: '长春', + }, + { + value: 220200, + label: '吉林', + }, + { + value: 220300, + label: '四平', + }, + { + value: 220400, + label: '辽源', + }, + { + value: 220500, + label: '通化', + }, + { + value: 220600, + label: '白山', + }, + { + value: 220700, + label: '松原', + }, + { + value: 220800, + label: '白城', + }, + { + value: 222400, + label: '延边', + }, + ], + value: 220000, + label: '吉林省', + }, + { + children: [ + { + value: 230100, + label: '哈尔滨', + }, + { + value: 230200, + label: '齐齐哈尔', + }, + { + value: 230300, + label: '鸡西', + }, + { + value: 230400, + label: '鹤岗', + }, + { + value: 230500, + label: '双鸭山', + }, + { + value: 230600, + label: '大庆', + }, + { + value: 230700, + label: '伊春', + }, + { + value: 230800, + label: '佳木斯', + }, + { + value: 230900, + label: '七台河', + }, + { + value: 231000, + label: '牡丹江', + }, + { + value: 231100, + label: '黑河', + }, + { + value: 231200, + label: '绥化', + }, + { + value: 232700, + label: '大兴安岭地区', + }, + ], + value: 230000, + label: '黑龙江省', + }, + { + children: [ + { + value: 310100, + label: '上海', + }, + ], + value: 310000, + label: '上海市', + }, + { + children: [ + { + value: 320100, + label: '南京', + }, + { + value: 320200, + label: '无锡', + }, + { + value: 320300, + label: '徐州', + }, + { + value: 320400, + label: '常州', + }, + { + value: 320500, + label: '苏州', + }, + { + value: 320600, + label: '南通', + }, + { + value: 320700, + label: '连云港', + }, + { + value: 320800, + label: '淮安', + }, + { + value: 320900, + label: '盐城', + }, + { + value: 321000, + label: '扬州', + }, + { + value: 321100, + label: '镇江', + }, + { + value: 321200, + label: '泰州', + }, + { + value: 321300, + label: '宿迁', + }, + ], + value: 320000, + label: '江苏省', + }, + { + children: [ + { + value: 330100, + label: '杭州', + }, + { + value: 330200, + label: '宁波', + }, + { + value: 330300, + label: '温州', + }, + { + value: 330400, + label: '嘉兴', + }, + { + value: 330500, + label: '湖州', + }, + { + value: 330600, + label: '绍兴', + }, + { + value: 330700, + label: '金华', + }, + { + value: 330800, + label: '衢州', + }, + { + value: 330900, + label: '舟山', + }, + { + value: 331000, + label: '台州', + }, + { + value: 331100, + label: '丽水', + }, + ], + value: 330000, + label: '浙江省', + }, + { + children: [ + { + value: 340100, + label: '合肥', + }, + { + value: 340200, + label: '芜湖', + }, + { + value: 340300, + label: '蚌埠', + }, + { + value: 340400, + label: '淮南', + }, + { + value: 340500, + label: '马鞍山', + }, + { + value: 340600, + label: '淮北', + }, + { + value: 340700, + label: '铜陵', + }, + { + value: 340800, + label: '安庆', + }, + { + value: 341000, + label: '黄山', + }, + { + value: 341100, + label: '滁州', + }, + { + value: 341200, + label: '阜阳', + }, + { + value: 341300, + label: '宿州', + }, + { + value: 341500, + label: '六安', + }, + { + value: 341600, + label: '亳州', + }, + { + value: 341700, + label: '池州', + }, + { + value: 341800, + label: '宣城', + }, + ], + value: 340000, + label: '安徽省', + }, + { + children: [ + { + value: 350100, + label: '福州', + }, + { + value: 350200, + label: '厦门', + }, + { + value: 350300, + label: '莆田', + }, + { + value: 350400, + label: '三明', + }, + { + value: 350500, + label: '泉州', + }, + { + value: 350600, + label: '漳州', + }, + { + value: 350700, + label: '南平', + }, + { + value: 350800, + label: '龙岩', + }, + { + value: 350900, + label: '宁德', + }, + ], + value: 350000, + label: '福建省', + }, + { + children: [ + { + value: 360100, + label: '南昌', + }, + { + value: 360200, + label: '景德镇', + }, + { + value: 360300, + label: '萍乡', + }, + { + value: 360400, + label: '九江', + }, + { + value: 360500, + label: '新余', + }, + { + value: 360600, + label: '鹰潭', + }, + { + value: 360700, + label: '赣州', + }, + { + value: 360800, + label: '吉安', + }, + { + value: 360900, + label: '宜春', + }, + { + value: 361000, + label: '抚州', + }, + { + value: 361100, + label: '上饶', + }, + ], + value: 360000, + label: '江西省', + }, + { + children: [ + { + value: 370100, + label: '济南', + }, + { + value: 370200, + label: '青岛', + }, + { + value: 370300, + label: '淄博', + }, + { + value: 370400, + label: '枣庄', + }, + { + value: 370500, + label: '东营', + }, + { + value: 370600, + label: '烟台', + }, + { + value: 370700, + label: '潍坊', + }, + { + value: 370800, + label: '济宁', + }, + { + value: 370900, + label: '泰安', + }, + { + value: 371000, + label: '威海', + }, + { + value: 371100, + label: '日照', + }, + { + value: 371300, + label: '临沂', + }, + { + value: 371400, + label: '德州', + }, + { + value: 371500, + label: '聊城', + }, + { + value: 371600, + label: '滨州', + }, + { + value: 371700, + label: '菏泽', + }, + { + value: 1072447, + label: '莱芜', + }, + ], + value: 370000, + label: '山东省', + }, + { + children: [ + { + value: 410100, + label: '郑州', + }, + { + value: 410200, + label: '开封', + }, + { + value: 410300, + label: '洛阳', + }, + { + value: 410400, + label: '平顶山', + }, + { + value: 410500, + label: '安阳', + }, + { + value: 410600, + label: '鹤壁', + }, + { + value: 410700, + label: '新乡', + }, + { + value: 410800, + label: '焦作', + }, + { + value: 410900, + label: '濮阳', + }, + { + value: 411000, + label: '许昌', + }, + { + value: 411100, + label: '漯河', + }, + { + value: 411200, + label: '三门峡', + }, + { + value: 411300, + label: '南阳', + }, + { + value: 411400, + label: '商丘', + }, + { + value: 411500, + label: '信阳', + }, + { + value: 411600, + label: '周口', + }, + { + value: 411700, + label: '驻马店', + }, + { + value: 419001, + label: '济源', + }, + ], + value: 410000, + label: '河南省', + }, + { + children: [ + { + value: 420100, + label: '武汉', + }, + { + value: 420200, + label: '黄石', + }, + { + value: 420300, + label: '十堰', + }, + { + value: 420500, + label: '宜昌', + }, + { + value: 420600, + label: '襄阳', + }, + { + value: 420700, + label: '鄂州', + }, + { + value: 420800, + label: '荆门', + }, + { + value: 420900, + label: '孝感', + }, + { + value: 421000, + label: '荆州', + }, + { + value: 421100, + label: '黄冈', + }, + { + value: 421200, + label: '咸宁', + }, + { + value: 421300, + label: '随州', + }, + { + value: 422800, + label: '恩施', + }, + { + value: 1072452, + label: '潜江', + }, + { + value: 1072453, + label: '仙桃', + }, + { + value: 1072457, + label: '大冶', + }, + { + value: 1072458, + label: '天门', + }, + { + value: 1072460, + label: '神农架', + }, + ], + value: 420000, + label: '湖北省', + }, + { + children: [ + { + value: 430100, + label: '长沙', + }, + { + value: 430200, + label: '株洲', + }, + { + value: 430300, + label: '湘潭', + }, + { + value: 430400, + label: '衡阳', + }, + { + value: 430500, + label: '邵阳', + }, + { + value: 430600, + label: '岳阳', + }, + { + value: 430700, + label: '常德', + }, + { + value: 430800, + label: '张家界', + }, + { + value: 430900, + label: '益阳', + }, + { + value: 431000, + label: '郴州', + }, + { + value: 431100, + label: '永州', + }, + { + value: 431200, + label: '怀化', + }, + { + value: 431300, + label: '娄底', + }, + { + value: 433100, + label: '湘西', + }, + ], + value: 430000, + label: '湖南省', + }, + { + children: [ + { + value: 440100, + label: '广州', + }, + { + value: 440200, + label: '韶关', + }, + { + value: 440300, + label: '深圳', + }, + { + value: 440400, + label: '珠海', + }, + { + value: 440500, + label: '汕头', + }, + { + value: 440600, + label: '佛山', + }, + { + value: 440700, + label: '江门', + }, + { + value: 440800, + label: '湛江', + }, + { + value: 440900, + label: '茂名', + }, + { + value: 441200, + label: '肇庆', + }, + { + value: 441300, + label: '惠州', + }, + { + value: 441400, + label: '梅州', + }, + { + value: 441500, + label: '汕尾', + }, + { + value: 441600, + label: '河源', + }, + { + value: 441700, + label: '阳江', + }, + { + value: 441800, + label: '清远', + }, + { + value: 441900, + label: '东莞', + }, + { + value: 442000, + label: '中山', + }, + { + value: 445100, + label: '潮州', + }, + { + value: 445200, + label: '揭阳', + }, + { + value: 445300, + label: '云浮', + }, + ], + value: 440000, + label: '广东省', + }, + { + children: [ + { + value: 450100, + label: '南宁', + }, + { + value: 450200, + label: '柳州', + }, + { + value: 450300, + label: '桂林', + }, + { + value: 450400, + label: '梧州', + }, + { + value: 450500, + label: '北海', + }, + { + value: 450600, + label: '防城港', + }, + { + value: 450700, + label: '钦州', + }, + { + value: 450800, + label: '贵港', + }, + { + value: 450900, + label: '玉林', + }, + { + value: 451000, + label: '百色', + }, + { + value: 451100, + label: '贺州', + }, + { + value: 451200, + label: '河池', + }, + { + value: 451300, + label: '来宾', + }, + { + value: 451400, + label: '崇左', + }, + ], + value: 450000, + label: '广西壮族自治区', + }, + { + children: [ + { + value: 460100, + label: '海口', + }, + { + value: 460200, + label: '三亚', + }, + { + value: 460300, + label: '三沙', + }, + { + value: 460400, + label: '儋州', + }, + { + value: 1072449, + label: '文昌', + }, + ], + value: 460000, + label: '海南省', + }, + { + children: [ + { + value: 500100, + label: '重庆', + }, + ], + value: 500000, + label: '重庆市', + }, + { + children: [ + { + value: 510100, + label: '成都', + }, + { + value: 510300, + label: '自贡', + }, + { + value: 510400, + label: '攀枝花', + }, + { + value: 510500, + label: '泸州', + }, + { + value: 510600, + label: '德阳', + }, + { + value: 510700, + label: '绵阳', + }, + { + value: 510800, + label: '广元', + }, + { + value: 510900, + label: '遂宁', + }, + { + value: 511000, + label: '内江', + }, + { + value: 511100, + label: '乐山', + }, + { + value: 511300, + label: '南充', + }, + { + value: 511400, + label: '眉山', + }, + { + value: 511500, + label: '宜宾', + }, + { + value: 511600, + label: '广安', + }, + { + value: 511700, + label: '达州', + }, + { + value: 511800, + label: '雅安', + }, + { + value: 511900, + label: '巴中', + }, + { + value: 512000, + label: '资阳', + }, + { + value: 513200, + label: '阿坝', + }, + { + value: 513300, + label: '甘孜', + }, + { + value: 513400, + label: '凉山', + }, + { + value: 1072456, + label: '西昌', + }, + ], + value: 510000, + label: '四川省', + }, + { + children: [ + { + value: 520100, + label: '贵阳', + }, + { + value: 520200, + label: '六盘水', + }, + { + value: 520300, + label: '遵义', + }, + { + value: 520400, + label: '安顺', + }, + { + value: 520500, + label: '毕节', + }, + { + value: 520600, + label: '铜仁', + }, + { + value: 522300, + label: '黔西南', + }, + { + value: 522600, + label: '黔东南', + }, + { + value: 522700, + label: '黔南', + }, + ], + value: 520000, + label: '贵州省', + }, + { + children: [ + { + value: 530100, + label: '昆明', + }, + { + value: 530300, + label: '曲靖', + }, + { + value: 530400, + label: '玉溪', + }, + { + value: 530500, + label: '保山', + }, + { + value: 530600, + label: '昭通', + }, + { + value: 530700, + label: '丽江', + }, + { + value: 530800, + label: '普洱', + }, + { + value: 530900, + label: '临沧', + }, + { + value: 532300, + label: '楚雄彝族自治州', + }, + { + value: 532500, + label: '红河哈尼族彝族自治州', + }, + { + value: 532600, + label: '文山壮族苗族自治州', + }, + { + value: 532800, + label: '西双版纳傣族自治州', + }, + { + value: 532900, + label: '大理', + }, + { + value: 533100, + label: '德宏傣族景颇族自治州', + }, + { + value: 533300, + label: '怒江傈僳族自治州', + }, + { + value: 533400, + label: '迪庆藏族自治州', + }, + ], + value: 530000, + label: '云南省', + }, + { + children: [ + { + value: 540100, + label: '拉萨', + }, + { + value: 540200, + label: '日喀则', + }, + { + value: 540300, + label: '昌都', + }, + { + value: 540400, + label: '林芝', + }, + { + value: 540500, + label: '山南', + }, + { + value: 540600, + label: '那曲', + }, + { + value: 542500, + label: '阿里', + }, + ], + value: 540000, + label: '西藏自治区', + }, + { + children: [ + { + value: 610100, + label: '西安', + }, + { + value: 610200, + label: '铜川', + }, + { + value: 610300, + label: '宝鸡', + }, + { + value: 610400, + label: '咸阳', + }, + { + value: 610500, + label: '渭南', + }, + { + value: 610600, + label: '延安', + }, + { + value: 610700, + label: '汉中', + }, + { + value: 610800, + label: '榆林', + }, + { + value: 610900, + label: '安康', + }, + { + value: 611000, + label: '商洛', + }, + { + value: 1072448, + label: '杨凌', + }, + ], + value: 610000, + label: '陕西省', + }, + { + children: [ + { + value: 620100, + label: '兰州', + }, + { + value: 620200, + label: '嘉峪关', + }, + { + value: 620300, + label: '金昌', + }, + { + value: 620400, + label: '白银', + }, + { + value: 620500, + label: '天水', + }, + { + value: 620600, + label: '武威', + }, + { + value: 620700, + label: '张掖', + }, + { + value: 620800, + label: '平凉', + }, + { + value: 620900, + label: '酒泉', + }, + { + value: 621000, + label: '庆阳', + }, + { + value: 621100, + label: '定西', + }, + { + value: 621200, + label: '陇南', + }, + { + value: 622900, + label: '临夏回族自治州', + }, + { + value: 623000, + label: '甘南藏族自治州', + }, + ], + value: 620000, + label: '甘肃省', + }, + { + children: [ + { + value: 630100, + label: '西宁', + }, + { + value: 630200, + label: '海东', + }, + { + value: 632200, + label: '海北', + }, + { + value: 632300, + label: '黄南', + }, + { + value: 632500, + label: '海南', + }, + { + value: 632600, + label: '果洛', + }, + { + value: 632700, + label: '玉树', + }, + { + value: 632800, + label: '海西', + }, + ], + value: 630000, + label: '青海省', + }, + { + children: [ + { + value: 640100, + label: '银川', + }, + { + value: 640200, + label: '石嘴山', + }, + { + value: 640300, + label: '吴忠', + }, + { + value: 640400, + label: '固原', + }, + { + value: 640500, + label: '中卫', + }, + ], + value: 640000, + label: '宁夏回族自治区', + }, + { + children: [ + { + value: 650100, + label: '乌鲁木齐', + }, + { + value: 650200, + label: '克拉玛依', + }, + { + value: 650400, + label: '吐鲁番', + }, + { + value: 650500, + label: '哈密', + }, + { + value: 652300, + label: '昌吉', + }, + { + value: 652700, + label: '博尔塔拉', + }, + { + value: 652800, + label: '巴音郭楞', + }, + { + value: 652900, + label: '阿克苏', + }, + { + value: 653000, + label: '克孜勒苏柯尔克孜', + }, + { + value: 653100, + label: '喀什地区', + }, + { + value: 653200, + label: '和田', + }, + { + value: 654000, + label: '伊犁', + }, + { + value: 654200, + label: '塔城', + }, + { + value: 654300, + label: '阿勒泰', + }, + { + value: 1072450, + label: '阿拉尔', + }, + { + value: 1072454, + label: '铁门关', + }, + { + value: 1072455, + label: '五家渠', + }, + { + value: 1072459, + label: '石河子', + }, + ], + value: 650000, + label: '新疆维吾尔自治区', + }, + { + children: [ + { + value: 8345963, + label: '台北', + }, + { + value: 8345964, + label: '高雄', + }, + ], + value: 710000, + label: '台湾省', + }, + { + children: [ + { + value: 810001, + label: '中西区', + }, + { + value: 810002, + label: '湾仔区', + }, + { + value: 810003, + label: '东区', + }, + { + value: 810004, + label: '南区', + }, + { + value: 810005, + label: '油尖旺区', + }, + { + value: 810006, + label: '深水埗区', + }, + { + value: 810007, + label: '九龙城区', + }, + { + value: 810008, + label: '黄大仙区', + }, + { + value: 810009, + label: '观塘区', + }, + { + value: 810010, + label: '荃湾区', + }, + { + value: 810011, + label: '屯门区', + }, + { + value: 810012, + label: '元朗区', + }, + { + value: 810013, + label: '北区', + }, + { + value: 810014, + label: '大埔区', + }, + { + value: 810015, + label: '西贡区', + }, + { + value: 810016, + label: '沙田区', + }, + { + value: 810017, + label: '葵青区', + }, + { + value: 810018, + label: '离岛区', + }, + ], + value: 810000, + label: '香港特别行政区', + }, + { + children: [ + { + value: 820001, + label: '花地玛堂区', + }, + { + value: 820002, + label: '花王堂区', + }, + { + value: 820003, + label: '望德堂区', + }, + { + value: 820004, + label: '大堂区', + }, + { + value: 820005, + label: '风顺堂区', + }, + { + value: 820006, + label: '嘉模堂区', + }, + { + value: 820007, + label: '路凼填海区', + }, + { + value: 820008, + label: '圣方济各堂区', + }, + ], + value: 820000, + label: '澳门特别行政区', + }, + { + children: [], + value: 900000, + label: '外国', + }, +]; diff --git a/smart-flow-web/src/components/framework/boolean-select/index.vue b/smart-flow-web/src/components/framework/boolean-select/index.vue new file mode 100644 index 0000000..e336bc8 --- /dev/null +++ b/smart-flow-web/src/components/framework/boolean-select/index.vue @@ -0,0 +1,80 @@ + + + + diff --git a/smart-flow-web/src/components/framework/icon-select/index.vue b/smart-flow-web/src/components/framework/icon-select/index.vue new file mode 100644 index 0000000..468335d --- /dev/null +++ b/smart-flow-web/src/components/framework/icon-select/index.vue @@ -0,0 +1,133 @@ + + + + + + diff --git a/smart-flow-web/src/components/framework/iframe/iframe-index.vue b/smart-flow-web/src/components/framework/iframe/iframe-index.vue new file mode 100644 index 0000000..f2478b1 --- /dev/null +++ b/smart-flow-web/src/components/framework/iframe/iframe-index.vue @@ -0,0 +1,18 @@ + + + diff --git a/smart-flow-web/src/components/framework/iframe/route-default-component.vue b/smart-flow-web/src/components/framework/iframe/route-default-component.vue new file mode 100644 index 0000000..7b8b46c --- /dev/null +++ b/smart-flow-web/src/components/framework/iframe/route-default-component.vue @@ -0,0 +1,3 @@ + diff --git a/smart-flow-web/src/components/framework/smart-copy-icon/index.vue b/smart-flow-web/src/components/framework/smart-copy-icon/index.vue new file mode 100644 index 0000000..5eeeaf4 --- /dev/null +++ b/smart-flow-web/src/components/framework/smart-copy-icon/index.vue @@ -0,0 +1,38 @@ + + + + diff --git a/smart-flow-web/src/components/framework/smart-enum-checkbox/index.vue b/smart-flow-web/src/components/framework/smart-enum-checkbox/index.vue new file mode 100644 index 0000000..cc2d2e3 --- /dev/null +++ b/smart-flow-web/src/components/framework/smart-enum-checkbox/index.vue @@ -0,0 +1,75 @@ + + + + diff --git a/smart-flow-web/src/components/framework/smart-enum-radio/index.vue b/smart-flow-web/src/components/framework/smart-enum-radio/index.vue new file mode 100644 index 0000000..830c2f1 --- /dev/null +++ b/smart-flow-web/src/components/framework/smart-enum-radio/index.vue @@ -0,0 +1,88 @@ + + + + diff --git a/smart-flow-web/src/components/framework/smart-enum-select/index.vue b/smart-flow-web/src/components/framework/smart-enum-select/index.vue new file mode 100644 index 0000000..df745a4 --- /dev/null +++ b/smart-flow-web/src/components/framework/smart-enum-select/index.vue @@ -0,0 +1,88 @@ + + + + diff --git a/smart-flow-web/src/components/framework/smart-loading/index.js b/smart-flow-web/src/components/framework/smart-loading/index.js new file mode 100644 index 0000000..2dd5dc3 --- /dev/null +++ b/smart-flow-web/src/components/framework/smart-loading/index.js @@ -0,0 +1,20 @@ +/* + * loading 组件 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-07-22 20:33:41 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import { useSpinStore } from "/@/store/modules/system/spin"; + +export const SmartLoading = { + show: () => { + useSpinStore().show(); + }, + + hide: () => { + useSpinStore().hide(); + }, +}; diff --git a/smart-flow-web/src/components/framework/text-ellipsis/index.vue b/smart-flow-web/src/components/framework/text-ellipsis/index.vue new file mode 100644 index 0000000..cd67cbd --- /dev/null +++ b/smart-flow-web/src/components/framework/text-ellipsis/index.vue @@ -0,0 +1,77 @@ + + + + diff --git a/smart-flow-web/src/components/framework/wangeditor/index.vue b/smart-flow-web/src/components/framework/wangeditor/index.vue new file mode 100644 index 0000000..dc3c31c --- /dev/null +++ b/smart-flow-web/src/components/framework/wangeditor/index.vue @@ -0,0 +1,118 @@ + + + + + + + diff --git a/smart-flow-web/src/components/support/data-tracer/data-tracer-table.vue b/smart-flow-web/src/components/support/data-tracer/data-tracer-table.vue new file mode 100644 index 0000000..ca5ea35 --- /dev/null +++ b/smart-flow-web/src/components/support/data-tracer/data-tracer-table.vue @@ -0,0 +1,97 @@ + + + + diff --git a/smart-flow-web/src/components/support/data-tracer/data-tracer-timeline.vue b/smart-flow-web/src/components/support/data-tracer/data-tracer-timeline.vue new file mode 100644 index 0000000..ddb7e05 --- /dev/null +++ b/smart-flow-web/src/components/support/data-tracer/data-tracer-timeline.vue @@ -0,0 +1,50 @@ + + + + diff --git a/smart-flow-web/src/components/support/data-tracer/index.vue b/smart-flow-web/src/components/support/data-tracer/index.vue new file mode 100644 index 0000000..d807227 --- /dev/null +++ b/smart-flow-web/src/components/support/data-tracer/index.vue @@ -0,0 +1,180 @@ + + + diff --git a/smart-flow-web/src/components/support/dict-code-select/index.vue b/smart-flow-web/src/components/support/dict-code-select/index.vue new file mode 100644 index 0000000..10852a5 --- /dev/null +++ b/smart-flow-web/src/components/support/dict-code-select/index.vue @@ -0,0 +1,83 @@ + + + + diff --git a/smart-flow-web/src/components/support/dict-label/index.vue b/smart-flow-web/src/components/support/dict-label/index.vue new file mode 100644 index 0000000..1f5d3f0 --- /dev/null +++ b/smart-flow-web/src/components/support/dict-label/index.vue @@ -0,0 +1,16 @@ + + + diff --git a/smart-flow-web/src/components/support/dict-select/index.vue b/smart-flow-web/src/components/support/dict-select/index.vue new file mode 100644 index 0000000..49f88bf --- /dev/null +++ b/smart-flow-web/src/components/support/dict-select/index.vue @@ -0,0 +1,103 @@ + + + + diff --git a/smart-flow-web/src/components/support/file-preview-modal/index.vue b/smart-flow-web/src/components/support/file-preview-modal/index.vue new file mode 100644 index 0000000..03f46ca --- /dev/null +++ b/smart-flow-web/src/components/support/file-preview-modal/index.vue @@ -0,0 +1,83 @@ + + + + + + diff --git a/smart-flow-web/src/components/support/file-preview/index.vue b/smart-flow-web/src/components/support/file-preview/index.vue new file mode 100644 index 0000000..665708c --- /dev/null +++ b/smart-flow-web/src/components/support/file-preview/index.vue @@ -0,0 +1,79 @@ + + + diff --git a/smart-flow-web/src/components/support/file-upload/index.vue b/smart-flow-web/src/components/support/file-upload/index.vue new file mode 100644 index 0000000..adae37a --- /dev/null +++ b/smart-flow-web/src/components/support/file-upload/index.vue @@ -0,0 +1,238 @@ + + + + diff --git a/smart-flow-web/src/components/support/table-header-cell/index.vue b/smart-flow-web/src/components/support/table-header-cell/index.vue new file mode 100644 index 0000000..2714bc0 --- /dev/null +++ b/smart-flow-web/src/components/support/table-header-cell/index.vue @@ -0,0 +1,178 @@ + + + diff --git a/smart-flow-web/src/components/support/table-operator/index.vue b/smart-flow-web/src/components/support/table-operator/index.vue new file mode 100644 index 0000000..c2f6710 --- /dev/null +++ b/smart-flow-web/src/components/support/table-operator/index.vue @@ -0,0 +1,222 @@ + + + + + diff --git a/smart-flow-web/src/components/support/table-operator/smart-table-column-merge.js b/smart-flow-web/src/components/support/table-operator/smart-table-column-merge.js new file mode 100644 index 0000000..3a19736 --- /dev/null +++ b/smart-flow-web/src/components/support/table-operator/smart-table-column-merge.js @@ -0,0 +1,63 @@ +/* + * 表格列设置 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-08-26 23:45:51 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ + +import _ from 'lodash'; + +/** + * 将原视表格列和用户表格列进行合并、排序 + * @param {*} originalTableColumnArray + * @param {*} userTableColumnArray + */ +export function mergeColumn(originalTableColumnArray, userTableColumnArray) { + let saveFlag = false; + if (!userTableColumnArray) { + return originalTableColumnArray; + } + + //第一步:将用户的列数据转为Map,以后备使用 + let userTableColumnMap = new Map(); + for (const item of userTableColumnArray) { + userTableColumnMap.set(item.columnKey, item); + } + + //第二步:以前端的table columns列为基础,将用户后端的数据填充到前端表格列里 + let fontColumnSort = 1; + let newColumns = []; + for (const fontColumn of originalTableColumnArray) { + //原始表格列默认显示 + fontColumn.columnKey = fontColumn.dataIndex; + fontColumn.showFlag = true; + fontColumn.sort = fontColumnSort; + + // 如果用户存在此列,则覆盖 sort和width、showFlag字段 + let userColumn = userTableColumnMap.get(fontColumn.columnKey); + if (userColumn) { + fontColumn.sort = userColumn.sort; + fontColumn.showFlag = userColumn.showFlag; + if (fontColumn.dragAndDropFlag) { + saveFlag = true; + delete fontColumn.dragAndDropFlag; + } else { + if (userColumn.width) { + fontColumn.width = userColumn.width; + } + } + } + newColumns.push(fontColumn); + fontColumnSort++; + } + + //第三步:前端列进行排序 + newColumns = _.sortBy(newColumns, (e) => e.sort); + return { + newColumns, + saveFlag, + }; +} diff --git a/smart-flow-web/src/components/support/table-operator/smart-table-column-modal.vue b/smart-flow-web/src/components/support/table-operator/smart-table-column-modal.vue new file mode 100644 index 0000000..6071202 --- /dev/null +++ b/smart-flow-web/src/components/support/table-operator/smart-table-column-modal.vue @@ -0,0 +1,312 @@ + + + + diff --git a/smart-flow-web/src/components/system/department-tree-select/index.vue b/smart-flow-web/src/components/system/department-tree-select/index.vue new file mode 100644 index 0000000..e9de799 --- /dev/null +++ b/smart-flow-web/src/components/system/department-tree-select/index.vue @@ -0,0 +1,58 @@ + + + diff --git a/smart-flow-web/src/components/system/employee-select/index.vue b/smart-flow-web/src/components/system/employee-select/index.vue new file mode 100644 index 0000000..c963957 --- /dev/null +++ b/smart-flow-web/src/components/system/employee-select/index.vue @@ -0,0 +1,97 @@ + + + + diff --git a/smart-flow-web/src/components/system/employee-table-select-modal/index.vue b/smart-flow-web/src/components/system/employee-table-select-modal/index.vue new file mode 100644 index 0000000..b68dfc3 --- /dev/null +++ b/smart-flow-web/src/components/system/employee-table-select-modal/index.vue @@ -0,0 +1,199 @@ + + + diff --git a/smart-flow-web/src/components/system/menu-tree-select/index.vue b/smart-flow-web/src/components/system/menu-tree-select/index.vue new file mode 100644 index 0000000..e7b1b62 --- /dev/null +++ b/smart-flow-web/src/components/system/menu-tree-select/index.vue @@ -0,0 +1,76 @@ + + + diff --git a/smart-flow-web/src/components/system/position-select/index.vue b/smart-flow-web/src/components/system/position-select/index.vue new file mode 100644 index 0000000..e90ff9d --- /dev/null +++ b/smart-flow-web/src/components/system/position-select/index.vue @@ -0,0 +1,89 @@ + + + + diff --git a/smart-flow-web/src/components/system/region-tree-select/index.vue b/smart-flow-web/src/components/system/region-tree-select/index.vue new file mode 100644 index 0000000..b0a0888 --- /dev/null +++ b/smart-flow-web/src/components/system/region-tree-select/index.vue @@ -0,0 +1,58 @@ + + + diff --git a/smart-flow-web/src/config/app-config.js b/smart-flow-web/src/config/app-config.js new file mode 100644 index 0000000..54a3ef9 --- /dev/null +++ b/smart-flow-web/src/config/app-config.js @@ -0,0 +1,49 @@ +/* + * 应用默认配置 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-03 22:07:01 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +export const appDefaultConfig = { + // i18n 语言选择 + language: 'zh_CN', + // 布局: side 或者 side-expand 或者 top + layout: 'side', + // 侧边菜单宽度 , 默认为200px + sideMenuWidth: 200, + //标签页位置 + pageTagLocation: 'top', + // 菜单主题 + sideMenuTheme: 'dark', + // 主题颜色索引 + colorIndex: 0, + // 顶部菜单页面宽度 + pageWidth: '99%', + // 圆角 + borderRadius: 6, + // 菜单展开模式 + flatPattern: true, + // 标签页 + pageTagFlag: true, + // 标签页样式: default、 antd、chrome + pageTagStyle: 'default', + // 面包屑 + breadCrumbFlag: true, + // 页脚 + footerFlag: true, + // 帮助文档 + helpDocFlag: false, + // 帮助文档默认展开 + helpDocExpandFlag: false, + // 水印 + watermarkFlag: true, + // 网站名称 + websiteName: '维护警权一键通', + // 主题颜色 + primaryColor: '#1677ff', + // 紧凑 + compactFlag: false, +}; diff --git a/smart-flow-web/src/constants/business/erp/category-const.js b/smart-flow-web/src/constants/business/erp/category-const.js new file mode 100644 index 0000000..e401e14 --- /dev/null +++ b/smart-flow-web/src/constants/business/erp/category-const.js @@ -0,0 +1,23 @@ +/* + * 类别 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-03 22:07:51 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +export const CATEGORY_TYPE_ENUM = { + GOODS: { + value: 1, + desc: '商品', + }, + DEMO: { + value: 2, + desc: '演示分类', + }, +}; + +export default { + CATEGORY_TYPE_ENUM, +}; diff --git a/smart-flow-web/src/constants/business/erp/goods-const.js b/smart-flow-web/src/constants/business/erp/goods-const.js new file mode 100644 index 0000000..599a62d --- /dev/null +++ b/smart-flow-web/src/constants/business/erp/goods-const.js @@ -0,0 +1,26 @@ +/* + * 商品 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-03 22:08:10 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +export const GOODS_STATUS_ENUM = { + APPOINTMENT: { + value: 1, + desc: '预约中', + }, + SELL: { + value: 2, + desc: '售卖中', + }, + SELL_OUT: { + value: 3, + desc: '售罄', + }, +}; +export default { + GOODS_STATUS_ENUM, +}; diff --git a/smart-flow-web/src/constants/business/message/message-const.js b/smart-flow-web/src/constants/business/message/message-const.js new file mode 100644 index 0000000..b71d130 --- /dev/null +++ b/smart-flow-web/src/constants/business/message/message-const.js @@ -0,0 +1,30 @@ +/* + * @Description: file content + * @Author: yandy + * @Date: 2022-07-24 21:43:43 + * @LastEditors: + * @LastEditTime: 2022-07-24 21:43:43 + */ +export const MESSAGE_TYPE_ENUM = { + MAIL: { + value: 1, + desc: '站内信' + }, + ORDER: { + value: 2, + desc: '订单' + }, + }; + + +export const MESSAGE_RECEIVE_TYPE_ENUM = { + EMPLOYEE: { + value: 1, + desc: '员工' + }, + }; + +export default { + MESSAGE_TYPE_ENUM, + MESSAGE_RECEIVE_TYPE_ENUM +}; diff --git a/smart-flow-web/src/constants/business/oa/enterprise-const.js b/smart-flow-web/src/constants/business/oa/enterprise-const.js new file mode 100644 index 0000000..b8aaccf --- /dev/null +++ b/smart-flow-web/src/constants/business/oa/enterprise-const.js @@ -0,0 +1,24 @@ +/* + * 企业 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-03 22:07:27 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ + +export const ENTERPRISE_TYPE_ENUM = { + NORMAL: { + value: 1, + desc: '有限企业', + }, + FOREIGN: { + value: 2, + desc: '外资企业', + }, +}; + +export default { + ENTERPRISE_TYPE_ENUM, +}; diff --git a/smart-flow-web/src/constants/business/oa/notice-const.js b/smart-flow-web/src/constants/business/oa/notice-const.js new file mode 100644 index 0000000..94ee083 --- /dev/null +++ b/smart-flow-web/src/constants/business/oa/notice-const.js @@ -0,0 +1,36 @@ +/* + * 通知 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-03 22:07:27 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ + +export const NOTICE_VISIBLE_RANGE_DATA_TYPE_ENUM = { + EMPLOYEE: { + value: 1, + desc: '员工', + }, + DEPARTMENT: { + value: 2, + desc: '部门', + }, +}; + +export const NOTICE_TYPE_ENUM = { + ANNOUNCEMENT: { + value: 1, + desc: '公告', + }, + NOTICE: { + value: 2, + desc: '通知', + }, +}; + +export default { + NOTICE_VISIBLE_RANGE_DATA_TYPE_ENUM, + NOTICE_TYPE_ENUM, +}; diff --git a/smart-flow-web/src/constants/business/rights/flow-status-enum.js b/smart-flow-web/src/constants/business/rights/flow-status-enum.js new file mode 100644 index 0000000..4ebdae8 --- /dev/null +++ b/smart-flow-web/src/constants/business/rights/flow-status-enum.js @@ -0,0 +1,78 @@ +/** + * 维权流程状态枚举 + * + * @Author: SmartFlow + * @Date: 2026-03-18 + */ + +export const RIGHTS_FLOW_STATUS_ENUM = { + /** + * 0 待提交 + */ + PENDING_SUBMIT: 0, + + /** + * 1 待受理 + */ + PENDING_ACCEPT: 1, + + /** + * 2 受理中 + */ + ACCEPTING: 2, + + /** + * 3 已受理 + */ + ACCEPTED: 3, + + /** + * 4 办理中 + */ + PROCESSING: 4, + + /** + * 5 已办结 + */ + COMPLETED: 5, + + /** + * 9 不受理 + */ + REJECTED: 9, + + /** + * 6 已评价 + */ + PENDING_EVALUATE: 6, +}; + +// 状态颜色映射 +export const FLOW_STATUS_COLOR_MAP = { + [RIGHTS_FLOW_STATUS_ENUM.PENDING_SUBMIT]: 'default', + [RIGHTS_FLOW_STATUS_ENUM.PENDING_ACCEPT]: 'processing', + [RIGHTS_FLOW_STATUS_ENUM.ACCEPTING]: 'processing', + [RIGHTS_FLOW_STATUS_ENUM.ACCEPTED]: 'success', + [RIGHTS_FLOW_STATUS_ENUM.PROCESSING]: 'processing', + [RIGHTS_FLOW_STATUS_ENUM.COMPLETED]: 'success', + [RIGHTS_FLOW_STATUS_ENUM.PENDING_EVALUATE]: 'warning', + [RIGHTS_FLOW_STATUS_ENUM.REJECTED]: 'error', +}; + +// 状态文本映射 +export const FLOW_STATUS_TEXT_MAP = { + [RIGHTS_FLOW_STATUS_ENUM.PENDING_SUBMIT]: '待提交', + [RIGHTS_FLOW_STATUS_ENUM.PENDING_ACCEPT]: '待受理', + [RIGHTS_FLOW_STATUS_ENUM.ACCEPTING]: '受理中', + [RIGHTS_FLOW_STATUS_ENUM.ACCEPTED]: '已受理', + [RIGHTS_FLOW_STATUS_ENUM.PROCESSING]: '办理中', + [RIGHTS_FLOW_STATUS_ENUM.COMPLETED]: '已办结', + [RIGHTS_FLOW_STATUS_ENUM.PENDING_EVALUATE]: '已评价', + [RIGHTS_FLOW_STATUS_ENUM.REJECTED]: '不受理', +}; + +export default { + RIGHTS_FLOW_STATUS_ENUM, + FLOW_STATUS_COLOR_MAP, + FLOW_STATUS_TEXT_MAP, +}; diff --git a/smart-flow-web/src/constants/common-const.js b/smart-flow-web/src/constants/common-const.js new file mode 100644 index 0000000..9d39a8a --- /dev/null +++ b/smart-flow-web/src/constants/common-const.js @@ -0,0 +1,75 @@ +/* + * 通用常量 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-06 19:57:29 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ + +export const PAGE_SIZE = 10; + +export const PAGE_SIZE_OPTIONS = ['5', '10', '15', '20', '30', '40', '50', '75', '100', '150', '200', '300', '500']; + +//登录页面名字 +export const PAGE_PATH_LOGIN = '/login'; +//首页页面名字 +export const HOME_PAGE_PATH = '/home'; + +//404页面名字 +export const PAGE_PATH_404 = '/404'; + +export const showTableTotal = function (total) { + return `共${total}条`; +}; + +export const FLAG_NUMBER_ENUM = { + TRUE: { + value: 1, + desc: '是', + }, + FALSE: { + value: 0, + desc: '否', + }, +}; + +export const GENDER_ENUM = { + UNKNOWN: { + value: 0, + desc: '未知', + }, + MAN: { + value: 1, + desc: '男', + }, + WOMAN: { + value: 2, + desc: '女', + }, +}; + +export const USER_TYPE_ENUM = { + POLICE_OFFICER: { + value: 1, + desc: '民警', + }, + AUXILIARY_POLICE: { + value: 2, + desc: '辅警', + }, +}; + +export const DATA_TYPE_ENUM = { + NORMAL: { + value: 1, + desc: '普通', + }, + ENCRYPT: { + value: 10, + desc: '加密', + }, + +}; + diff --git a/smart-flow-web/src/constants/flow/flow-const.js b/smart-flow-web/src/constants/flow/flow-const.js new file mode 100644 index 0000000..ba0eeb8 --- /dev/null +++ b/smart-flow-web/src/constants/flow/flow-const.js @@ -0,0 +1,60 @@ +export const FLOW_STATUS_ENUM = { + PENDING_SUBMIT: { + value: 0, + desc: '待提交', + }, + APPROVING: { + value: 1, + desc: '审批中', + }, + APPROVED: { + value: 2, + desc: '审批通过', + }, + AUTO_APPROVED: { + value: 3, + desc: '自动通过', + }, + TERMINATED: { + value: 4, + desc: '终止', + }, + INVALID: { + value: 5, + desc: '作废', + }, + REVOKED: { + value: 6, + desc: '撤销', + }, + RETRIEVED: { + value: 7, + desc: '取回', + }, + COMPLETED: { + value: 8, + desc: '已完成', + }, + RETURNED: { + value: 9, + desc: '已退回', + }, + EXPIRED: { + value: 10, + desc: '失效', + }, +}; +export const COOPERATE_TYPE_ENUM = { + APPROVAL: { value: 1, desc: '审批' }, + TRANSFER: { value: 2, desc: '转办' }, + DELEGATE: { value: 3, desc: '委派' }, + COUNTERSIGN: { value: 4, desc: '会签' }, + VOTE_SIGN: { value: 5, desc: '票签' }, + ADD_SIGN: { value: 6, desc: '加签' }, + REMOVE_SIGN: { value: 7, desc: '减签' }, +} + +export default { + FLOW_STATUS_ENUM, + COOPERATE_TYPE_ENUM +}; \ No newline at end of file diff --git a/smart-flow-web/src/constants/index.js b/smart-flow-web/src/constants/index.js new file mode 100644 index 0000000..6f55c45 --- /dev/null +++ b/smart-flow-web/src/constants/index.js @@ -0,0 +1,44 @@ +/* + * 所有常量入口 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-06 19:58:28 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import menu from './system/menu-const'; +import goods from './business/erp/goods-const'; +import category from './business/erp/category-const'; +import { LOGIN_DEVICE_ENUM } from './system/login-device-const'; +import { FLAG_NUMBER_ENUM, GENDER_ENUM, USER_TYPE_ENUM } from './common-const'; +import { LAYOUT_ENUM } from './layout-const'; +import file from './support/file-const'; +import notice from './business/oa/notice-const'; +import loginLog from './support/login-log-const'; +import enterprise from './business/oa/enterprise-const'; +import message from './business/message/message-const'; +import codeGeneratorConst from './support/code-generator-const'; +import changeLogConst from './support/change-log-const'; +import jobConst from './support/job-const'; +import flowConst from './flow/flow-const'; + +export default { + FLAG_NUMBER_ENUM, + LOGIN_DEVICE_ENUM, + GENDER_ENUM, + USER_TYPE_ENUM, + LAYOUT_ENUM, + ...loginLog, + ...menu, + ...goods, + ...category, + ...file, + ...notice, + ...enterprise, + ...message, + ...codeGeneratorConst, + ...changeLogConst, + ...jobConst, + ...flowConst +}; diff --git a/smart-flow-web/src/constants/layout-const.js b/smart-flow-web/src/constants/layout-const.js new file mode 100644 index 0000000..07c58ea --- /dev/null +++ b/smart-flow-web/src/constants/layout-const.js @@ -0,0 +1,42 @@ +/* + * 布局格式 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-06 19:58:41 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +export const LAYOUT_ENUM = { + SIDE: { + value: 'side', + desc: '传统', + }, + SIDE_EXPAND: { + value: 'side-expand', + desc: '展开', + }, + TOP: { + value: 'top', + desc: '顶部', + }, + TOP_EXPAND: { + value: 'top-expand', + desc: '分组', + }, +}; + +export const PAGE_TAG_ENUM = { + DEFAULT: { + value: 'default', + desc: '默认', + }, + ANTD: { + value: 'antd', + desc: 'Ant Design', + }, + CHROME: { + value: 'chrome', + desc: 'Chrome', + }, +}; diff --git a/smart-flow-web/src/constants/local-storage-key-const.js b/smart-flow-web/src/constants/local-storage-key-const.js new file mode 100644 index 0000000..b48a1f6 --- /dev/null +++ b/smart-flow-web/src/constants/local-storage-key-const.js @@ -0,0 +1,33 @@ +/* + * key 常量 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-06 19:58:50 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ + +/** + * key前缀 + */ +const KEY_PREFIX = 'smart_admin_'; +/** + * localStorageKey集合 + */ +export default { + // 用户token + USER_TOKEN: `${KEY_PREFIX}user_token`, + // 用户权限点 + USER_POINTS: `${KEY_PREFIX}user_points`, + // 用户的tag列表 + USER_TAG_NAV: `${KEY_PREFIX}user_tag_nav`, + // app config 配置信息 + APP_CONFIG: `${KEY_PREFIX}app_config`, + // 首页快捷入口 + HOME_QUICK_ENTRY: `${KEY_PREFIX}home_quick_entry`, + // 通知信息已读 + NOTICE_READ: `${KEY_PREFIX}notice_read`, + // 待办 + TO_BE_DONE: `${KEY_PREFIX}to_be_done`, +}; diff --git a/smart-flow-web/src/constants/regular-const.js b/smart-flow-web/src/constants/regular-const.js new file mode 100644 index 0000000..4c1338c --- /dev/null +++ b/smart-flow-web/src/constants/regular-const.js @@ -0,0 +1,29 @@ +/* + * 正则常量 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-06 19:59:05 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +export const regular = { + phone: /^(13|14|15|16|17|18|19)\d{9}$/, + qq: /^[1-9]\d{3,}$/, + linkUrl: + /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/, + // eslint-disable-next-line no-useless-escape + isNumber: /(^[\-1-9][1-9]*(.[1-9]+)?)$/, // 判断是否为数字,除了0 外 + isLandlineOrPhone: /^((0\d{2,3}-\d{7,8})|(1[3584]\d{9}))$/, // 验证 座机 或者手机 + account: /^[a-z0-9]{3,16}$/, // 请输入3-16位(小写字母|数字)的账号 + mobileAccount: /^[a-z0-9]{6,16}$/, // 请输入6-16位(小写字母|数字)的账号(和移动端保持一致) + accountDesc: '请输入3-16位(小写字母|数字)的账号', + pwd: /^[A-Za-z0-9._]{6,16}$/, // 请输入6-16位(大小写字母|数字|小数点|下划线)的密码 + pwdDesc: '请输入6-16位(大小写字母|数字|小数点|下划线)的密码', + delBlankSpace: /\s+/g, // 删除空格 + isPdfReg: new RegExp(/\.(pdf|PDF)/), + isElseFileReg: new RegExp(/\.(doc|docx|xls|xlsx|txt|ppt|pptx|pps|ppxs)/), + isIdentityCard: /^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X|x)$/, // 验证身份证号 + isChinese: /^[\u4e00-\u9fa5]+$/gi, // 验证是否汉字 + +}; diff --git a/smart-flow-web/src/constants/support/change-log-const.js b/smart-flow-web/src/constants/support/change-log-const.js new file mode 100644 index 0000000..d7c29f5 --- /dev/null +++ b/smart-flow-web/src/constants/support/change-log-const.js @@ -0,0 +1,29 @@ +/** + * 系统更新日志 枚举 + * + * @Author: 卓大 + * @Date: 2022-09-26 14:53:50 + * @Copyright 1024创新实验室 + */ + +/** + * 更新类型:[1:特大版本功能更新;2:功能更新;3:bug修复] + */ +export const CHANGE_LOG_TYPE_ENUM = { + MAJOR_UPDATE: { + value: 1, + desc: '重大更新', + }, + FUNCTION_UPDATE: { + value: 2, + desc: '功能更新', + }, + BUG_FIX: { + value: 3, + desc: 'Bug修复', + }, +}; + +export default { + CHANGE_LOG_TYPE_ENUM, +}; diff --git a/smart-flow-web/src/constants/support/code-generator-const.js b/smart-flow-web/src/constants/support/code-generator-const.js new file mode 100644 index 0000000..ddf2512 --- /dev/null +++ b/smart-flow-web/src/constants/support/code-generator-const.js @@ -0,0 +1,118 @@ +/* + * 代码生成 枚举 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-03 22:09:10 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ + +// 前端组件类型 +export const CODE_FRONT_COMPONENT_ENUM = { + INPUT: { + value: 'Input', + desc: '输入框', + }, + INPUT_NUMBER: { + value: 'InputNumber', + desc: '数字输入框', + }, + TEXTAREA: { + value: 'Textarea', + desc: '文本', + }, + BOOLEAN_SELECT: { + value: 'BooleanSelect', + desc: '布尔下拉框', + }, + ENUM_SELECT: { + value: 'SmartEnumSelect', + desc: '枚举下拉框', + }, + DICT_SELECT: { + value: 'DictSelect', + desc: '字典下拉', + }, + DATE: { + value: 'Date', + desc: '日期选择', + }, + DATE_TIME: { + value: 'DateTime', + desc: '时间选择', + }, + FILE_UPLOAD: { + value: 'FileUpload', + desc: '文件上传', + }, +}; + +// 新增、修改 页面类型 +export const CODE_INSERT_AND_UPDATE_PAGE_ENUM = { + MODAL: { + value: 'modal', + desc: '弹窗', + }, + DRAWER: { + value: 'drawer', + desc: '抽屉', + }, + PAGE: { + value: 'Page', + desc: '新页面', + }, +}; + +// 删除类型 +export const CODE_DELETE_ENUM = { + SINGLE: { + value: 'Single', + desc: '单个删除', + }, + BATCH: { + value: 'Batch', + desc: '批量删除', + }, + SINGLE_AND_BATCH: { + value: 'SingleAndBatch', + desc: '单个删除和批量删除', + }, +}; + +/** + * 查询类型 + */ +export const CODE_QUERY_FIELD_QUERY_TYPE_ENUM = { + LIKE: { + value: 'Like', + desc: '模糊查询', + }, + EQUAL: { + value: 'Equal', + desc: '等于查询', + }, + DATE_RANGE: { + value: 'DateRange', + desc: '日期范围', + }, + DATE: { + value: 'Date', + desc: '指定日期', + }, + ENUM: { + value: 'Enum', + desc: '枚举', + }, + DICT: { + value: 'Dict', + desc: '字典', + }, +}; + +export default { + CODE_FRONT_COMPONENT_ENUM, + CODE_INSERT_AND_UPDATE_PAGE_ENUM, + CODE_DELETE_ENUM, + CODE_QUERY_FIELD_QUERY_TYPE_ENUM +}; diff --git a/smart-flow-web/src/constants/support/data-tracer-const.js b/smart-flow-web/src/constants/support/data-tracer-const.js new file mode 100644 index 0000000..371c682 --- /dev/null +++ b/smart-flow-web/src/constants/support/data-tracer-const.js @@ -0,0 +1,29 @@ +/* + * 数据变动记录 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-03 22:09:10 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ + +// 业务类型 +export const DATA_TRACER_TYPE_ENUM = { + GOODS: { + value: 1, + desc: '商品', + }, + OA_NOTICE: { + value: 2, + desc: 'OA-通知公告', + }, + OA_ENTERPRISE: { + value: 3, + desc: 'OA-企业信息', + }, +}; + +export default { + DATA_TRACER_TYPE_ENUM, +}; diff --git a/smart-flow-web/src/constants/support/dict-const.js b/smart-flow-web/src/constants/support/dict-const.js new file mode 100644 index 0000000..0e6f337 --- /dev/null +++ b/smart-flow-web/src/constants/support/dict-const.js @@ -0,0 +1,26 @@ +/** + * 字典key 编码 常量 + * + * 该常量来自于 字典管理中的数据,写在该文件目的是为了统一引用,将来好修改 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2024-09-03 22:09:10 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ + +export const DICT_SPLIT = ','; + +export const DICT_CODE_ENUM = { + GOODS_PLACE: 'GOODS_PLACE', + LEAVE_TYPE: 'LEAVE_TYPE', + POLICE_TYPE: 'POLICE_TYPE', + RIGHTS_APPLY_TYPE: 'RIGHTS_APPLY_TYPE', + INFRINGEMENT_TYPE: 'INFRINGEMENT_TYPE', + RIGHTS_FLOW_STATUS: 'RIGHTS_FLOW_STATUS', +}; + +export default { + DICT_CODE_ENUM, +}; diff --git a/smart-flow-web/src/constants/support/file-const.js b/smart-flow-web/src/constants/support/file-const.js new file mode 100644 index 0000000..4bb1e31 --- /dev/null +++ b/smart-flow-web/src/constants/support/file-const.js @@ -0,0 +1,57 @@ +/* + * 文件类型 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-03 22:09:10 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +// 文件上传类型 +export const FILE_FOLDER_TYPE_ENUM = { + COMMON: { + value: 1, + desc: '通用', + }, + NOTICE: { + value: 2, + desc: '公告', + }, + HELP_DOC: { + value: 3, + desc: '帮助中心', + }, + FEEDBACK: { + value: 4, + desc: '意见反馈', + }, + + WORK_BOOK: { + value: 5, + desc: '工作手册', + }, +}; + +// 工作手册类型 +export const WORK_BOOK_TYPE_ENUM = { + COMMON: { + value: 1, + desc: '常用文书', + }, + LEGAL: { + value: 2, + desc: '法律文书', + }, + REGULATION: { + value: 3, + desc: '相关规定', + }, + OTHER: { + value: 4, + desc: '其他', + }, +}; +export default { + FILE_FOLDER_TYPE_ENUM, + WORK_BOOK_TYPE_ENUM, +}; diff --git a/smart-flow-web/src/constants/support/job-const.js b/smart-flow-web/src/constants/support/job-const.js new file mode 100644 index 0000000..6d89f8e --- /dev/null +++ b/smart-flow-web/src/constants/support/job-const.js @@ -0,0 +1,18 @@ +/** + * job 常量 + * @type {{CRON: {value: number, desc: string}, FIXED_DELAY: {value: number, desc: string}}} + */ +export const TRIGGER_TYPE_ENUM = { + CRON: { + value: 'cron', + desc: 'cron表达式', + }, + FIXED_DELAY: { + value: 'fixed_delay', + desc: '固定间隔', + }, +}; + +export default { + TRIGGER_TYPE_ENUM, +}; diff --git a/smart-flow-web/src/constants/support/login-log-const.js b/smart-flow-web/src/constants/support/login-log-const.js new file mode 100644 index 0000000..0420500 --- /dev/null +++ b/smart-flow-web/src/constants/support/login-log-const.js @@ -0,0 +1,27 @@ +/* + * 登录日志 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-03 22:08:45 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +export const LOGIN_RESULT_ENUM = { + LOGIN_SUCCESS: { + value: 0, + desc: '登录成功', + }, + LOGIN_FAIL: { + value: 1, + desc: '登录失败', + }, + LOGIN_OUT: { + value: 2, + desc: '退出登录', + }, +}; + +export default { + LOGIN_RESULT_ENUM, +}; diff --git a/smart-flow-web/src/constants/support/table-id-const.js b/smart-flow-web/src/constants/support/table-id-const.js new file mode 100644 index 0000000..3a937fe --- /dev/null +++ b/smart-flow-web/src/constants/support/table-id-const.js @@ -0,0 +1,66 @@ +/* + * @Description: 表格id + * @Author: zhuoda + * @Date: 2022-08-21 + * @LastEditTime: 2022-08-21 + * @LastEditors: zhuoda + */ + +//system系统功能表格初始化id +let systemInitTableId = 10000; + +//support支撑功能表格初始化id +let supportInitTableId = 20000; + +//业务表格初始化id +let businessOAInitTableId = 30000; + +let businessERPInitTableId = 40000; + +export const TABLE_ID_CONST = { + /** + * 业务 + */ + BUSINESS: { + OA: { + NOTICE: businessOAInitTableId + 1, //通知公告 + ENTERPRISE: businessOAInitTableId + 2, //企业信息 + ENTERPRISE_EMPLOYEE: businessOAInitTableId + 3, //企业员工 + ENTERPRISE_BANK: businessOAInitTableId + 4, //企业银行 + ENTERPRISE_INVOICE: businessOAInitTableId + 5, //企业发票 + }, + ERP: { + GOODS: businessERPInitTableId + 1, //商品管理 + }, + RIGHTS:{ + APPLY: businessOAInitTableId + 6, //维权申请 + RECORD: businessOAInitTableId + 7, //维权报备 + CASE: businessOAInitTableId + 8, //维权办件 + } + }, + + /** + * 系统 + */ + SYSTEM: { + EMPLOYEE: systemInitTableId + 1, //员工 + MENU: systemInitTableId + 2, //菜单 + POSITION: systemInitTableId + 3, //职位 + }, + /** + * 支撑 + */ + SUPPORT: { + CONFIG: supportInitTableId + 1, //参数配置 + DICT: supportInitTableId + 2, //字典 + SERIAL_NUMBER: supportInitTableId + 3, //单号 + OPERATE_LOG: supportInitTableId + 4, //请求监控 + HEART_BEAT: supportInitTableId + 5, //心跳 + LOGIN_LOG: supportInitTableId + 6, //登录日志 + RELOAD: supportInitTableId + 7, //reload + HELP_DOC: supportInitTableId + 8, //帮助文档 + JOB: supportInitTableId + 9, //Job + JOB_LOG: supportInitTableId + 10, //JobLog + MAIL: supportInitTableId + 11, + }, +}; diff --git a/smart-flow-web/src/constants/system/employee-const.js b/smart-flow-web/src/constants/system/employee-const.js new file mode 100644 index 0000000..61503b1 --- /dev/null +++ b/smart-flow-web/src/constants/system/employee-const.js @@ -0,0 +1,27 @@ +/* + * 员工 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-03 22:08:45 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +export const GENDER_ENUM = { + UNKNOWN: { + value: 0, + desc: '未知', + }, + MAN: { + value: 1, + desc: '男', + }, + WOMAN: { + value: 2, + desc: '女', + }, +} + +export default { + GENDER_ENUM, +} diff --git a/smart-flow-web/src/constants/system/home-const.js b/smart-flow-web/src/constants/system/home-const.js new file mode 100644 index 0000000..c0c37db --- /dev/null +++ b/smart-flow-web/src/constants/system/home-const.js @@ -0,0 +1,15 @@ +/* + * 首页页面名词 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-06 19:56:43 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ + +//首页页面名字 +export const HOME_PAGE_NAME = 'Home'; + +//首页页面路径 +export const HOME_PAGE_PATH = '/home'; diff --git a/smart-flow-web/src/constants/system/login-device-const.js b/smart-flow-web/src/constants/system/login-device-const.js new file mode 100644 index 0000000..f203121 --- /dev/null +++ b/smart-flow-web/src/constants/system/login-device-const.js @@ -0,0 +1,31 @@ +/* + * 登录设备 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-06 19:56:56 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +export const LOGIN_DEVICE_ENUM = { + PC: { + value: 1, + desc: '电脑端', + }, + ANDROID: { + value: 2, + desc: '安卓', + }, + APPLE: { + value: 3, + desc: '苹果', + }, + H5: { + value: 3, + desc: 'H5', + }, +}; + +export default { + LOGIN_DEVICE_ENUM, +}; diff --git a/smart-flow-web/src/constants/system/menu-const.js b/smart-flow-web/src/constants/system/menu-const.js new file mode 100644 index 0000000..6adf4b1 --- /dev/null +++ b/smart-flow-web/src/constants/system/menu-const.js @@ -0,0 +1,43 @@ +/* + * 菜单类型 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-06 19:57:04 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +export const MENU_TYPE_ENUM = { + CATALOG: { + value: 1, + desc: '目录', + }, + MENU: { + value: 2, + desc: '菜单', + }, + POINTS: { + value: 3, + desc: '功能点', + }, +}; + +/** + * 权限类型 + */ +export const MENU_PERMS_TYPE_ENUM = { + SA_TOKEN: { + value: 1, + desc: 'Sa-Token模式', + } +}; + +/** + * 默认的顶级菜单id为0 + */ +export const MENU_DEFAULT_PARENT_ID = 0; + +export default { + MENU_TYPE_ENUM, + MENU_PERMS_TYPE_ENUM +}; diff --git a/smart-flow-web/src/directives/privilege.js b/smart-flow-web/src/directives/privilege.js new file mode 100644 index 0000000..d425664 --- /dev/null +++ b/smart-flow-web/src/directives/privilege.js @@ -0,0 +1,29 @@ +/* + * 权限 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-06 20:00:40 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ + +import { useUserStore } from '/@/store/modules/system/user'; +import _ from 'lodash'; + +export function privilegeDirective(el, binding) { + // 超级管理员 + if (useUserStore().administratorFlag) { + return true; + } + // 获取功能点权限 + let userPointsList = useUserStore().getPointList; + if (!userPointsList) { + return false; + } + // 如果没有权限,删除节点 + if (!_.some(userPointsList, ['webPerms', binding.value])) { + el.parentNode.removeChild(el); + } + return true; +} diff --git a/smart-flow-web/src/i18n/index.js b/smart-flow-web/src/i18n/index.js new file mode 100644 index 0000000..44c8b97 --- /dev/null +++ b/smart-flow-web/src/i18n/index.js @@ -0,0 +1,41 @@ +/* + * 国际化入口文件 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-06 20:01:19 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ + +import en_US from './lang/en-US/index'; +import zh_CN from './lang/zh-CN/index'; +import { createI18n } from 'vue-i18n'; +import { getInitializedLanguage } from '/@/store/modules/system/app-config'; + +// 语言选择数组 +export const i18nList = [ + { + text: '简体中文', + value: 'zh_CN', + }, + { + text: 'English', + value: 'en_US', + }, +]; + +export const messages = { + zh_CN: zh_CN, + en_US: en_US, +}; + +const i18n = createI18n({ + fallbackLocale: 'zh_CN', //预设语言环境 + globalInjection: true, + legacy: false, // + locale: getInitializedLanguage(), //默认初始化的语言 + messages, // +}); + +export default i18n; diff --git a/smart-flow-web/src/i18n/lang/en-US/index.js b/smart-flow-web/src/i18n/lang/en-US/index.js new file mode 100644 index 0000000..30a5cf1 --- /dev/null +++ b/smart-flow-web/src/i18n/lang/en-US/index.js @@ -0,0 +1,34 @@ +/* + * 英文国际化 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-06 20:00:57 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import antd from 'ant-design-vue/es/locale/en_US'; +import dayjs from 'dayjs/locale/eu'; + +export default { + antdLocale: antd, + dayjsLocale: dayjs, + 'setting.title': 'Setting', + 'setting.table.yHeight': 'Table Height', + 'setting.pagetag.location': 'TagPage Position', + 'setting.color': 'Theme Color', + 'setting.menu.layout': 'Menu Layout', + 'setting.menu.width': 'Menu Width', + 'setting.menu.theme': 'Menu Theme', + 'setting.page.width': 'Page Width', + 'setting.border.radius': 'Border Radius', + 'setting.compact': 'Page Compact', + 'setting.bread': 'Show Bread', + 'setting.flatPattern': 'Flat Pattern', + 'setting.pagetag': 'Show PageTag', + 'setting.pagetag.style': 'PageTag Style', + 'setting.footer': 'Show Footer', + 'setting.helpdoc': 'Show Helpdoc', + 'setting.helpdoc.expand': 'Helpdoc Expand', + 'setting.watermark': 'Show Watermark', +}; diff --git a/smart-flow-web/src/i18n/lang/zh-CN/index.js b/smart-flow-web/src/i18n/lang/zh-CN/index.js new file mode 100644 index 0000000..6e085c9 --- /dev/null +++ b/smart-flow-web/src/i18n/lang/zh-CN/index.js @@ -0,0 +1,34 @@ +/* + * 中文国际化 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-06 20:01:06 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import antd from 'ant-design-vue/es/locale/zh_CN'; +import dayjs from 'dayjs/locale/zh-cn'; + +export default { + antdLocale: antd, + dayjsLocale: dayjs, + 'setting.title': '网站设置', + 'setting.table.yHeight': '表格高度', + 'setting.pagetag.location': '标签页位置', + 'setting.color': '主题颜色', + 'setting.menu.layout': '菜单布局', + 'setting.menu.width': '菜单宽度', + 'setting.menu.theme': '菜单主题', + 'setting.compact': '页面紧凑', + 'setting.border.radius': '页面圆角', + 'setting.page.width': '页面宽度', + 'setting.bread': '面包屑', + 'setting.flatPattern': '菜单展开模式', + 'setting.pagetag': '标签页', + 'setting.pagetag.style': '标签页样式', + 'setting.footer': '页脚', + 'setting.helpdoc': '帮助文档', + 'setting.helpdoc.expand': '帮助文档展开', + 'setting.watermark': '水印', +}; diff --git a/smart-flow-web/src/layout/components/change-password/regular-change-password-modal.vue b/smart-flow-web/src/layout/components/change-password/regular-change-password-modal.vue new file mode 100644 index 0000000..2f863e6 --- /dev/null +++ b/smart-flow-web/src/layout/components/change-password/regular-change-password-modal.vue @@ -0,0 +1,47 @@ + + + diff --git a/smart-flow-web/src/layout/components/header-user-space/header-avatar.vue b/smart-flow-web/src/layout/components/header-user-space/header-avatar.vue new file mode 100644 index 0000000..509b6c3 --- /dev/null +++ b/smart-flow-web/src/layout/components/header-user-space/header-avatar.vue @@ -0,0 +1,142 @@ + + + + + diff --git a/smart-flow-web/src/layout/components/header-user-space/header-message-detail-modal.vue b/smart-flow-web/src/layout/components/header-user-space/header-message-detail-modal.vue new file mode 100644 index 0000000..3e0e7a3 --- /dev/null +++ b/smart-flow-web/src/layout/components/header-user-space/header-message-detail-modal.vue @@ -0,0 +1,47 @@ + + diff --git a/smart-flow-web/src/layout/components/header-user-space/header-message.vue b/smart-flow-web/src/layout/components/header-user-space/header-message.vue new file mode 100644 index 0000000..971d515 --- /dev/null +++ b/smart-flow-web/src/layout/components/header-user-space/header-message.vue @@ -0,0 +1,250 @@ + + + + + + + diff --git a/smart-flow-web/src/layout/components/header-user-space/header-reset-password-modal/index.vue b/smart-flow-web/src/layout/components/header-user-space/header-reset-password-modal/index.vue new file mode 100644 index 0000000..53fd9d5 --- /dev/null +++ b/smart-flow-web/src/layout/components/header-user-space/header-reset-password-modal/index.vue @@ -0,0 +1,88 @@ + + + diff --git a/smart-flow-web/src/layout/components/header-user-space/header-setting.vue b/smart-flow-web/src/layout/components/header-user-space/header-setting.vue new file mode 100644 index 0000000..fcafc2c --- /dev/null +++ b/smart-flow-web/src/layout/components/header-user-space/header-setting.vue @@ -0,0 +1,378 @@ + + + + + diff --git a/smart-flow-web/src/layout/components/header-user-space/index.vue b/smart-flow-web/src/layout/components/header-user-space/index.vue new file mode 100644 index 0000000..96e4cbe --- /dev/null +++ b/smart-flow-web/src/layout/components/header-user-space/index.vue @@ -0,0 +1,102 @@ + + + + + + diff --git a/smart-flow-web/src/layout/components/menu-location-breadcrumb/index.vue b/smart-flow-web/src/layout/components/menu-location-breadcrumb/index.vue new file mode 100644 index 0000000..ad30078 --- /dev/null +++ b/smart-flow-web/src/layout/components/menu-location-breadcrumb/index.vue @@ -0,0 +1,40 @@ + + + + \ No newline at end of file diff --git a/smart-flow-web/src/layout/components/page-tag/components/antd-tab.vue b/smart-flow-web/src/layout/components/page-tag/components/antd-tab.vue new file mode 100644 index 0000000..22289a6 --- /dev/null +++ b/smart-flow-web/src/layout/components/page-tag/components/antd-tab.vue @@ -0,0 +1,223 @@ + + + + + + diff --git a/smart-flow-web/src/layout/components/page-tag/components/chrome-tab.vue b/smart-flow-web/src/layout/components/page-tag/components/chrome-tab.vue new file mode 100644 index 0000000..5e485b6 --- /dev/null +++ b/smart-flow-web/src/layout/components/page-tag/components/chrome-tab.vue @@ -0,0 +1,342 @@ + + + + + + diff --git a/smart-flow-web/src/layout/components/page-tag/components/default-tab.vue b/smart-flow-web/src/layout/components/page-tag/components/default-tab.vue new file mode 100644 index 0000000..243fcee --- /dev/null +++ b/smart-flow-web/src/layout/components/page-tag/components/default-tab.vue @@ -0,0 +1,222 @@ + + + + + + diff --git a/smart-flow-web/src/layout/components/page-tag/index.vue b/smart-flow-web/src/layout/components/page-tag/index.vue new file mode 100644 index 0000000..4fc5549 --- /dev/null +++ b/smart-flow-web/src/layout/components/page-tag/index.vue @@ -0,0 +1,27 @@ + + + + diff --git a/smart-flow-web/src/layout/components/side-expand-menu/index.vue b/smart-flow-web/src/layout/components/side-expand-menu/index.vue new file mode 100644 index 0000000..06fd240 --- /dev/null +++ b/smart-flow-web/src/layout/components/side-expand-menu/index.vue @@ -0,0 +1,75 @@ + + + + diff --git a/smart-flow-web/src/layout/components/side-expand-menu/recursion-menu.vue b/smart-flow-web/src/layout/components/side-expand-menu/recursion-menu.vue new file mode 100644 index 0000000..67fd92b --- /dev/null +++ b/smart-flow-web/src/layout/components/side-expand-menu/recursion-menu.vue @@ -0,0 +1,109 @@ + + + + diff --git a/smart-flow-web/src/layout/components/side-expand-menu/side-expand-menu-mitt.js b/smart-flow-web/src/layout/components/side-expand-menu/side-expand-menu-mitt.js new file mode 100644 index 0000000..22e6a40 --- /dev/null +++ b/smart-flow-web/src/layout/components/side-expand-menu/side-expand-menu-mitt.js @@ -0,0 +1,11 @@ +/* + * 展开菜单 event bus + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-07-12 23:32:48 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import mitt from 'mitt'; +export default mitt(); diff --git a/smart-flow-web/src/layout/components/side-expand-menu/sub-menu.vue b/smart-flow-web/src/layout/components/side-expand-menu/sub-menu.vue new file mode 100644 index 0000000..ff46877 --- /dev/null +++ b/smart-flow-web/src/layout/components/side-expand-menu/sub-menu.vue @@ -0,0 +1,46 @@ + + + + diff --git a/smart-flow-web/src/layout/components/side-expand-menu/top-menu.vue b/smart-flow-web/src/layout/components/side-expand-menu/top-menu.vue new file mode 100644 index 0000000..4ca8a5f --- /dev/null +++ b/smart-flow-web/src/layout/components/side-expand-menu/top-menu.vue @@ -0,0 +1,109 @@ + + + + diff --git a/smart-flow-web/src/layout/components/side-help-doc/components/contact-modal.vue b/smart-flow-web/src/layout/components/side-help-doc/components/contact-modal.vue new file mode 100644 index 0000000..5b9c793 --- /dev/null +++ b/smart-flow-web/src/layout/components/side-help-doc/components/contact-modal.vue @@ -0,0 +1,102 @@ + + + + diff --git a/smart-flow-web/src/layout/components/side-help-doc/components/feedback-modal.vue b/smart-flow-web/src/layout/components/side-help-doc/components/feedback-modal.vue new file mode 100644 index 0000000..0cfdc0e --- /dev/null +++ b/smart-flow-web/src/layout/components/side-help-doc/components/feedback-modal.vue @@ -0,0 +1,85 @@ + + + diff --git a/smart-flow-web/src/layout/components/side-help-doc/index.vue b/smart-flow-web/src/layout/components/side-help-doc/index.vue new file mode 100644 index 0000000..8770f84 --- /dev/null +++ b/smart-flow-web/src/layout/components/side-help-doc/index.vue @@ -0,0 +1,234 @@ + + + + + diff --git a/smart-flow-web/src/layout/components/side-menu/index.vue b/smart-flow-web/src/layout/components/side-menu/index.vue new file mode 100644 index 0000000..3e4bccd --- /dev/null +++ b/smart-flow-web/src/layout/components/side-menu/index.vue @@ -0,0 +1,131 @@ + + + + + + diff --git a/smart-flow-web/src/layout/components/side-menu/recursion-menu.vue b/smart-flow-web/src/layout/components/side-menu/recursion-menu.vue new file mode 100644 index 0000000..af9c1ad --- /dev/null +++ b/smart-flow-web/src/layout/components/side-menu/recursion-menu.vue @@ -0,0 +1,115 @@ + + + + + diff --git a/smart-flow-web/src/layout/components/side-menu/sub-menu.vue b/smart-flow-web/src/layout/components/side-menu/sub-menu.vue new file mode 100644 index 0000000..60ccfdc --- /dev/null +++ b/smart-flow-web/src/layout/components/side-menu/sub-menu.vue @@ -0,0 +1,45 @@ + + + diff --git a/smart-flow-web/src/layout/components/smart-footer/index.vue b/smart-flow-web/src/layout/components/smart-footer/index.vue new file mode 100644 index 0000000..527a103 --- /dev/null +++ b/smart-flow-web/src/layout/components/smart-footer/index.vue @@ -0,0 +1,32 @@ + + + + + diff --git a/smart-flow-web/src/layout/components/smart-keep-alive.js b/smart-flow-web/src/layout/components/smart-keep-alive.js new file mode 100644 index 0000000..52cd8d1 --- /dev/null +++ b/smart-flow-web/src/layout/components/smart-keep-alive.js @@ -0,0 +1,39 @@ +/* + * keep-alive + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-06 20:39:54 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import { computed } from 'vue'; +import { useRoute, useRouter } from 'vue-router'; +import { useUserStore } from '/@/store/modules/system/user'; + +export function smartKeepAlive() { + const route = useRoute(); + const router = useRouter(); + // 需要keep-alive的页面 + const keepAliveIncludes = computed(() => { + return useUserStore().keepAliveIncludes || []; + }); + + // ----------------------- iframe相关 ----------------------- + + // 当前路由是否为不需要缓存的iframe页面 + const iframeNotKeepAlivePageFlag = computed(() => route.meta.frameFlag && !route.meta.keepAlive); + // 打开中的tagNav + const tagNav = computed(() => useUserStore().getTagNav || []); + // 已打开的iframe列表 + const keepAliveIframePages = computed(() => { + let routes = router.getRoutes(); + return routes.filter((e) => e.meta.frameFlag && e.meta.keepAlive && tagNav.value.some((t) => t.menuName == e.name)); + }); + return { + route, + keepAliveIncludes, + iframeNotKeepAlivePageFlag, + keepAliveIframePages, + }; +} diff --git a/smart-flow-web/src/layout/components/top-expand-menu/index.vue b/smart-flow-web/src/layout/components/top-expand-menu/index.vue new file mode 100644 index 0000000..193fe22 --- /dev/null +++ b/smart-flow-web/src/layout/components/top-expand-menu/index.vue @@ -0,0 +1,71 @@ + + + + diff --git a/smart-flow-web/src/layout/components/top-expand-menu/recursion-menu.vue b/smart-flow-web/src/layout/components/top-expand-menu/recursion-menu.vue new file mode 100644 index 0000000..992e763 --- /dev/null +++ b/smart-flow-web/src/layout/components/top-expand-menu/recursion-menu.vue @@ -0,0 +1,192 @@ + + + + diff --git a/smart-flow-web/src/layout/components/top-expand-menu/sub-menu.vue b/smart-flow-web/src/layout/components/top-expand-menu/sub-menu.vue new file mode 100644 index 0000000..ff46877 --- /dev/null +++ b/smart-flow-web/src/layout/components/top-expand-menu/sub-menu.vue @@ -0,0 +1,46 @@ + + + + diff --git a/smart-flow-web/src/layout/components/top-expand-menu/top-expand-menu-mitt.js b/smart-flow-web/src/layout/components/top-expand-menu/top-expand-menu-mitt.js new file mode 100644 index 0000000..22e6a40 --- /dev/null +++ b/smart-flow-web/src/layout/components/top-expand-menu/top-expand-menu-mitt.js @@ -0,0 +1,11 @@ +/* + * 展开菜单 event bus + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-07-12 23:32:48 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import mitt from 'mitt'; +export default mitt(); diff --git a/smart-flow-web/src/layout/components/top-expand-menu/top-menu.vue b/smart-flow-web/src/layout/components/top-expand-menu/top-menu.vue new file mode 100644 index 0000000..210ee32 --- /dev/null +++ b/smart-flow-web/src/layout/components/top-expand-menu/top-menu.vue @@ -0,0 +1,151 @@ + + + + diff --git a/smart-flow-web/src/layout/components/top-menu/index.vue b/smart-flow-web/src/layout/components/top-menu/index.vue new file mode 100644 index 0000000..af3d9b9 --- /dev/null +++ b/smart-flow-web/src/layout/components/top-menu/index.vue @@ -0,0 +1,173 @@ + + + + + + diff --git a/smart-flow-web/src/layout/components/top-menu/recursion-menu.vue b/smart-flow-web/src/layout/components/top-menu/recursion-menu.vue new file mode 100644 index 0000000..120e4c8 --- /dev/null +++ b/smart-flow-web/src/layout/components/top-menu/recursion-menu.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/smart-flow-web/src/layout/components/top-menu/sub-menu.vue b/smart-flow-web/src/layout/components/top-menu/sub-menu.vue new file mode 100644 index 0000000..8faeb1a --- /dev/null +++ b/smart-flow-web/src/layout/components/top-menu/sub-menu.vue @@ -0,0 +1,45 @@ + + + diff --git a/smart-flow-web/src/layout/help-doc-layout.vue b/smart-flow-web/src/layout/help-doc-layout.vue new file mode 100644 index 0000000..7bc858a --- /dev/null +++ b/smart-flow-web/src/layout/help-doc-layout.vue @@ -0,0 +1,305 @@ + + + + + + +../lib/smart-watermark diff --git a/smart-flow-web/src/layout/index.vue b/smart-flow-web/src/layout/index.vue new file mode 100644 index 0000000..96ede82 --- /dev/null +++ b/smart-flow-web/src/layout/index.vue @@ -0,0 +1,33 @@ + + + diff --git a/smart-flow-web/src/layout/layout-const.js b/smart-flow-web/src/layout/layout-const.js new file mode 100644 index 0000000..98fc8b5 --- /dev/null +++ b/smart-flow-web/src/layout/layout-const.js @@ -0,0 +1,9 @@ +/** + * layout 相关元素 id + */ +export const LAYOUT_ELEMENT_IDS = { + menu: 'smartAdminMenu', + main: 'smartAdminMain', + header: 'smartAdminHeader', + content: 'smartAdminLayoutContent', +} diff --git a/smart-flow-web/src/layout/side-expand-layout.vue b/smart-flow-web/src/layout/side-expand-layout.vue new file mode 100644 index 0000000..5fc0da4 --- /dev/null +++ b/smart-flow-web/src/layout/side-expand-layout.vue @@ -0,0 +1,319 @@ + + + + diff --git a/smart-flow-web/src/layout/side-layout.vue b/smart-flow-web/src/layout/side-layout.vue new file mode 100644 index 0000000..ffb658d --- /dev/null +++ b/smart-flow-web/src/layout/side-layout.vue @@ -0,0 +1,355 @@ + + + + + diff --git a/smart-flow-web/src/layout/top-expand-layout.vue b/smart-flow-web/src/layout/top-expand-layout.vue new file mode 100644 index 0000000..836cbbb --- /dev/null +++ b/smart-flow-web/src/layout/top-expand-layout.vue @@ -0,0 +1,318 @@ + + + + diff --git a/smart-flow-web/src/layout/top-layout.vue b/smart-flow-web/src/layout/top-layout.vue new file mode 100644 index 0000000..0969493 --- /dev/null +++ b/smart-flow-web/src/layout/top-layout.vue @@ -0,0 +1,201 @@ + + + + + diff --git a/smart-flow-web/src/lib/axios.js b/smart-flow-web/src/lib/axios.js new file mode 100644 index 0000000..d095bf2 --- /dev/null +++ b/smart-flow-web/src/lib/axios.js @@ -0,0 +1,251 @@ +/* + * ajax请求 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-06 20:46:03 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import { message, Modal } from 'ant-design-vue'; +import axios from 'axios'; +import { localRead } from '/@/utils/local-util'; +import { useUserStore } from '/@/store/modules/system/user'; +import { decryptData, encryptData } from './encrypt'; +import { DATA_TYPE_ENUM } from '../constants/common-const'; +import _ from 'lodash'; +import LocalStorageKeyConst from '/@/constants/local-storage-key-const.js'; + +// token的消息头 +const TOKEN_HEADER = 'Authorization'; + +// 创建axios对象 +const smartAxios = axios.create({ + baseURL: import.meta.env.VITE_APP_API_URL, +}); + +// 退出系统 +function logout() { + useUserStore().logout(); + location.href = '/'; +} + +// ================================= 请求拦截器 ================================= + +smartAxios.interceptors.request.use( + (config) => { + // 在发送请求之前消息头加入token token + const token = localRead(LocalStorageKeyConst.USER_TOKEN); + if (token) { + config.headers[TOKEN_HEADER] = 'Bearer ' + token; + } else { + delete config.headers[TOKEN_HEADER]; + } + return config; + }, + (error) => { + // 对请求错误做些什么 + return Promise.reject(error); + } +); + +// ================================= 响应拦截器 ================================= + +// 添加响应拦截器 +smartAxios.interceptors.response.use( + (response) => { + // 根据content-type ,判断是否为 json 数据 + let contentType = response.headers['content-type'] ? response.headers['content-type'] : response.headers['Content-Type']; + if (contentType.indexOf('application/json') === -1) { + return Promise.resolve(response); + } + + // 如果是json数据 + if (response.data && response.data instanceof Blob) { + return Promise.reject(response.data); + } + + // 如果是加密数据 + if (response.data.dataType === DATA_TYPE_ENUM.ENCRYPT.value) { + response.data.encryptData = response.data.data; + let decryptStr = decryptData(response.data.data); + if (decryptStr) { + response.data.data = JSON.parse(decryptStr); + } + } + + const res = response.data; + // console.log('response', res); + if (res.code && res.code !== 1) { + // `token` 过期或者账号已在别处登录 + if (res.code === 30007 || res.code === 30008) { + message.destroy(); + message.error('您没有登录,请重新登录'); + setTimeout(logout, 300); + return Promise.reject(response); + } + + // 等保安全的登录提醒 + if (res.code === 30010 || res.code === 30011) { + Modal.error({ + title: '重要提醒', + content: res.msg, + }); + return Promise.reject(response); + } + + // 长时间未操作系统,需要重新登录 + if (res.code === 30012) { + Modal.error({ + title: '重要提醒', + content: res.msg, + onOk: logout, + }); + setTimeout(logout, 3000); + return Promise.reject(response); + } + message.destroy(); + message.error(res.msg); + return Promise.reject(response); + } else { + return Promise.resolve(res); + } + }, + (error) => { + // 对响应错误做点什么 + if (error.message.indexOf('timeout') !== -1) { + message.destroy(); + message.error('网络超时'); + } else if (error.message === 'Network Error') { + message.destroy(); + message.error('网络连接错误'); + } else if (error.message.indexOf('Request') !== -1) { + message.destroy(); + message.error('网络发生错误'); + } + return Promise.reject(error); + } +); + +// ================================= 对外提供请求方法:通用请求,get, post, 下载download等 ================================= + +/** + * get请求 + */ +export const getRequest = (url, params) => { + return request({ url, method: 'get', params }); +}; + +/** + * 通用请求封装 + * @param config + */ +export const request = (config) => { + return smartAxios.request(config); +}; + +/** + * post请求 + */ +export const postRequest = (url, data) => { + return request({ + data, + url, + method: 'post', + }); +}; + +// ================================= 加密 ================================= + +/** + * 加密请求参数的post请求 + */ +export const postEncryptRequest = (url, data) => { + return request({ + data: { encryptData: encryptData(data) }, + url, + method: 'post', + }); +}; + +// ================================= 下载 ================================= + +export const postDownload = function (url, data) { + request({ + method: 'post', + url, + data, + responseType: 'blob', + }) + .then((data) => { + handleDownloadData(data); + }) + .catch((error) => { + handleDownloadError(error); + }); +}; + +/** + * 文件下载 + */ +export const getDownload = function (url, params) { + request({ + method: 'get', + url, + params, + responseType: 'blob', + }) + .then((data) => { + handleDownloadData(data); + }) + .catch((error) => { + handleDownloadError(error); + }); +}; + +function handleDownloadError(error) { + if (error instanceof Blob) { + const fileReader = new FileReader(); + fileReader.readAsText(error); + fileReader.onload = () => { + const msg = fileReader.result; + const jsonMsg = JSON.parse(msg); + message.destroy(); + message.error(jsonMsg.msg); + }; + } else { + message.destroy(); + message.error('网络发生错误', error); + } +} + +function handleDownloadData(response) { + if (!response) { + return; + } + + // 获取返回类型 + let contentType = _.isUndefined(response.headers['content-type']) ? response.headers['Content-Type'] : response.headers['content-type']; + + // 构建下载数据 + let url = window.URL.createObjectURL(new Blob([response.data], { type: contentType })); + let link = document.createElement('a'); + link.style.display = 'none'; + link.href = url; + + // 从消息头获取文件名 + let str = _.isUndefined(response.headers['content-disposition']) + ? response.headers['Content-Disposition'].split(';')[1] + : response.headers['content-disposition'].split(';')[1]; + + let filename = _.isUndefined(str.split('fileName=')[1]) ? str.split('filename=')[1] : str.split('fileName=')[1]; + link.setAttribute('download', decodeURIComponent(filename)); + + // 触发点击下载 + document.body.appendChild(link); + link.click(); + + // 下载完释放 + document.body.removeChild(link); // 下载完成移除元素 + window.URL.revokeObjectURL(url); // 释放掉blob对象 +} diff --git a/smart-flow-web/src/lib/default-time-ranges.js b/smart-flow-web/src/lib/default-time-ranges.js new file mode 100644 index 0000000..634da84 --- /dev/null +++ b/smart-flow-web/src/lib/default-time-ranges.js @@ -0,0 +1,29 @@ +/* + * 时间选择框快捷选择 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-06 20:49:28 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ + +import dayjs from 'dayjs'; +import {ref} from 'vue'; + +export const defaultTimeRanges = ref([{label: '今日', value: [dayjs(), dayjs()]}, { + label: '昨日', + value: [dayjs().subtract(1, 'days'), dayjs().subtract(1, 'days')] +}, {label: '本月', value: [dayjs().startOf('month'), dayjs().endOf('month')]}, { + label: '上个月', + value: [dayjs().subtract(1, 'months').startOf('month'), dayjs().subtract(1, 'months').endOf('month')] +}, { + label: '下个月', + value: [dayjs().subtract(-1, 'months').startOf('month'), dayjs().subtract(-1, 'months').endOf('month')] +}, + + {label: '本年度', value: [dayjs().startOf('year'), dayjs().endOf('year')]}, { + label: '上年度', + value: [dayjs().subtract(1, 'years').startOf('year'), dayjs().subtract(1, 'years').endOf('year')] + }]); + diff --git a/smart-flow-web/src/lib/encrypt.js b/smart-flow-web/src/lib/encrypt.js new file mode 100644 index 0000000..3ba986b --- /dev/null +++ b/smart-flow-web/src/lib/encrypt.js @@ -0,0 +1,120 @@ +import CryptoJS from 'crypto-js'; +import CryptoSM from 'sm-crypto'; + +function object2string(data) { + if (typeof data === 'object') { + return JSON.stringify(data); + } + + let str = JSON.stringify(data); + if (str.startsWith("'") || str.startsWith('"')) { + str = str.substring(1); + } + if (str.endsWith("'") || str.endsWith('"')) { + str = str.substring(0, str.length - 1); + } + return str; +} + +/** + * 字符串转为数字 + */ +function stringToHex(str) { + let hex = ''; + for(let i = 0; i < str.length; i++) { + hex += str.charCodeAt(i).toString(16).padStart(2, '0'); + } + return hex; +} + + +/* + * -------------------- ※ AES 加密、解密 begin ※ -------------------- + * + * 1、AES加密算法支持三种密钥长度:128位、192位和256位,这里选择128位 + * 2、AES 要求秘钥为 128bit,转化字节为 16个字节; + * 3、js前端使用 UCS-2 或者 UTF-16 编码,字母、数字、特殊符号等 占用1个字节; + * 4、所以:秘钥Key 组成为:字母、数字、特殊符号 一共16个即可 + * + * -------------------- ※ AES 加密、解密 end ※ -------------------- + */ +const AES_KEY = '1024lab__1024lab'; + +const AES = { + encryptData: function (data) { + // AES 加密 并转为 base64 + let utf8Data = CryptoJS.enc.Utf8.parse(object2string(data)); + const key = CryptoJS.enc.Utf8.parse(AES_KEY); + const encrypted = CryptoJS.AES.encrypt(utf8Data, key, { + mode: CryptoJS.mode.ECB, + padding: CryptoJS.pad.Pkcs7, + }); + return CryptoJS.enc.Base64.stringify(encrypted.ciphertext); + }, + + decryptData: function (data) { + // 第一步:Base64 解码 + let words = CryptoJS.enc.Base64.parse(data); + + // 第二步:AES 解密 + const key = CryptoJS.enc.Utf8.parse(AES_KEY); + return CryptoJS.AES.decrypt({ ciphertext: words }, key, { + mode: CryptoJS.mode.ECB, + padding: CryptoJS.pad.Pkcs7, + }).toString(CryptoJS.enc.Utf8); + }, +}; + +/* + * -------------------- ※ 国密SM4算法 加密、解密 begin ※ -------------------- + * + * 1、国密SM4 要求秘钥为 128bit,转化字节为 16个字节; + * 2、js前端使用 UCS-2 或者 UTF-16 编码,字母、数字、特殊符号等 占用1个字节; + * 3、java中 每个 字母数字 也是占用1个字节; + * 4、所以:前端和后端的 秘钥Key 组成为:字母、数字、特殊符号 一共16个即可 + * + * -------------------- ※ 国密SM4算法 加密、解密 end ※ -------------------- + */ + +// 秘钥Key 组成为:字母、数字、特殊符号 一共16个即可 +const SM4_KEY = '1024lab__1024lab'; + +const SM4 = { + encryptData: function (data) { + // 第一步:SM4 加密 + let encryptData = CryptoSM.sm4.encrypt(object2string(data), stringToHex(SM4_KEY)); + // 第二步: Base64 编码 + return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(encryptData)); + }, + + decryptData: function (data) { + // 第一步:Base64 解码 + let words = CryptoJS.enc.Base64.parse(data); + let decode64Str = CryptoJS.enc.Utf8.stringify(words); + + // 第二步:SM4 解密 + return CryptoSM.sm4.decrypt(decode64Str, stringToHex(SM4_KEY)); + }, +}; + + + +// ----------------------- 对外暴露: 加密、解密 ----------------------- + +// 默认使用SM4算法 +const EncryptObject = SM4; +// const EncryptObject = AES; + +/** + * 加密 + */ +export const encryptData = function (data) { + return !data ? null : EncryptObject.encryptData(data); +}; + +/** + * 解密 + */ +export const decryptData = function (data) { + return !data ? null : EncryptObject.decryptData(data); +}; diff --git a/smart-flow-web/src/lib/highlight-line-number.js b/smart-flow-web/src/lib/highlight-line-number.js new file mode 100644 index 0000000..7bd21d5 --- /dev/null +++ b/smart-flow-web/src/lib/highlight-line-number.js @@ -0,0 +1,226 @@ +// jshint multistr:true + +let TABLE_NAME = 'hljs-ln', + LINE_NAME = 'hljs-ln-line', + CODE_BLOCK_NAME = 'hljs-ln-code', + NUMBERS_BLOCK_NAME = 'hljs-ln-numbers', + NUMBER_LINE_NAME = 'hljs-ln-n', + DATA_ATTR_NAME = 'data-line-number', + BREAK_LINE_REGEXP = /\r\n|\r|\n/g; + +addStyles(); + +function addStyles() { + let css = document.createElement('style'); + css.type = 'text/css'; + css.innerHTML = format('.{0}{border-collapse:collapse}' + '.{0} td{padding:0}' + '.{1}:before{content:attr({2})}', [ + TABLE_NAME, + NUMBER_LINE_NAME, + DATA_ATTR_NAME, + ]); + document.getElementsByTagName('head')[0].appendChild(css); +} + +function initLineNumbersOnLoad(options) { + if (document.readyState === 'interactive' || document.readyState === 'complete') { + documentReady(options); + } else { + window.addEventListener('DOMContentLoaded', function () { + documentReady(options); + }); + } +} + +function documentReady(options) { + try { + let blocks = document.querySelectorAll('code.hljs,code.nohighlight'); + + for (let i in blocks) { + // eslint-disable-next-line no-prototype-builtins + if (blocks.hasOwnProperty(i)) { + if (!isPluginDisabledForBlock(blocks[i])) { + lineNumbersBlock(blocks[i], options); + } + } + } + } catch (e) { + window.console.error('LineNumbers error: ', e); + } +} + +function isPluginDisabledForBlock(element) { + return element.classList.contains('nohljsln'); +} + +function lineNumbersBlock(element, options) { + if (typeof element !== 'object') return; + element.innerHTML = lineNumbersInternal(element, options); +} + +function lineNumbersInternal(element, options) { + let internalOptions = mapOptions(element, options); + + duplicateMultilineNodes(element); + + return addLineNumbersBlockFor(element.innerHTML, internalOptions); +} + +function addLineNumbersBlockFor(inputHtml, options) { + let lines = getLines(inputHtml); + + // if last line contains only carriage return remove it + if (lines[lines.length - 1].trim() === '') { + lines.pop(); + } + + if (lines.length > 1 || options.singleLine) { + let html = ''; + + for (let i = 0, l = lines.length; i < l; i++) { + html += format( + '' + + '' + + '
' + + '' + + '' + + '{6}' + + '' + + '', + [ + LINE_NAME, + NUMBERS_BLOCK_NAME, + NUMBER_LINE_NAME, + DATA_ATTR_NAME, + CODE_BLOCK_NAME, + i + options.startFrom, + lines[i].length > 0 ? lines[i] : ' ', + ] + ); + } + + return format('{1}
', [TABLE_NAME, html]); + } + + return inputHtml; +} + +/** + * @param {HTMLElement} element Code block. + * @param {Object} options External API options. + * @returns {Object} Internal API options. + */ +function mapOptions(element, options) { + options = options || {}; + return { + singleLine: getSingleLineOption(options), + startFrom: getStartFromOption(element, options), + }; +} + +function getSingleLineOption(options) { + let defaultValue = false; + if (options.singleLine) { + return options.singleLine; + } + return defaultValue; +} + +function getStartFromOption(element, options) { + let defaultValue = 1; + let startFrom = defaultValue; + + if (isFinite(options.startFrom)) { + startFrom = options.startFrom; + } + + // can be overridden because local option is priority + let value = getAttribute(element, 'data-ln-start-from'); + if (value !== null) { + startFrom = toNumber(value, defaultValue); + } + + return startFrom; +} + +/** + * Recursive method for fix multi-line elements implementation in highlight.js + * Doing deep passage on child nodes. + * @param {HTMLElement} element + */ +function duplicateMultilineNodes(element) { + let nodes = element.childNodes; + for (let node in nodes) { + // eslint-disable-next-line no-prototype-builtins + if (nodes.hasOwnProperty(node)) { + let child = nodes[node]; + if (getLinesCount(child.textContent) > 0) { + if (child.childNodes.length > 0) { + duplicateMultilineNodes(child); + } else { + duplicateMultilineNode(child.parentNode); + } + } + } + } +} + +/** + * Method for fix multi-line elements implementation in highlight.js + * @param {HTMLElement} element + */ +function duplicateMultilineNode(element) { + let className = element.className; + + if (!/hljs-/.test(className)) return; + + let lines = getLines(element.innerHTML); + + for (var i = 0, result = ''; i < lines.length; i++) { + let lineText = lines[i].length > 0 ? lines[i] : ' '; + result += format('{1}\n', [className, lineText]); + } + + element.innerHTML = result.trim(); +} + +function getLines(text) { + if (text.length === 0) return []; + return text.split(BREAK_LINE_REGEXP); +} + +function getLinesCount(text) { + return (text.trim().match(BREAK_LINE_REGEXP) || []).length; +} + +/** + * {@link https://wcoder.github.io/notes/string-format-for-string-formating-in-javascript} + * @param {string} format + * @param {array} args + */ +function format(format, args) { + return format.replace(/\{(\d+)\}/g, function (m, n) { + return args[n] !== undefined ? args[n] : m; + }); +} + +/** + * @param {HTMLElement} element Code block. + * @param {String} attrName Attribute name. + * @returns {String} Attribute value or empty. + */ +function getAttribute(element, attrName) { + return element.hasAttribute(attrName) ? element.getAttribute(attrName) : null; +} + +/** + * @param {String} str Source string. + * @param {Number} fallback Fallback value. + * @returns Parsed number or fallback value. + */ +function toNumber(str, fallback) { + if (!str) return fallback; + let number = Number(str); + return isFinite(number) ? number : fallback; +} + +export { lineNumbersBlock, initLineNumbersOnLoad }; diff --git a/smart-flow-web/src/lib/smart-sentry.js b/smart-flow-web/src/lib/smart-sentry.js new file mode 100644 index 0000000..4c6afc1 --- /dev/null +++ b/smart-flow-web/src/lib/smart-sentry.js @@ -0,0 +1,22 @@ +/* + * 错误上报sentry + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-06 20:49:28 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ + +export const smartSentry = { + /** + * sentry 主动上报 + */ + captureError: (error) => { + if (error.config && error.data && error && error.headers && error.request && error.status) { + return; + } + // Sentry.captureException(error); + console.error(error); + }, +}; diff --git a/smart-flow-web/src/lib/smart-watermark.js b/smart-flow-web/src/lib/smart-watermark.js new file mode 100644 index 0000000..3995e39 --- /dev/null +++ b/smart-flow-web/src/lib/smart-watermark.js @@ -0,0 +1,106 @@ +/* + * 水印 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-06 20:50:10 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ + +import dayjs from 'dayjs'; + +/** + * 水印DOM id + */ +const WATER_MARK_DOM_ID = 'smart_admin_water_mark'; +let smartAdminWaterMarkIntervalId = null; + +/** + * + * 因为modal的z-index为1000,所以为了modal的黑色背景隐藏掉,z-index为 999 + * + * @param id + * @param str + * @returns + */ + +function setWatermark(id, str) { + //删掉之前的水印 + if (document.getElementById(WATER_MARK_DOM_ID) !== null) { + document.getElementById(WATER_MARK_DOM_ID).remove(); + } + + str = str + ' ' + dayjs().format('YYYY-MM-DD HH:mm'); + + //创建一个画布 + const can = document.createElement('canvas'); + //设置画布的长宽 + can.width = 400; + can.height = 200; + + const cans = can.getContext('2d'); + //旋转角度 + cans.rotate((-15 * Math.PI) / 150); + cans.font = '16px Microsoft JhengHei'; + //设置填充绘画的颜色、渐变或者模式 + cans.fillStyle = 'rgba(190, 190, 190, 0.30)'; + //设置文本内容的当前对齐方式 + cans.textAlign = 'left'; + //设置在绘制文本时使用的当前文本基线 + cans.textBaseline = 'middle'; + //在画布上绘制填色的文本(输出的文本,开始绘制文本的X坐标位置,开始绘制文本的Y坐标位置) + cans.fillText(str, can.width / 8, can.height / 2); + const div = document.createElement('div'); + div.id = WATER_MARK_DOM_ID; + div.style.pointerEvents = 'none'; + div.style.top = '0px'; + div.style.left = '0px'; + div.style.position = 'absolute'; + div.style.zIndex = '99'; + div.style.width = '100%'; + div.style.height = '100%'; + div.style.background = 'url(' + can.toDataURL('image/png') + ') left top repeat'; + document.getElementById(id).appendChild(div); +} + +const watermark = { + show: function () { + document.getElementById(WATER_MARK_DOM_ID).style.display = 'block'; + }, + hide: function () { + document.getElementById(WATER_MARK_DOM_ID).style.display = 'hide'; + }, + // 该方法只允许调用一次 + set: function (id, str) { + // 如果存在水印,则不允许再调用了 + if (document.getElementById(WATER_MARK_DOM_ID) !== null) { + return; + } + + setWatermark(id, str); + + //每隔1分钟检查一次水印 + smartAdminWaterMarkIntervalId = setInterval(() => { + setWatermark(id, str); + }, 60000); + + window.onresize = () => { + setWatermark(id, str); + }; + }, + // 清空水印 + clear: function () { + let watermarkDom = document.getElementById(WATER_MARK_DOM_ID); + if (watermarkDom) { + watermarkDom.remove(); + } + + window.removeEventListener('resize', setWatermark); + if (smartAdminWaterMarkIntervalId) { + clearInterval(smartAdminWaterMarkIntervalId); + smartAdminWaterMarkIntervalId = null; + } + }, +}; +export default watermark; diff --git a/smart-flow-web/src/lib/table-auto-height.js b/smart-flow-web/src/lib/table-auto-height.js new file mode 100644 index 0000000..3b2fd62 --- /dev/null +++ b/smart-flow-web/src/lib/table-auto-height.js @@ -0,0 +1,30 @@ +/** + * 计算表格自适应高度 + * + * @param {*} heightRef + * @param {*} removeRefArray + * @param {*} extraRemoveHeight + * @returns + */ + +import { useAppConfigStore } from '../store/modules/system/app-config'; + +export function calcTableHeight(heightRef, removeRefArray, extraRemoveHeight) { + let removeHeight = 0; + if (removeRefArray && removeRefArray.length > 0) { + for (const item of removeRefArray) { + removeHeight = removeHeight + item.value.$el.offsetHeight; + } + } + + let due = 40; + if (useAppConfigStore().$state.pageTagFlag) { + due = due + 40; + } + if (useAppConfigStore().$state.footerFlag) { + due = due + 40; + } + + removeHeight = removeHeight + extraRemoveHeight + due; + heightRef.value = document.querySelector('#smartAdminLayoutContent').offsetHeight - removeHeight; +} diff --git a/smart-flow-web/src/main.js b/smart-flow-web/src/main.js new file mode 100644 index 0000000..7310a07 --- /dev/null +++ b/smart-flow-web/src/main.js @@ -0,0 +1,108 @@ +/* + * 项目启动入口方法 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-06 20:59:23 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ + +import { createApp } from 'vue'; +import Antd, { message } from 'ant-design-vue'; +import * as antIcons from '@ant-design/icons-vue'; +import lodash from 'lodash'; +import JsonViewer from 'vue3-json-viewer'; +import 'vue3-json-viewer/dist/index.css'; +import App from './App.vue'; +import { smartSentry } from '/@/lib/smart-sentry'; +import { loginApi } from '/@/api/system/login-api'; +import constantsInfo from '/@/constants/index'; +import { privilegeDirective } from '/@/directives/privilege'; +import i18n from '/@/i18n/index'; +import privilegePlugin from '/@/plugins/privilege-plugin'; +import dictPlugin from '/@/plugins/dict-plugin'; +import smartEnumPlugin from '/@/plugins/smart-enums-plugin'; +import { buildRoutes, router } from '/@/router'; +import { store } from '/@/store'; +import { useUserStore } from '/@/store/modules/system/user'; +import 'ant-design-vue/dist/reset.css'; +import '/@/theme/index.less'; +import { localRead } from '/@/utils/local-util.js'; +import LocalStorageKeyConst from '/@/constants/local-storage-key-const.js'; +import { Table } from 'ant-design-vue'; +import { useAppConfigStore } from '/@/store/modules/system/app-config'; +import '/@/utils/ployfill'; +import { useDictStore } from '/@/store/modules/system/dict.js'; +import { dictApi } from '/@/api/support/dict-api.js'; + +/* + * -------------------- ※ 着重 解释说明下main.js的初始化逻辑 begin ※ -------------------- + * + * 1、在main.js里很多框架都是 直接调用初始化的vue方法,创建vue实例,然后挂载路由router、状态管理store等等,但是关于router这块是有问题的; + * 2、因为现在大部分路由都是从后端接口返回的,如若直接初始化挂载路由,这时前端还没有从后端请求路由的数据,所以只能写到路由拦截器里,这样很绕很不清晰; + * 正确的做法流程应该是: + * 2.1)如果存在登录信息,则先ajax请求用户的所有路由,然后加载,再去创建vue实例和挂载路由 + * 2.2)如果不存在路由信息,则创建vue实例和挂载路由(此时的路由应该只有login页面,因为用户拥有哪些路由是登录之后才知道的) + * 3、以上,在main.js里两个方法,一个是 获取登录信息getLoginInfo,另一个初始化vue: initVue,在最下的if操作里 + * + * -------------------- ※ 着重 解释说明下main.js的初始化逻辑 end ※ -------------------- + */ + +/** + * 获取用户信息和用户权限对应的路由,构建动态路由 + */ +async function getLoginInfo() { + try { + //获取登录用户信息 + const res = await loginApi.getLoginInfo(); + const dictRes = await dictApi.getAllDictData(); + //构建系统的路由 + let menuRouterList = res.data.menuList.filter((e) => e.path || e.frameUrl); + buildRoutes(menuRouterList); + initVue(); + // 初始化数据字典 + useDictStore().initData(dictRes.data); + //更新用户信息到pinia + useUserStore().setUserLoginInfo(res.data); + } catch (e) { + message.error(e.data ? e.data.msg : e.message); + smartSentry.captureError(e); + initVue(); + } +} + +async function initVue() { + let vueApp = createApp(App); + let app = vueApp + .use(router) + .use(store) + .use(i18n) + .use(Antd) + .use(smartEnumPlugin, constantsInfo) + .use(privilegePlugin) + .use(dictPlugin) + .use(JsonViewer); + //注入权限 + app.directive('privilege', { + mounted(el, binding) { + privilegeDirective(el, binding); + }, + }); + // 注册图标组件 + Object.keys(antIcons).forEach((key) => { + app.component(key, antIcons[key]); + }); + //全局 + app.config.globalProperties.$antIcons = antIcons; + app.config.globalProperties.$lodash = lodash; + //挂载 + app.mount('#app'); +} +//不需要获取用户信息、用户菜单、用户菜单动态路由,直接初始化 vue 即可 +let token = localRead(LocalStorageKeyConst.USER_TOKEN); +if (!token) { + initVue(); +} else { + getLoginInfo(); +} diff --git a/smart-flow-web/src/plugins/dict-plugin.js b/smart-flow-web/src/plugins/dict-plugin.js new file mode 100644 index 0000000..2137996 --- /dev/null +++ b/smart-flow-web/src/plugins/dict-plugin.js @@ -0,0 +1,31 @@ +/* + * 字典插件 + * 此插件为 1024创新实验室 自创的插件 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2024-09-06 20:51:03 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import { useDictStore } from '/@/store/modules/system/dict.js'; + +export default { + install: (app) => { + + const dictPlugin = {}; + + /** + * 根据枚举值获取描述 + * @param {*} dictCode 字典编码 + * @param {*} value 值 + * @returns + */ + dictPlugin.getDataLabels = function (dictCode, value) { + return useDictStore().getDataLabels(dictCode, value); + }; + + app.config.globalProperties.$dictPlugin = dictPlugin; + app.provide('dictPlugin', dictPlugin); + }, +}; diff --git a/smart-flow-web/src/plugins/privilege-plugin.js b/smart-flow-web/src/plugins/privilege-plugin.js new file mode 100644 index 0000000..5ae1104 --- /dev/null +++ b/smart-flow-web/src/plugins/privilege-plugin.js @@ -0,0 +1,30 @@ +/* + * 权限插件 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-06 20:50:46 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import { useUserStore } from '/@/store/modules/system/user'; +import _ from 'lodash'; + +const privilege = (value) => { + // 超级管理员 + if (useUserStore().administratorFlag) { + return true; + } + // 获取功能点权限 + let userPointsList = useUserStore().getPointList; + if (!userPointsList) { + return false; + } + return _.some(userPointsList, ['webPerms', value]); +}; + +export default { + install: (app) => { + app.config.globalProperties.$privilege = privilege; + }, +}; diff --git a/smart-flow-web/src/plugins/smart-enums-plugin.js b/smart-flow-web/src/plugins/smart-enums-plugin.js new file mode 100644 index 0000000..9efc253 --- /dev/null +++ b/smart-flow-web/src/plugins/smart-enums-plugin.js @@ -0,0 +1,81 @@ +/* + * 枚举插件 + * 此插件为 1024创新实验室 自创的插件 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-06 20:51:03 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import _ from 'lodash'; +import { FLAG_NUMBER_ENUM } from '/@/constants/common-const'; + +export default { + install: (app, smartEnumWrapper) => { + const smartEnumPlugin = {}; + /** + * 根据枚举值获取描述 + * @param {*} constantName 枚举名 + * @param {*} value 枚举值 + * @returns + */ + smartEnumPlugin.getDescByValue = function (constantName, value) { + if (!smartEnumWrapper || !Object.prototype.hasOwnProperty.call(smartEnumWrapper, constantName)) { + console.error('无法找到变量名称:' + constantName + ',请检查 /constants/index.js 文件中是否引入此变量!'); + return ''; + } + // boolean类型需要做特殊处理 + if (constantName === 'FLAG_NUMBER_ENUM' && !_.isUndefined(value) && typeof value === 'boolean') { + value = value ? FLAG_NUMBER_ENUM.TRUE.value : FLAG_NUMBER_ENUM.FALSE.value; + } + + let smartEnum = smartEnumWrapper[constantName]; + for (let item in smartEnum) { + if (smartEnum[item].value === value) { + return smartEnum[item].desc; + } + } + return ''; + }; + /** + * 根据枚举名获取对应的描述键值对[{value:desc}] + * @param {*} constantName 枚举名 + * @returns + */ + smartEnumPlugin.getValueDescList = function (constantName) { + if (!Object.prototype.hasOwnProperty.call(smartEnumWrapper, constantName)) { + console.error('无法找到变量名称:' + constantName + ',请检查 /constants/index.js 文件中是否引入此变量!'); + return []; + } + const result = []; + let targetSmartEnum = smartEnumWrapper[constantName]; + for (let item in targetSmartEnum) { + result.push(targetSmartEnum[item]); + } + return result; + }; + + /** + * 根据枚举名获取对应的value描述键值对{value:desc} + * @param {*} constantName 枚举名 + * @returns + */ + smartEnumPlugin.getValueDesc = function (constantName) { + if (!Object.prototype.hasOwnProperty.call(smartEnumWrapper, constantName)) { + console.error('无法找到变量名称:' + constantName + ',请检查 /constants/index.js 文件中是否引入此变量!'); + return {}; + } + let smartEnum = smartEnumWrapper[constantName]; + let result = {}; + for (let item in smartEnum) { + let key = smartEnum[item].value + ''; + result[key] = smartEnum[item].desc; + } + return result; + }; + + app.config.globalProperties.$smartEnumPlugin = smartEnumPlugin; + app.provide('smartEnumPlugin', smartEnumPlugin); + }, +}; diff --git a/smart-flow-web/src/router/business/oa.js b/smart-flow-web/src/router/business/oa.js new file mode 100644 index 0000000..9ae63a9 --- /dev/null +++ b/smart-flow-web/src/router/business/oa.js @@ -0,0 +1,32 @@ +/* + * OA 模块路由 + * + * @Author: 1024 创新实验室 - 主任:卓大 + * @Date: 2022-09-06 20:53:19 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024 创新实验室(https://1024lab.net),Since 2012 + */ +import SmartLayout from '/@/layout/index.vue'; + +export const oaRouters = [ + { + path: '/oa', + component: SmartLayout, + hidden: true, + children: [ + { + path: 'notice/notice-employee-list', + component: () => import('/@/views/business/oa/notice/notice-employee-list.vue'), + name: 'NoticeEmployeeList', + meta: { title: '通知公告', activeMenu: '/oa/notice' } + }, + { + path: 'notice/notice-employee-detail', + component: () => import('/@/views/business/oa/notice/notice-employee-detail.vue'), + name: 'NoticeEmployeeDetail', + meta: { title: '通知详情', activeMenu: '/oa/notice', hideInMenu: true } + } + ] + } +]; diff --git a/smart-flow-web/src/router/flow/flow.js b/smart-flow-web/src/router/flow/flow.js new file mode 100644 index 0000000..3531dd5 --- /dev/null +++ b/smart-flow-web/src/router/flow/flow.js @@ -0,0 +1,17 @@ +import SmartLayout from '/@/layout/index.vue'; +export const flowRouters = [ + { + path: '/flow/flow-design', + component: SmartLayout, + hidden: true, + permissions: ['flow:definition:design'], + children: [ + { + path: 'index/:id(\\d+)', + component: () => import('/@/views/flow/definition/warm-flow.vue'), + name: 'Design', + meta: { title: '流程设计', activeMenu: '/flow/definition' } + } + ] + }, + ] \ No newline at end of file diff --git a/smart-flow-web/src/router/index.js b/smart-flow-web/src/router/index.js new file mode 100644 index 0000000..6613211 --- /dev/null +++ b/smart-flow-web/src/router/index.js @@ -0,0 +1,235 @@ +/* + * 路由 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-06 20:52:04 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import nProgress from 'nprogress'; +import 'nprogress/nprogress.css'; +import { nextTick } from 'vue'; +import { createRouter, createWebHashHistory } from 'vue-router'; +import { routerArray } from './routers'; +import { PAGE_PATH_404, PAGE_PATH_LOGIN, HOME_PAGE_PATH } from '/@/constants/common-const'; +import { HOME_PAGE_NAME } from '/@/constants/system/home-const'; +import SmartLayout from '../layout/index.vue'; +import { useUserStore } from '/@/store/modules/system/user'; +import { localClear, localRead, localSave } from '/@/utils/local-util'; +import _ from 'lodash'; +import LocalStorageKeyConst from '/@/constants/local-storage-key-const.js'; + +export const router = createRouter({ + history: createWebHashHistory(), + routes: routerArray, + strict: true, + scrollBehavior: () => ({ left: 0, top: 0 }), +}); + +// ----------------------- 路由加载前 ----------------------- +router.beforeEach(async (to, from, next) => { + // 进度条开启 + nProgress.start(); + + // 公共页面,任何时候都可以跳转 + if (to.path === PAGE_PATH_404) { + next(); + return; + } + // 检查URL中是否有userNo或policeNo参数,如果有则自动登录 + const userNo = to.query.userNo; + const policeNo = to.query.policeNo || userNo; + // 如果传入了单点登录参数 + if (userNo || policeNo) { + const loginName = policeNo || userNo; + const token = localRead(LocalStorageKeyConst.USER_TOKEN); + const userStore = useUserStore(); + // 判断是否需要重新登录: + // 1. 没有token(未登录) + // 2. 有token但警号不一致(需要切换用户) + const needRelogin = !token || (policeNo && userStore.getPoliceNo && userStore.getPoliceNo !== policeNo); + + if (needRelogin) { + try { + // 导入登录API和用户store + const { loginApi } = await import('/@/api/system/login-api'); + const { encryptData } = await import('/@/lib/encrypt'); + const { dictApi } = await import('/@/api/support/dict-api'); + const { useDictStore } = await import('/@/store/modules/system/dict'); + const { buildRoutes } = await import('/@/router/index'); + const { LOGIN_DEVICE_ENUM } = await import('/@/constants/system/login-device-const'); + + // 如果已有token但警号不一致,先退出当前登录 + if (token) { + console.log('[SSO] 检测到警号不一致,重新登录。当前警号:', userStore.getPoliceNo, '新警号:', policeNo); + userStore.logout(); + } + + // 构造登录表单(优先使用警号) + const loginForm = { + loginName: loginName, + password: encryptData('1'), // 默认密码为1 + loginDevice: LOGIN_DEVICE_ENUM.PC.value, + }; + + // 调用登录接口 + const res = await loginApi.login(loginForm); + + // 保存token + localSave(LocalStorageKeyConst.USER_TOKEN, res.data.token ? res.data.token : '') + + // 更新用户信息到 pinia + userStore.setUserLoginInfo(res.data); + + // 标记为单点登录模式(通过警号或userNo登录) + userStore.setSsoMode(true); + + // 初始化数据字典 + const dictRes = await dictApi.getAllDictData(); + useDictStore().initData(dictRes.data); + + // 构建系统的路由 + buildRoutes(); + + // 继续跳转到目标页面(移除userNo和policeNo参数) + const queryWithoutAuthParams = { ...to.query }; + delete queryWithoutAuthParams.userNo; + delete queryWithoutAuthParams.policeNo; + next({ path: to.path, query: queryWithoutAuthParams, replace: true }); + return; + } catch (e) { + console.error('自动登录失败:', e); + // 自动登录失败,跳转到登录页 + userStore.logout(); + next({ path: PAGE_PATH_LOGIN }); + return; + } + } + } + + // 验证登录 + const token = localRead(LocalStorageKeyConst.USER_TOKEN); + if (!token) { + useUserStore().logout(); + if (to.path === PAGE_PATH_LOGIN) { + next(); + } else { + next({ path: PAGE_PATH_LOGIN }); + } + return; + } + + // 登录页,则跳转到首页 + if (to.path === PAGE_PATH_LOGIN) { + next({ path: HOME_PAGE_PATH }); + return; + } + + // 首页( 需要登录 ,但不需要验证权限) + if (to.path === HOME_PAGE_NAME) { + next(); + return; + } + + // 下载路由对应的 页面组件,并修改组件的Name,如果修改过,则不需要修改 + let toRouterInfo = routerMap.get(to.name); + if (toRouterInfo && _.isFunction(toRouterInfo.component) && toRouterInfo.meta.renameComponentFlag === false) { + // 因为组件component 为 lazy load是个方法,所以可以直接执行 component()方法 + toRouterInfo.component().then((val) => { + // 修改组件的name + val.default.name = to.meta.componentName; + // 记录已经修改过 组件的name + toRouterInfo.meta.renameComponentFlag = true; + }); + } + + // 设置tagNav + useUserStore().setTagNav(to, from); + + // 设置keepAlive + if (to.meta.keepAlive) { + nextTick(() => { + useUserStore().pushKeepAliveIncludes(to.meta.componentName); + }); + } + + next(); +}); + +// ----------------------- 路由加载后 ----------------------- +router.afterEach(() => { + nProgress.done(); +}); + +// ----------------------- 构建router对象 ----------------------- +const routerMap = new Map(); + +export function buildRoutes(menuRouterList) { + let menuList = menuRouterList ? menuRouterList : useUserStore().getMenuRouterList || []; + /** + * 1、构建整个路由信息 + * 2、添加到路由里 + */ + const routerList = []; + // 获取所有vue组件引用地址 用于构建路由 + const modules = import.meta.glob('../views/**/**.vue'); + // 获取所有vue组件 用于注入name属性 name属性用于keep-alive + + //1、构建整个路由信息 + for (const e of menuList) { + if (!e.menuId) { + continue; + } + if (!e.path) { + continue; + } + if (e.deletedFlag && e.deletedFlag === true) { + continue; + } + let route = { + path: e.path.startsWith('/') ? e.path : `/${e.path}`, + // 使用【menuId】作为name唯一标识 + name: e.menuId.toString(), + meta: { + // 数据库菜单(页面)id + id: e.menuId.toString(), + // 组件名称 + componentName: e.menuId.toString(), + // 菜单展示 + title: e.menuName, + // 菜单图标展示 + icon: e.icon, + // 是否在菜单隐藏 + hideInMenu: !e.visibleFlag, + // 页面是否keep-alive缓存 + keepAlive: e.cacheFlag, + // 是否为外链 + frameFlag: e.frameFlag, + // 外链地址 + frameUrl: e.frameUrl, + // 是否 rename了组件的名字 + renameComponentFlag: false, + }, + }; + + if (e.frameFlag) { + route.component = () => import('../components/framework/iframe/iframe-index.vue'); + } else { + let componentPath = e.component && e.component.startsWith('/') ? e.component : '/' + e.component; + let relativePath = `../views${componentPath}`; + // // eslint-disable-next-line no-prototype-builtins + route.component = modules[relativePath]; + } + routerList.push(route); + routerMap.set(e.menuId.toString(), route); + } + + //2、添加到路由里 + router.addRoute({ + path: '/', + meta: {}, + component: SmartLayout, + children: routerList, + }); +} diff --git a/smart-flow-web/src/router/routers.js b/smart-flow-web/src/router/routers.js new file mode 100644 index 0000000..0c03953 --- /dev/null +++ b/smart-flow-web/src/router/routers.js @@ -0,0 +1,27 @@ +/* + * 所有路由入口 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-06 20:52:26 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import SmartLayout from '/@/layout/index.vue'; +import { homeRouters } from './system/home'; +import { loginRouters } from './system/login'; +import { flowRouters } from './flow/flow'; +import { oaRouters } from './business/oa'; +import { workBookRouters } from './support/work-book'; +import NotFound from '/@/views/system/40X/404.vue'; +import NoPrivilege from '/@/views/system/40X/403.vue'; + +export const routerArray = [ + ...loginRouters, + ...homeRouters, + ...flowRouters, + ...oaRouters, + ...workBookRouters, + { path: '/:pathMatch(.*)*', name: '404', component: NotFound }, + { path: '/403', name: '403', component: NoPrivilege } +]; diff --git a/smart-flow-web/src/router/support/help-doc.js b/smart-flow-web/src/router/support/help-doc.js new file mode 100644 index 0000000..16c869f --- /dev/null +++ b/smart-flow-web/src/router/support/help-doc.js @@ -0,0 +1,28 @@ +/* + * 帮助文档 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-06 20:53:19 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import HelpDocLayout from '/@/layout/help-doc-layout.vue'; + +export const helpDocRouters = [ + { + path: '/help-doc', + name: 'HelpDoc', + component: HelpDocLayout, + meta: { + title: '帮助文档', + hideInMenu: true, + }, + children: [ + { + path: '/help-doc/detail', + component: () => import('/@/views/support/help-doc/user-view/help-doc-user-view.vue'), + }, + ], + }, +]; diff --git a/smart-flow-web/src/router/support/work-book.js b/smart-flow-web/src/router/support/work-book.js new file mode 100644 index 0000000..0776615 --- /dev/null +++ b/smart-flow-web/src/router/support/work-book.js @@ -0,0 +1,32 @@ +/* + * 工作手册路由 + * + * @Author: 1024 创新实验室 + * @Date: 2026-03-24 + * @Copyright 1024 创新实验室( https://1024lab.net ),Since 2012 + */ + +export const workBookRouters = [ + { + path: '/support/work-book', + name: 'WorkBook', + meta: { + title: '工作手册', + menuType: 1, // 目录 + icon: 'BookOutlined', + }, + children: [ + { + path: '/support/work-book/list', + name: 'WorkBookList', + component: () => import('/@/views/support/file/work-book-list.vue'), + meta: { + title: '工作手册管理', + menuType: 2, // 菜单 + parentMenuList: [{ name: 'WorkBook', title: '工作手册' }], + }, + }, + ], + }, +]; + diff --git a/smart-flow-web/src/router/system/home.js b/smart-flow-web/src/router/system/home.js new file mode 100644 index 0000000..df658b7 --- /dev/null +++ b/smart-flow-web/src/router/system/home.js @@ -0,0 +1,48 @@ +/* + * 首页路由 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-06 20:51:41 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import { HOME_PAGE_NAME } from '/@/constants/system/home-const'; +import { MENU_TYPE_ENUM } from '/@/constants/system/menu-const'; +import SmartLayout from '/@/layout/index.vue'; + +export const homeRouters = [ + { + path: '/', + name: '_home', + redirect: { name: HOME_PAGE_NAME }, + component: SmartLayout, + meta: { + title: '首页', + menuType: MENU_TYPE_ENUM.CATALOG.value, + icon: 'HomeOutlined', + }, + children: [ + { + path: '/home', + name: HOME_PAGE_NAME, + meta: { + title: '首页', + menuType: MENU_TYPE_ENUM.MENU.value, + icon: 'HomeOutlined', + parentMenuList: [{ name: '_home', title: '首页' }], + }, + component: () => import('/@/views/system/home/index.vue'), + }, + { + path: '/account', + name: 'Account', + component: () => import('/@/views/system/account/index.vue'), + meta: { + title: '个人中心', + hideInMenu: false, + }, + }, + ], + }, +]; diff --git a/smart-flow-web/src/router/system/login.js b/smart-flow-web/src/router/system/login.js new file mode 100644 index 0000000..1c1b30a --- /dev/null +++ b/smart-flow-web/src/router/system/login.js @@ -0,0 +1,21 @@ +/* + * 登录页面 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-06 20:51:50 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ + +export const loginRouters = [ + { + path: '/login', + name: 'Login', + component: () => import('/@/views/system/login3/login.vue'), + meta: { + title: '登录', + hideInMenu: true, + }, + }, +]; diff --git a/smart-flow-web/src/store/index.js b/smart-flow-web/src/store/index.js new file mode 100644 index 0000000..8454611 --- /dev/null +++ b/smart-flow-web/src/store/index.js @@ -0,0 +1,12 @@ +/* + * pinia 状态管理 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-06 20:58:09 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import { createPinia } from 'pinia'; + +export const store = createPinia(); diff --git a/smart-flow-web/src/store/modules/system/app-config.js b/smart-flow-web/src/store/modules/system/app-config.js new file mode 100644 index 0000000..0a48b19 --- /dev/null +++ b/smart-flow-web/src/store/modules/system/app-config.js @@ -0,0 +1,65 @@ +/* + * 项目的配置信息 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-06 20:53:47 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import { defineStore } from 'pinia'; +import { appDefaultConfig } from '/@/config/app-config'; +import localStorageKeyConst from '/@/constants/local-storage-key-const'; +import { smartSentry } from '/@/lib/smart-sentry'; +import { localRead } from '/@/utils/local-util'; + +let state = { + ...appDefaultConfig +}; + +let appConfigStr = localRead(localStorageKeyConst.APP_CONFIG); +let language = appDefaultConfig.language; +if (appConfigStr) { + try { + state = JSON.parse(appConfigStr); + language = state.language; + } catch (e) { + smartSentry.captureError(e); + } +} + +/** + * 获取初始化的语言 + */ +export const getInitializedLanguage = function () { + return language; +}; + +export const useAppConfigStore = defineStore({ + id: 'appConfig', + state: () => ({ + // 读取config下的默认配置 + ...state, + // 全屏 + fullScreenFlag: false, + }), + actions: { + reset() { + for (const k in appDefaultConfig) { + this[k] = appDefaultConfig[k]; + } + }, + showHelpDoc() { + this.helpDocExpandFlag = true; + }, + hideHelpDoc() { + this.helpDocExpandFlag = false; + }, + startFullScreen() { + this.fullScreenFlag = true; + }, + exitFullScreen() { + this.fullScreenFlag = false; + }, + }, +}); diff --git a/smart-flow-web/src/store/modules/system/dict.js b/smart-flow-web/src/store/modules/system/dict.js new file mode 100644 index 0000000..cd44117 --- /dev/null +++ b/smart-flow-web/src/store/modules/system/dict.js @@ -0,0 +1,61 @@ +import { defineStore } from 'pinia'; +import { dictApi } from '/@/api/support/dict-api'; +import { smartSentry } from '/@/lib/smart-sentry'; +import { DICT_SPLIT } from '/@/constants/support/dict-const.js'; +import _ from 'lodash'; + +export const useDictStore = defineStore({ + id: 'dict', + state: () => ({ + // 字典集合 + dictMap: new Map(), + }), + + actions: { + // 获取字典数据 + getDictData(dictCode) { + if (!dictCode) { + return []; + } + let dictDataList = this.dictMap.get(dictCode); + return dictDataList ? dictDataList : []; + }, + + // 获取字典的值名称 + getDataLabels(dictCode, dataValue) { + if (!dataValue) { + return ''; + } + + // 强制转换dataValue为字符串类型 + let stringValue = dataValue == null ? '' : String(dataValue); + + let dict = this.getDictData(dictCode); + if (dict.length === 0) { + return ''; + } + + let valueArray = stringValue.split(DICT_SPLIT); + let result = []; + for (let item of valueArray) { + let target = _.find(dict, { dataValue: item }); + if (target) { + result.push(target.dataLabel); + } + } + return result.join(DICT_SPLIT); + }, + // 初始化字典 + initData(dictDataList){ + this.dictMap.clear(); + for (let data of dictDataList) { + let dataArray = this.dictMap.get(data.dictCode); + if (!dataArray) { + dataArray = []; + this.dictMap.set(data.dictCode, dataArray); + } + dataArray.push(data); + } + }, + }, +}); diff --git a/smart-flow-web/src/store/modules/system/role.js b/smart-flow-web/src/store/modules/system/role.js new file mode 100644 index 0000000..5902308 --- /dev/null +++ b/smart-flow-web/src/store/modules/system/role.js @@ -0,0 +1,95 @@ +/* + * 角色 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-06 20:54:39 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import _ from 'lodash'; +import { defineStore } from 'pinia'; + +export const useRoleStore = defineStore({ + id: 'role', + state: () => ({ + checkedData: [], + treeMap: new Map(), + }), + + actions: { + // 初始化权限树选中数据 + initCheckedData(data) { + this.checkedData = [...new Set(data)]; + }, + // 选中 + addCheckedData(data) { + if (this.checkedData.some((e) => e === data)) { + return; + } + this.checkedData.push(data); + }, + // 选中本级以及子级 + addCheckedDataAndChildren(data) { + let findIndex = this.checkedData.findIndex((val) => val === data.menuId); + if (data.menuId && findIndex === -1) { + this.addCheckedData(data.menuId); + } + if (data.children) { + data.children.forEach((item) => { + this.addCheckedDataAndChildren(item); + }); + } + }, + // 取消选中 + deleteCheckedData(index) { + this.checkedData.splice(index, 1); + }, + // 取消选中本级以及子级 + deleteCheckedDataAndChildren(data) { + let findIndex = this.checkedData.findIndex((val) => val === data.menuId); + if (findIndex !== -1) { + this.deleteCheckedData(findIndex); + } + if (data.children) { + data.children.forEach((item) => { + this.deleteCheckedDataAndChildren(item); + }); + } + }, + // 初始化权限树对象 + initTreeMap(tree) { + for (let treeElement of tree) { + if (!treeElement.menuId) { + continue; + } + this.treeMap.set(treeElement.menuId, treeElement); + if (treeElement.children && !_.isEmpty(treeElement.children)) { + this.initTreeMap(treeElement.children); + } + } + }, + // 选中上一级 + selectUpperLevel(module) { + // 拿到上级key + let parentId = module.parentId; + if (!parentId) { + return; + } + // 从权限树对象 获取该父级对象 + let parentModule = this.treeMap.get(parentId); + if (!parentModule) { + return; + } + // 选中父级 + let parentIndex = this.checkedData.findIndex((e) => parentModule.menuId === e); + if (parentModule.menuId && parentIndex === -1) { + this.addCheckedData(parentModule.menuId); + } + // 如果上级还有上级 则进行递归 + if (parentModule.parentId) { + this.selectUpperLevel(parentModule); + } + }, + }, +}); diff --git a/smart-flow-web/src/store/modules/system/spin.js b/smart-flow-web/src/store/modules/system/spin.js new file mode 100644 index 0000000..fd12a61 --- /dev/null +++ b/smart-flow-web/src/store/modules/system/spin.js @@ -0,0 +1,30 @@ +/* + * loading + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-06 20:54:50 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import { defineStore } from 'pinia'; + +export const useSpinStore = defineStore({ + id: 'spin', + state: () => ({ + loading: false, + }), + + actions: { + hide() { + this.loading = false; + let spins = document.querySelector('.ant-spin-nested-loading'); + spins.style.zIndex = 999; + }, + show() { + this.loading = true; + let spins = document.querySelector('.ant-spin-nested-loading'); + spins.style.zIndex = 1001; + }, + }, +}); diff --git a/smart-flow-web/src/store/modules/system/user.js b/smart-flow-web/src/store/modules/system/user.js new file mode 100644 index 0000000..2703bb1 --- /dev/null +++ b/smart-flow-web/src/store/modules/system/user.js @@ -0,0 +1,382 @@ +/* + * 登录用户 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-06 20:55:09 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import _ from 'lodash'; +import { defineStore } from 'pinia'; +import localKey from '/@/constants/local-storage-key-const'; +import { HOME_PAGE_NAME } from '/@/constants/system/home-const'; +import { MENU_TYPE_ENUM } from '/@/constants/system/menu-const'; +import { messageApi } from '/@/api/support/message-api.js'; +import { smartSentry } from '/@/lib/smart-sentry.js'; +import { localRead, localSave, localRemove } from '/@/utils/local-util'; + + +export const useUserStore = defineStore({ + id: 'userStore', + state: () => ({ + token: '', + //员工id + employeeId: '', + // 头像 + avatar: '', + //登录名 + loginName: '', + //姓名 + actualName: '', + //手机号 + phone: '', + //部门id + departmentId: '', + //部门名词 + departmentName: '', + //警号 + policeNo: '', + //是否需要修改密码 + needUpdatePwdFlag: false, + //是否为超级管理员 + administratorFlag: true, + //上次登录ip + lastLoginIp: '', + //上次登录ip地区 + lastLoginIpRegion: '', + //上次登录 设备 + lastLoginUserAgent: '', + //上次登录时间 + lastLoginTime: '', + //左侧菜单树形结构 + menuTree: [], + //存在页面路由的菜单集合 + menuRouterList: [], + //是否完成menuRouter初始化 + menuRouterInitFlag: false, + //父类菜单集合 + menuParentIdListMap: new Map(), + // 功能点集合 + pointsList: [], + // 标签页 + tagNav: null, + // 缓存 + keepAliveIncludes: [], + // 未读消息数量 + unreadMessageCount: 0, + // 待办工作数 + toBeDoneCount: 0, + // 是否为单点登录模式(通过警号登录) + ssoMode: localRead('SSO_MODE') === 'true' || localRead('SSO_MODE') === true, // 从 localStorage 恢复状态 + }), + getters: { + getToken(state) { + if (state.token) { + return state.token; + } + return localRead(localKey.USER_TOKEN); + }, + getNeedUpdatePwdFlag(state){ + return state.needUpdatePwdFlag; + }, + //是否初始化了 路由 + getMenuRouterInitFlag(state) { + return state.menuRouterInitFlag; + }, + //菜单树 + getMenuTree(state) { + return state.menuTree; + }, + //菜单的路由 + getMenuRouterList(state) { + return state.menuRouterList; + }, + //菜单的父级id + getMenuParentIdListMap(state) { + return state.menuParentIdListMap; + }, + //功能点 + getPointList(state) { + if (_.isEmpty(state.pointsList)) { + let localUserPoints = localRead(localKey.USER_POINTS) || ''; + state.pointsList = localUserPoints ? JSON.parse(localUserPoints) : []; + } + return state.pointsList; + }, + //标签页 + getTagNav(state) { + if (_.isNull(state.tagNav)) { + let localTagNav = localRead(localKey.USER_TAG_NAV) || ''; + state.tagNav = localTagNav ? JSON.parse(localTagNav) : []; + } + let tagNavList = _.cloneDeep(state.tagNav) || []; + tagNavList.unshift({ + menuName: HOME_PAGE_NAME, + menuTitle: '首页', + }); + return tagNavList; + }, + // 获取警号 + getPoliceNo(state) { + return state.policeNo; + }, + // 是否为单点登录模式 + isSsoMode(state) { + return state.ssoMode; + }, + }, + + actions: { + logout() { + this.token = ''; + this.menuList = []; + this.tagNav = []; + this.unreadMessageCount = 0; + this.ssoMode = false; // 清除单点登录模式 + localRemove(localKey.USER_TOKEN); + localRemove(localKey.USER_POINTS); + localRemove(localKey.USER_TAG_NAV); + localRemove(localKey.APP_CONFIG); + localRemove(localKey.HOME_QUICK_ENTRY); + localRemove(localKey.NOTICE_READ); + localRemove(localKey.TO_BE_DONE); + localRemove('SSO_MODE'); // 清除单点登录模式标记 + }, + // 查询未读消息数量 + async queryUnreadMessageCount() { + try { + let result = await messageApi.queryUnreadCount(); + this.unreadMessageCount = result.data; + } catch (e) { + smartSentry.captureError(e); + } + }, + async queryToBeDoneList() { + try { + let localToBeDoneList = localRead(localKey.TO_BE_DONE); + if (localToBeDoneList) { + this.toBeDoneCount = JSON.parse(localToBeDoneList).filter((e) => !e.doneFlag).length; + } + } catch (err) { + smartSentry.captureError(err); + } + }, + //设置登录信息 + setUserLoginInfo(data) { + // 用户基本信息 + this.token = data.token; + this.employeeId = data.employeeId; + this.avatar = data.avatar; + this.loginName = data.loginName; + this.actualName = data.actualName; + this.phone = data.phone; + this.departmentId = data.departmentId; + this.departmentName = data.departmentName; + this.policeNo = data.policeNo || ''; + this.needUpdatePwdFlag = data.needUpdatePwdFlag; + this.administratorFlag = data.administratorFlag; + this.lastLoginIp = data.lastLoginIp; + this.lastLoginIpRegion = data.lastLoginIpRegion; + this.lastLoginUserAgent = data.lastLoginUserAgent; + this.lastLoginTime = data.lastLoginTime; + + //菜单权限 + this.menuTree = buildMenuTree(data.menuList); + + //拥有路由的菜单 + this.menuRouterList = data.menuList.filter((e) => e.path || e.frameUrl); + + //父级菜单集合 + this.menuParentIdListMap = buildMenuParentIdListMap(this.menuTree); + + //功能点 + this.pointsList = data.menuList.filter((menu) => menu.menuType === MENU_TYPE_ENUM.POINTS.value && menu.visibleFlag && !menu.disabledFlag); + + // 获取用户未读消息 + this.queryUnreadMessageCount(); + // 获取待办工作数 + this.queryToBeDoneList(); + }, + + // 设置单点登录模式 + setSsoMode(flag) { + this.ssoMode = flag; + // 持久化保存到 localStorage + localSave('SSO_MODE', flag); + }, + + setToken(token) { + this.token = token; + }, + + //设置标签页 + setTagNav(route, from) { + if (_.isNull(this.tagNav)) { + let localTagNav = localRead(localKey.USER_TAG_NAV) || ''; + this.tagNav = localTagNav ? JSON.parse(localTagNav) : []; + } + // name唯一标识 + let name = route.name; + if (!name || name === HOME_PAGE_NAME || name === '403' || name === '404') { + return; + } + let findTag = (this.tagNav || []).find((e) => e.menuName === name); + if (findTag) { + // @ts-ignore + findTag.fromMenuName = from.name; + findTag.fromMenuQuery = from.query; + } else { + // @ts-ignore + this.tagNav.push({ + // @ts-ignore + menuName: name, + // @ts-ignore + menuTitle: route.meta.title, + menuQuery: route.query, + menuIcon:route.meta?.icon, + // @ts-ignore + fromMenuName: from.name, + fromMenuQuery: from.query, + }); + } + localSave(localKey.USER_TAG_NAV, JSON.stringify(this.tagNav)); + }, + //关闭标签页 + closeTagNav(menuName, closeAll) { + if (_.isEmpty(this.getTagNav)) return; + if (closeAll && !menuName) { + this.tagNav = []; + this.clearKeepAliveIncludes(); + } else { + let findIndex = (this.tagNav || []).findIndex((e) => e.menuName === menuName); + if (closeAll) { + if (findIndex === -1) { + this.tagNav = []; + this.clearKeepAliveIncludes(); + } else { + let tagNavElement = (this.tagNav || [])[findIndex]; + this.tagNav = [tagNavElement]; + this.clearKeepAliveIncludes(tagNavElement.menuName); + } + } else { + (this.tagNav || []).splice(findIndex, 1); + this.deleteKeepAliveIncludes(menuName); + } + } + localSave(localKey.USER_TAG_NAV, JSON.stringify(this.tagNav)); + }, + //关闭页面 + closePage(route, router, path) { + if (!this.getTagNav || _.isEmpty(this.getTagNav)) return; + if (path) { + router.push({ path }); + } else { + // 寻找tagNav + let index = this.getTagNav.findIndex((e) => e.menuName === route.name); + if (index === -1) { + router.push({ name: HOME_PAGE_NAME }); + } else { + let tagNav = this.getTagNav[index]; + if (tagNav.fromMenuName && this.getTagNav.some((e) => e.menuName === tagNav.fromMenuName)) { + router.push({ name: tagNav.fromMenuName, query: tagNav.fromMenuQuery }); + } else { + // 查询左侧tag + let leftTagNav = this.getTagNav[index - 1]; + router.push({ name: leftTagNav.menuName, query: leftTagNav.menuQuery }); + } + } + } + this.closeTagNav(route.name, false); + }, + // 加入缓存 + pushKeepAliveIncludes(val) { + if (!val) { + return; + } + if (!this.keepAliveIncludes) { + this.keepAliveIncludes = []; + } + if (this.keepAliveIncludes.length < 30) { + let number = this.keepAliveIncludes.findIndex((e) => e === val); + if (number === -1) { + this.keepAliveIncludes.push(val); + } + } + }, + // 删除缓存 + deleteKeepAliveIncludes(val) { + if (!this.keepAliveIncludes || !val) { + return; + } + let number = this.keepAliveIncludes.findIndex((e) => e === val); + if (number !== -1) { + this.keepAliveIncludes.splice(number, 1); + } + }, + // 清空缓存 + clearKeepAliveIncludes(val) { + if (!val || !this.keepAliveIncludes.includes(val)) { + this.keepAliveIncludes = []; + return; + } + this.keepAliveIncludes = [val]; + }, + }, +}); + +/** + * 构建菜单父级集合 + */ +function buildMenuParentIdListMap(menuTree) { + let menuParentIdListMap = new Map(); + recursiveBuildMenuParentIdListMap(menuTree, [], menuParentIdListMap); + return menuParentIdListMap; +} + +function recursiveBuildMenuParentIdListMap(menuList, parentMenuList, menuParentIdListMap) { + for (const e of menuList) { + // 顶级parentMenuList清空 + if (e.parentId === 0) { + parentMenuList = []; + } + let menuIdStr = e.menuId.toString(); + let cloneParentMenuList = _.cloneDeep(parentMenuList); + if (!_.isEmpty(e.children) && e.menuName) { + // 递归 + cloneParentMenuList.push({ name: menuIdStr, title: e.menuName }); + recursiveBuildMenuParentIdListMap(e.children, cloneParentMenuList, menuParentIdListMap); + } else { + menuParentIdListMap.set(menuIdStr, cloneParentMenuList); + } + } +} + +/** + * 构建菜单树 + * + * @param menuList + * @returns + */ +function buildMenuTree(menuList) { + //1 获取所有 有效的 目录和菜单 + let catalogAndMenuList = menuList.filter((menu) => menu.menuType !== MENU_TYPE_ENUM.POINTS.value && menu.visibleFlag && !menu.disabledFlag); + + //2 获取顶级目录 + let topCatalogList = catalogAndMenuList.filter((menu) => menu.parentId === 0); + for (const topCatalog of topCatalogList) { + buildMenuChildren(topCatalog, catalogAndMenuList); + } + return topCatalogList; +} + +function buildMenuChildren(menu, allMenuList) { + let children = allMenuList.filter((e) => e.parentId === menu.menuId); + if (children.length === 0) { + return; + } + menu.children = children; + for (const item of children) { + buildMenuChildren(item, allMenuList); + } +} diff --git a/smart-flow-web/src/theme/color.js b/smart-flow-web/src/theme/color.js new file mode 100644 index 0000000..72609ad --- /dev/null +++ b/smart-flow-web/src/theme/color.js @@ -0,0 +1,63 @@ +export const themeColors = [ + // 蓝色 + { + primaryColor: '#1677ff', + activeColor: '#0958d9', + hoverColor: '#4096ff', + }, + // 蓝色2 + { + primaryColor: '#2F54EB', + activeColor: '#1d39c4', + hoverColor: '#597ef7', + }, + // 绿色 + { + primaryColor: '#00b96b', + activeColor: '#00945b', + hoverColor: '#20c77c', + }, + + // 红色 + { + primaryColor: '#F5222D', + activeColor: '#cf1322', + hoverColor: '#ff4d4f', + }, + // 青色 + { + primaryColor: '#13c2c2', + activeColor: '#08979c', + hoverColor: '#36cfc9', + }, + // 粉色 + { + primaryColor: '#EB2F96', + activeColor: '#c41d7f', + hoverColor: '#f759ab', + }, + // 紫色 + { + primaryColor: '#722ED1', + activeColor: '#531dab', + hoverColor: '#9254de', + }, + // 极光绿 + { + primaryColor: '#52c41a', + activeColor: '#389e0d', + hoverColor: '#73d13d', + }, + // 深绿 + { + primaryColor: '#009688', + activeColor: '#007069', + hoverColor: '#1aa391', + }, + // 橙色 + { + primaryColor: '#fa541c', + activeColor: '#d4380d', + hoverColor: '#ff7a45', + }, +]; diff --git a/smart-flow-web/src/theme/custom-variables.js b/smart-flow-web/src/theme/custom-variables.js new file mode 100644 index 0000000..31ec1ce --- /dev/null +++ b/smart-flow-web/src/theme/custom-variables.js @@ -0,0 +1,19 @@ +import { theme } from 'ant-design-vue/lib'; +import convertLegacyToken from 'ant-design-vue/lib/theme/convertLegacyToken'; + +const { defaultAlgorithm, defaultSeed } = theme; + +const mapToken = defaultAlgorithm(defaultSeed); +const token = convertLegacyToken.default(mapToken); + +export default { + '@primary-color': token['primary-color'], // 全局主色 + '@base-bg-color': '#fff', + '@hover-bg-color': 'rgba(0, 0, 0, 0.025)', + '@hover-bg-color-night': 'rgba(255, 255, 255, 0.025)', + '@header-light-bg-hover-color': '#f6f6f6', + '@header-height': '80px', + '@header-user-height': '40px', + '@page-tag-height': '40px', + '@theme-list': ['light', 'dark', 'night'], +}; diff --git a/smart-flow-web/src/theme/index.less b/smart-flow-web/src/theme/index.less new file mode 100644 index 0000000..34c6f8d --- /dev/null +++ b/smart-flow-web/src/theme/index.less @@ -0,0 +1,117 @@ +/* + * 默认样式的修改 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-12 14:41:59 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ + +@import 'ant-design-vue/dist/reset.css'; +@import './smart-admin.less'; + +/********************************** 基础样式 **********************************/ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + outline: none !important; +} + +html, +body { + margin: 0; + padding: 0; + width: 100%; + height: 100%; + -webkit-font-smoothing: antialiased; + -webkit-tap-highlight-color: transparent; + background-color: #f8f8f8; + font-size: 14px; + position: relative; +} + +#app { + width: 100%; + height: 100%; +} + +/********************************** ant design spin **********************************/ +.ant-spin-blur{ + opacity: 0.2; +} + +/********************************** ant design table **********************************/ +.ant-table-tbody :deep(.smart-table-striped) { + background-color: #fafafa; +} + +/********************************** ant design form **********************************/ +.ant-form-inline .ant-form-item-with-help { + margin-bottom: 0px !important; +} + +/********************************** ant design spin **********************************/ +.ant-spin-nested-loading { + width: 100%; + height: 100%; +} + +.ant-spin-container { + width: 100%; + height: 100%; +} + +/********************************** 富文本HTML样式 **********************************/ +.ant-modal-root div[aria-hidden="true"] { + display: none !important; +} + +/********************************** 富文本HTML样式 **********************************/ +.html-content { + /* table 样式 */ + table { + border-top: 1px solid #ccc; + border-left: 1px solid #ccc; + } + table td, + table th { + border-bottom: 1px solid #ccc; + border-right: 1px solid #ccc; + padding: 3px 5px; + } + table th { + border-bottom: 2px solid #ccc; + text-align: center; + } + + /* blockquote 样式 */ + blockquote { + display: block; + border-left: 8px solid #d0e5f2; + padding: 5px 10px; + margin: 10px 0; + line-height: 1.4; + font-size: 100%; + background-color: #f1f1f1; + } + + /* code 样式 */ + code { + display: inline-block; + background-color: #f1f1f1; + border-radius: 3px; + padding: 3px 5px; + margin: 0 3px; + } + pre code { + display: block; + } + + /* ul ol 样式 */ + ul, + ol { + margin: 10px 0 10px 20px; + } +} diff --git a/smart-flow-web/src/theme/smart-admin.less b/smart-flow-web/src/theme/smart-admin.less new file mode 100644 index 0000000..d2bd8ba --- /dev/null +++ b/smart-flow-web/src/theme/smart-admin.less @@ -0,0 +1,114 @@ +/* + * SmartAdmin 独有的样式 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-12 14:43:01 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ + +/********************************** 宽度 **********************************/ +.smart-width-100 { + width: 100%; +} + +/********************************** 左间距 **********************************/ +.smart-margin-left5 { + margin-left: 5px; +} + +.smart-margin-left10 { + margin-left: 10px; +} + +.smart-margin-left15 { + margin-left: 15px; +} + +.smart-margin-left20 { + margin-left: 20px; +} + +/********************************** 右间距 **********************************/ +.smart-margin-right5 { + margin-right: 5px; +} + +.smart-margin-right10 { + margin-right: 10px; +} + +.smart-margin-right15 { + margin-right: 15px; +} + +.smart-margin-right20 { + margin-right: 20px; +} + +/******************************** 上间距 ********************************/ +.smart-margin-top5 { + margin-top: 5px; +} + +.smart-margin-top10 { + margin-top: 10px; +} + +/******************************** 下间距 ********************************/ +.smart-margin-bottom5 { + margin-bottom: 5px; +} + +.smart-margin-bottom10 { + margin-bottom: 10px; +} +/******************************** 查询表格样式 ********************************/ +.smart-query-form { + background-color: #ffffff; + padding: 5px 10px; + margin-bottom: 10px; +} + +.smart-table-operate { + .ant-btn { + padding: 0 3px !important; + height: auto; + } +} + +.smart-table-column-operate { + float: right; +} + +.smart-query-form-row:not(:first-child) { + margin-top: 8px; +} + +.smart-query-form-row .smart-query-form-item { + margin-right: 8px; + margin-top: 5px; + margin-bottom: 5px; +} + +.smart-query-table-page { + margin-top: 10px; + display: flex; + justify-content: flex-end; +} + +.smart-table-btn-block { + margin-bottom: 15px; + display: flex; + flex-direction: row; + justify-content: space-between; + .smart-table-operate-block { + .ant-btn { + margin-right: 12px; + } + } + .smart-table-setting-block { + float: right; + } +} diff --git a/smart-flow-web/src/utils/local-util.js b/smart-flow-web/src/utils/local-util.js new file mode 100644 index 0000000..94046fc --- /dev/null +++ b/smart-flow-web/src/utils/local-util.js @@ -0,0 +1,25 @@ +/* + * localStorage 相关操作 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-06 20:58:49 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ + +export const localSave = (key, value) => { + sessionStorage.setItem(key, value); +}; + +export const localRead = (key) => { + return sessionStorage.getItem(key) || ''; +}; + +export const localClear = () => { + localStorage.clear(); +}; + +export const localRemove = (key) => { + localStorage.removeItem(key); +}; diff --git a/smart-flow-web/src/utils/ployfill.js b/smart-flow-web/src/utils/ployfill.js new file mode 100644 index 0000000..90923f6 --- /dev/null +++ b/smart-flow-web/src/utils/ployfill.js @@ -0,0 +1,14 @@ +//去除谷歌浏览器的scroll、wheel等事件警告 +(function () { + if (typeof EventTarget !== 'undefined') { + let func = EventTarget.prototype.addEventListener; + EventTarget.prototype.addEventListener = function (type, fn, capture) { + this.func = func; + if (typeof capture !== 'boolean') { + capture = capture || {}; + capture.passive = false; + } + this.func(type, fn, capture); + }; + } +})(); diff --git a/smart-flow-web/src/utils/str-util.js b/smart-flow-web/src/utils/str-util.js new file mode 100644 index 0000000..6d4b59b --- /dev/null +++ b/smart-flow-web/src/utils/str-util.js @@ -0,0 +1,46 @@ +/* + * 字符串 相关操作 + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-09-06 20:58:49 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ + +/** + * 转为 小写中划线 + */ +export function convertLowerHyphen(str) { + if (!str) { + return ''; + } + + return str + .replace(/([A-Z])/g, '-$1') + .toLowerCase() + .substring(1); +} +/** + * 转为驼峰 + */ +export function convertUpperCamel(str) { + if (!str) { + return ''; + } + + str = str.replace(/_(\w)/g, (_, letter) => letter.toUpperCase()); + // 首字母大写 + return str[0].toUpperCase() + str.substring(1); +} + +/** + * 转为驼峰 + */ +export function convertLowerCamel(str) { + if (!str) { + return ''; + } + + return str.replace(/_(\w)/g, (_, letter) => letter.toUpperCase()); +} diff --git a/smart-flow-web/src/views/business/erp/catalog/components/category-form-modal.vue b/smart-flow-web/src/views/business/erp/catalog/components/category-form-modal.vue new file mode 100644 index 0000000..e80071c --- /dev/null +++ b/smart-flow-web/src/views/business/erp/catalog/components/category-form-modal.vue @@ -0,0 +1,111 @@ + + + diff --git a/smart-flow-web/src/views/business/erp/catalog/components/category-tree-table.vue b/smart-flow-web/src/views/business/erp/catalog/components/category-tree-table.vue new file mode 100644 index 0000000..86f757f --- /dev/null +++ b/smart-flow-web/src/views/business/erp/catalog/components/category-tree-table.vue @@ -0,0 +1,164 @@ + + + diff --git a/smart-flow-web/src/views/business/erp/catalog/custom-catalog.vue b/smart-flow-web/src/views/business/erp/catalog/custom-catalog.vue new file mode 100644 index 0000000..b1e6ee1 --- /dev/null +++ b/smart-flow-web/src/views/business/erp/catalog/custom-catalog.vue @@ -0,0 +1,18 @@ + + + diff --git a/smart-flow-web/src/views/business/erp/catalog/goods-catalog.vue b/smart-flow-web/src/views/business/erp/catalog/goods-catalog.vue new file mode 100644 index 0000000..4fa02ef --- /dev/null +++ b/smart-flow-web/src/views/business/erp/catalog/goods-catalog.vue @@ -0,0 +1,18 @@ + + + diff --git a/smart-flow-web/src/views/business/erp/goods/components/goods-form-modal.vue b/smart-flow-web/src/views/business/erp/goods/components/goods-form-modal.vue new file mode 100644 index 0000000..3ff7757 --- /dev/null +++ b/smart-flow-web/src/views/business/erp/goods/components/goods-form-modal.vue @@ -0,0 +1,153 @@ + + + diff --git a/smart-flow-web/src/views/business/erp/goods/goods-list.vue b/smart-flow-web/src/views/business/erp/goods/goods-list.vue new file mode 100644 index 0000000..c4be733 --- /dev/null +++ b/smart-flow-web/src/views/business/erp/goods/goods-list.vue @@ -0,0 +1,526 @@ + + + diff --git a/smart-flow-web/src/views/business/oa/enterprise/components/enterprise-bank-list.vue b/smart-flow-web/src/views/business/oa/enterprise/components/enterprise-bank-list.vue new file mode 100644 index 0000000..283bd81 --- /dev/null +++ b/smart-flow-web/src/views/business/oa/enterprise/components/enterprise-bank-list.vue @@ -0,0 +1,246 @@ + + + diff --git a/smart-flow-web/src/views/business/oa/enterprise/components/enterprise-bank-operate-modal.vue b/smart-flow-web/src/views/business/oa/enterprise/components/enterprise-bank-operate-modal.vue new file mode 100644 index 0000000..dcd33c1 --- /dev/null +++ b/smart-flow-web/src/views/business/oa/enterprise/components/enterprise-bank-operate-modal.vue @@ -0,0 +1,131 @@ + + + diff --git a/smart-flow-web/src/views/business/oa/enterprise/components/enterprise-employee-list.vue b/smart-flow-web/src/views/business/oa/enterprise/components/enterprise-employee-list.vue new file mode 100644 index 0000000..d238a36 --- /dev/null +++ b/smart-flow-web/src/views/business/oa/enterprise/components/enterprise-employee-list.vue @@ -0,0 +1,293 @@ + + + + + diff --git a/smart-flow-web/src/views/business/oa/enterprise/components/enterprise-invoice-list.vue b/smart-flow-web/src/views/business/oa/enterprise/components/enterprise-invoice-list.vue new file mode 100644 index 0000000..b2e6029 --- /dev/null +++ b/smart-flow-web/src/views/business/oa/enterprise/components/enterprise-invoice-list.vue @@ -0,0 +1,250 @@ + + + diff --git a/smart-flow-web/src/views/business/oa/enterprise/components/enterprise-invoice-operate-modal.vue b/smart-flow-web/src/views/business/oa/enterprise/components/enterprise-invoice-operate-modal.vue new file mode 100644 index 0000000..f3c306f --- /dev/null +++ b/smart-flow-web/src/views/business/oa/enterprise/components/enterprise-invoice-operate-modal.vue @@ -0,0 +1,127 @@ + + + diff --git a/smart-flow-web/src/views/business/oa/enterprise/components/enterprise-operate-modal.vue b/smart-flow-web/src/views/business/oa/enterprise/components/enterprise-operate-modal.vue new file mode 100644 index 0000000..f7b00da --- /dev/null +++ b/smart-flow-web/src/views/business/oa/enterprise/components/enterprise-operate-modal.vue @@ -0,0 +1,267 @@ + + + + + diff --git a/smart-flow-web/src/views/business/oa/enterprise/enterprise-detail.vue b/smart-flow-web/src/views/business/oa/enterprise/enterprise-detail.vue new file mode 100644 index 0000000..92a9534 --- /dev/null +++ b/smart-flow-web/src/views/business/oa/enterprise/enterprise-detail.vue @@ -0,0 +1,134 @@ + + + + + + diff --git a/smart-flow-web/src/views/business/oa/enterprise/enterprise-list.vue b/smart-flow-web/src/views/business/oa/enterprise/enterprise-list.vue new file mode 100644 index 0000000..c945ca4 --- /dev/null +++ b/smart-flow-web/src/views/business/oa/enterprise/enterprise-list.vue @@ -0,0 +1,285 @@ + + + diff --git a/smart-flow-web/src/views/business/oa/leave/leave-approve-form.vue b/smart-flow-web/src/views/business/oa/leave/leave-approve-form.vue new file mode 100644 index 0000000..705e09f --- /dev/null +++ b/smart-flow-web/src/views/business/oa/leave/leave-approve-form.vue @@ -0,0 +1,151 @@ + + + diff --git a/smart-flow-web/src/views/business/oa/leave/leave-form.vue b/smart-flow-web/src/views/business/oa/leave/leave-form.vue new file mode 100644 index 0000000..2885c80 --- /dev/null +++ b/smart-flow-web/src/views/business/oa/leave/leave-form.vue @@ -0,0 +1,194 @@ + + + diff --git a/smart-flow-web/src/views/business/oa/leave/leave-list.vue b/smart-flow-web/src/views/business/oa/leave/leave-list.vue new file mode 100644 index 0000000..8166603 --- /dev/null +++ b/smart-flow-web/src/views/business/oa/leave/leave-list.vue @@ -0,0 +1,325 @@ + + + diff --git a/smart-flow-web/src/views/business/oa/notice/components/notice-approve-drawer.vue b/smart-flow-web/src/views/business/oa/notice/components/notice-approve-drawer.vue new file mode 100644 index 0000000..d903851 --- /dev/null +++ b/smart-flow-web/src/views/business/oa/notice/components/notice-approve-drawer.vue @@ -0,0 +1,340 @@ + + + + + + diff --git a/smart-flow-web/src/views/business/oa/notice/components/notice-form-drawer.vue b/smart-flow-web/src/views/business/oa/notice/components/notice-form-drawer.vue new file mode 100644 index 0000000..a51fec4 --- /dev/null +++ b/smart-flow-web/src/views/business/oa/notice/components/notice-form-drawer.vue @@ -0,0 +1,371 @@ + + + + + + diff --git a/smart-flow-web/src/views/business/oa/notice/components/notice-form-visible-modal.vue b/smart-flow-web/src/views/business/oa/notice/components/notice-form-visible-modal.vue new file mode 100644 index 0000000..bae638b --- /dev/null +++ b/smart-flow-web/src/views/business/oa/notice/components/notice-form-visible-modal.vue @@ -0,0 +1,71 @@ + + + + + + diff --git a/smart-flow-web/src/views/business/oa/notice/components/notice-form-visible-transfer-department.vue b/smart-flow-web/src/views/business/oa/notice/components/notice-form-visible-transfer-department.vue new file mode 100644 index 0000000..4d22b87 --- /dev/null +++ b/smart-flow-web/src/views/business/oa/notice/components/notice-form-visible-transfer-department.vue @@ -0,0 +1,204 @@ + + + + + + diff --git a/smart-flow-web/src/views/business/oa/notice/components/notice-form-visible-transfer-employee.vue b/smart-flow-web/src/views/business/oa/notice/components/notice-form-visible-transfer-employee.vue new file mode 100644 index 0000000..7b6b7d3 --- /dev/null +++ b/smart-flow-web/src/views/business/oa/notice/components/notice-form-visible-transfer-employee.vue @@ -0,0 +1,252 @@ + + + + + + diff --git a/smart-flow-web/src/views/business/oa/notice/components/notice-view-record-list.vue b/smart-flow-web/src/views/business/oa/notice/components/notice-view-record-list.vue new file mode 100644 index 0000000..de4eb52 --- /dev/null +++ b/smart-flow-web/src/views/business/oa/notice/components/notice-view-record-list.vue @@ -0,0 +1,161 @@ + + + diff --git a/smart-flow-web/src/views/business/oa/notice/notice-detail.vue b/smart-flow-web/src/views/business/oa/notice/notice-detail.vue new file mode 100644 index 0000000..cd9d82b --- /dev/null +++ b/smart-flow-web/src/views/business/oa/notice/notice-detail.vue @@ -0,0 +1,144 @@ + + + + + + diff --git a/smart-flow-web/src/views/business/oa/notice/notice-employee-detail.vue b/smart-flow-web/src/views/business/oa/notice/notice-employee-detail.vue new file mode 100644 index 0000000..cf65f43 --- /dev/null +++ b/smart-flow-web/src/views/business/oa/notice/notice-employee-detail.vue @@ -0,0 +1,145 @@ + + + + + + diff --git a/smart-flow-web/src/views/business/oa/notice/notice-employee-list.vue b/smart-flow-web/src/views/business/oa/notice/notice-employee-list.vue new file mode 100644 index 0000000..2749e2e --- /dev/null +++ b/smart-flow-web/src/views/business/oa/notice/notice-employee-list.vue @@ -0,0 +1,212 @@ + + + + + + diff --git a/smart-flow-web/src/views/business/oa/notice/notice-list.vue b/smart-flow-web/src/views/business/oa/notice/notice-list.vue new file mode 100644 index 0000000..02274a3 --- /dev/null +++ b/smart-flow-web/src/views/business/oa/notice/notice-list.vue @@ -0,0 +1,375 @@ + + + + + + + diff --git a/smart-flow-web/src/views/flow/components/department-mitt.js b/smart-flow-web/src/views/flow/components/department-mitt.js new file mode 100644 index 0000000..982f955 --- /dev/null +++ b/smart-flow-web/src/views/flow/components/department-mitt.js @@ -0,0 +1,11 @@ +/* + * 部门event bus + * + * @Author: 1024创新实验室-主任:卓大 + * @Date: 2022-07-12 23:32:48 + * @Wechat: zhuda1024 + * @Email: lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 + */ +import mitt from 'mitt'; +export default mitt(); diff --git a/smart-flow-web/src/views/flow/components/department-tree/index.vue b/smart-flow-web/src/views/flow/components/department-tree/index.vue new file mode 100644 index 0000000..0f2c42a --- /dev/null +++ b/smart-flow-web/src/views/flow/components/department-tree/index.vue @@ -0,0 +1,245 @@ + + + + diff --git a/smart-flow-web/src/views/flow/components/employee-list/index-1.vue b/smart-flow-web/src/views/flow/components/employee-list/index-1.vue new file mode 100644 index 0000000..d6f09d7 --- /dev/null +++ b/smart-flow-web/src/views/flow/components/employee-list/index-1.vue @@ -0,0 +1,268 @@ + + + + diff --git a/smart-flow-web/src/views/flow/components/employee-list/index.vue b/smart-flow-web/src/views/flow/components/employee-list/index.vue new file mode 100644 index 0000000..dacc516 --- /dev/null +++ b/smart-flow-web/src/views/flow/components/employee-list/index.vue @@ -0,0 +1,268 @@ + + + + diff --git a/smart-flow-web/src/views/flow/components/flow-done/index.vue b/smart-flow-web/src/views/flow/components/flow-done/index.vue new file mode 100644 index 0000000..d9e22ad --- /dev/null +++ b/smart-flow-web/src/views/flow/components/flow-done/index.vue @@ -0,0 +1,58 @@ + + \ No newline at end of file diff --git a/smart-flow-web/src/views/flow/components/select-user.vue b/smart-flow-web/src/views/flow/components/select-user.vue new file mode 100644 index 0000000..986caad --- /dev/null +++ b/smart-flow-web/src/views/flow/components/select-user.vue @@ -0,0 +1,106 @@ + + + + diff --git a/smart-flow-web/src/views/flow/definition/definition-form.vue b/smart-flow-web/src/views/flow/definition/definition-form.vue new file mode 100644 index 0000000..6f76776 --- /dev/null +++ b/smart-flow-web/src/views/flow/definition/definition-form.vue @@ -0,0 +1,262 @@ + + + + diff --git a/smart-flow-web/src/views/flow/definition/definition-list.vue b/smart-flow-web/src/views/flow/definition/definition-list.vue new file mode 100644 index 0000000..5d16800 --- /dev/null +++ b/smart-flow-web/src/views/flow/definition/definition-list.vue @@ -0,0 +1,591 @@ + + + + + + diff --git a/smart-flow-web/src/views/flow/task/done/index.vue b/smart-flow-web/src/views/flow/task/done/index.vue new file mode 100644 index 0000000..7b86788 --- /dev/null +++ b/smart-flow-web/src/views/flow/task/done/index.vue @@ -0,0 +1,256 @@ +