V2.1.4 升级 V2.2.X 方案

由于 2.2.X 采用的是 jdk22 和 springboot3.4.6,整体框架包做了全方位的升级 包括mybatis、hutool、guava、spring、spring-security、swagger 等等
不建议在现有框架的基础上升级,过程中容易出现疏漏,以下为本次升级方案的最佳实践

一、本地下载jdk 理论上openjdk18以上都可以,本次使用jdk22

推荐使用 sdkman 做统一的环境环境,方便快速切换jdk环境

https://zhuanlan.zhihu.com/p/6236073515

二、下载最新版基础脚手架

https://github.com/hiparker/opsli-boot.git
https://github.com/hiparker/opsli-ui.git
对应最新版的前后端代码

三、拷贝现有业务代码到moduler中

四、进行代码升级替换

4.1 替换 javax 为 jakarta

// 全局查找替换
javax.servlet         → jakarta.servlet
javax.persistence     → jakarta.persistence
javax.validation      → jakarta.validation
javax.annotation      → jakarta.annotation
javax.xml.bind        → jakarta.xml.bind
javax.activation      → jakarta.activation
javax.transaction     → jakarta.transaction
javax.ejb             → jakarta.ejb
javax.jms             → jakarta.jms
javax.mail            → jakarta.mail
javax.websocket       → jakarta.websocket
javax.json            → jakarta.json
javax.ws.rs           → jakarta.ws.rs

具体示例

// 之前 (Spring Boot 2.x)
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.persistence.Entity;
import javax.persistence.Id;

// 现在 (Spring Boot 3.x)
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;

4.2 配置属性变更

如果你本地业务代码有对 application.yaml application-local.yaml application-dev.yaml application-prod.yaml
做相关的修改 请对比最新版配置
比如 spring.redis.xxx 已改为 spring.data.redis
文档也更换为spring-doc

// 旧
spring:
  #redis 配置
  redis:
    database: 0
    host: 127.0.0.1
    password: '123456'
    port: 6379



// 新
spring:
  data:
    #redis 配置
    redis:
      database: 0
      host: 127.0.0.1
      password: '123456'
      port: 6379
# swagger接口文档
springdoc:
  swagger-ui:
    enabled: true
# druid 配置
# dynamic.druid 配置

4.3 从 Swagger 2/3 迁移到 SpringDoc

4.3.1 配置迁移

由于Knife4j 不太支持最新版3.4.x 的springboot,本次直接使用的springdoc作为接口文档,也就是swagger3
所以之前使用Knife4j 的一些配置都需要修改,包括之前的SwaggerConfig.java里的配置 现在已迁移到SpringDocConfig.java


由于 springboot 3.x 只支持 openapi方式的接口文档注入 所以 controller model 中的注解也得修改

4.3.2 代码升级

// 全局查找,逐个替换
// ❌ 旧的 SpringFox 注解
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;

// ✅ 新的 OpenAPI 3 注解
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;

示例

4.4 MybatisPlus升级 需要替换注解

主要就是 FiledStratege 策略枚举的变更

FieldStrategy.IGNOREDFieldStrategy.ALWAYS

4.5 logback配置变更 如果本地有变更自定义输出内容的注意这里

4.6 代码生成器升级

执行数据替换

TRUNCATE table gen_template;
TRUNCATE table gen_template_detail;
INSERT INTO `gen_template` VALUES (1398253704724828162, 'Form表单', '0', '默认Form表单', 50, 1313694379541635074, '2021-05-28 20:23:56', 1, '2025-06-02 17:30:27');
INSERT INTO `gen_template_detail` VALUES (1929470655727972354, 1398253704724828162, '0', '${packageName}/${moduleName}/${subModuleName}/entity', '${model.tableHumpName}Entity.java', '#if(data.subModuleName != null && data.subModuleName != \"\")\npackage #(data.packageName+\".\"+data.moduleName+\".\"+data.subModuleName).entity;\n#else\npackage #(data.packageName+\".\"+data.moduleName).entity;\n#end\n\n#for(pkg : data.model.entityPkgList)\nimport #(pkg);\n#end\nimport com.baomidou.mybatisplus.annotation.FieldStrategy;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableLogic;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport org.opsli.core.base.entity.BaseEntity;\n\n/**\n * #(data.codeTitle) Entity\n *\n * @author #(data.authorName)\n * @date #(currTime)\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\npublic class #(data.model.tableHumpName) extends BaseEntity {\n\n\n    #for(column : data.model.columnList)\n    ### 不等于 删除字段 和 不等于 租户字段放入上边\n    #if(column.fieldHumpName != \"deleted\" && column.fieldHumpName != \"tenantId\")\n    /** #(column.fieldComments) */\n    #if(!column.izNotNull)\n    @TableField(updateStrategy = FieldStrategy.IGNORED)\n    #end\n    private #(column.javaType) #(column.fieldHumpName);\n\n    #end\n    #end\n\n    // ========================================\n\n    ### 专门处理 删除字段 和 租户字段\n    #for(column : data.model.columnList)\n    #if(column.fieldHumpName == \"deleted\")\n    /** 逻辑删除字段 */\n    @TableLogic\n    private Integer deleted;\n    #else if(column.fieldHumpName == \"tenantId\")\n    /** 多租户字段 */\n    private String tenantId;\n    #end\n\n    #end\n\n}', '1', 0, 1, '2025-06-02 17:30:27', 1, '2025-06-02 17:30:27');
INSERT INTO `gen_template_detail` VALUES (1929470655816052738, 1398253704724828162, '0', '${packageName}/${moduleName}/${subModuleName}/mapper', '${model.tableHumpName}Mapper.java', '#if(data.subModuleName != null && data.subModuleName != \"\")\npackage #(data.packageName+\".\"+data.moduleName+\".\"+data.subModuleName).mapper;\n#else\npackage #(data.packageName+\".\"+data.moduleName).mapper;\n#end\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Param;\n#if(data.subModuleName != null && data.subModuleName != \"\")\nimport #(data.packageName+\".\"+data.moduleName+\".\"+data.subModuleName).entity.#(data.model.tableHumpName);\n#else\nimport #(data.packageName+\".\"+data.moduleName).entity.#(data.model.tableHumpName);\n#end\n\n/**\n * #(data.codeTitle) Mapper\n *\n * @author #(data.authorName)\n * @date #(currTime)\n */\n@Mapper\npublic interface #(data.model.tableHumpName)Mapper extends BaseMapper<#(data.model.tableHumpName)> {\n\n}', '0', 0, 1, '2025-06-02 17:30:27', 1, '2025-06-02 17:30:27');
INSERT INTO `gen_template_detail` VALUES (1929470655899938817, 1398253704724828162, '0', '${packageName}/${moduleName}/${subModuleName}/mapper/xml', '${model.tableHumpName}Mapper.xml', '<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n#if(data.subModuleName != null && data.subModuleName != \"\")\n<mapper namespace=\"#(data.packageName+\'.\'+data.moduleName+\'.\'+data.subModuleName).mapper.#(data.model.tableHumpName)Mapper\">\n#else\n<mapper namespace=\"#(data.packageName+\'.\'+data.moduleName).mapper.#(data.model.tableHumpName)Mapper\">\n#end\n\n\n</mapper>', '0', 0, 1, '2025-06-02 17:30:27', 1, '2025-06-02 17:30:27');
INSERT INTO `gen_template_detail` VALUES (1929470655996407809, 1398253704724828162, '0', 'org/opsli/api/wrapper/${moduleName}/${subModuleName}', '${model.tableHumpName}Model.java', '#if(data.subModuleName != null && data.subModuleName != \"\")\npackage #(apiPath).wrapper.#(data.moduleName+\".\"+data.subModuleName);\n#else\npackage #(apiPath).wrapper.#(data.moduleName);\n#end\n\n#for(pkg : data.model.entityPkgList)\nimport #(pkg);\n#end\nimport com.alibaba.excel.annotation.ExcelProperty;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport #(apiPath).base.warpper.ApiWrapper;\nimport org.opsli.common.annotation.validator.Validator;\nimport org.opsli.common.annotation.validator.ValidatorLenMax;\nimport org.opsli.common.annotation.validator.ValidatorLenMin;\nimport org.opsli.common.enums.ValidatorType;\nimport org.opsli.plugins.excel.annotation.ExcelInfo;\nimport com.fasterxml.jackson.annotation.JsonFormat;\nimport org.springframework.format.annotation.DateTimeFormat;\n\n/**\n* #(data.codeTitle) Model\n*\n* @author #(data.authorName)\n* @date #(currTime)\n*/\n@Data\n@EqualsAndHashCode(callSuper = false)\npublic class #(data.model.tableHumpName)Model extends ApiWrapper {\n\n    #for(column : data.model.columnList)\n    ### 不等于 删除字段 和 不等于 租户字段放入上边\n    #if(column.fieldHumpName != \"deleted\" && column.fieldHumpName != \"tenantId\")\n    /** #(column.fieldComments) */\n    @Schema(description = \"#(column.fieldComments)\")\n    @ExcelProperty(value = \"#(column.fieldComments)\", order = #(column.sort))\n    #if(column.dictTypeCode != null && column.dictTypeCode != \"\")\n    @ExcelInfo( dictType = \"#(column.dictTypeCode)\" )\n    #else\n    @ExcelInfo\n    #end\n    #if(column.validateTypeAndCommaList != null && column.validateTypeAndCommaList.size() > 0)\n    @Validator({\n        #for(typeAndComma : column.validateTypeAndCommaList)\n        ValidatorType.#(typeAndComma)\n        #end\n    })\n    #end\n    #if(column.fieldLength != null && column.fieldLength > 0)\n    #if(column.fieldPrecision != null && column.fieldPrecision > 0)\n    @ValidatorLenMax(#(column.fieldLength+column.fieldPrecision))\n    #else\n    @ValidatorLenMax(#(column.fieldLength))\n    #end\n    #end\n    ### 日期处理\n    #if(column.javaType == \"Date\")\n    #if(column.showType == \"4\")\n    @JsonFormat(timezone = \"GMT+8\", pattern = \"yyyy-MM-dd\")\n    @DateTimeFormat(pattern = \"yyyy-MM-dd\")\n    #else\n    @JsonFormat(timezone = \"GMT+8\", pattern = \"yyyy-MM-dd HH:mm:ss\")\n    @DateTimeFormat(pattern = \"yyyy-MM-dd HH:mm:ss\")\n    #end\n    #end\n    private #(column.javaType) #(column.fieldHumpName);\n\n    #end\n    #end\n\n\n}', '0', 0, 1, '2025-06-02 17:30:27', 1, '2025-06-02 17:30:27');
INSERT INTO `gen_template_detail` VALUES (1929470656071905282, 1398253704724828162, '0', 'org/opsli/api/web/${moduleName}/${subModuleName}', '${model.tableHumpName}RestApi.java', '#if(data.subModuleName != null && data.subModuleName != \"\")\npackage #(apiPath).web.#(data.moduleName+\".\"+data.subModuleName);\n#else\npackage #(apiPath).web.#(data.moduleName);\n#end\n\nimport org.opsli.api.base.result.ResultWrapper;\nimport org.springframework.web.bind.annotation.*;\nimport org.springframework.web.multipart.MultipartHttpServletRequest;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\n\n#if(data.subModuleName != null && data.subModuleName != \"\")\nimport #(apiPath).wrapper.#(data.moduleName+\".\"+data.subModuleName).#(data.model.tableHumpName)Model;\n#else\nimport #(apiPath).wrapper.#(data.moduleName).#(data.model.tableHumpName)Model;\n#end\n\n\n/**\n * #(data.codeTitle) Api\n *\n * 对外 API 直接 暴露 @GetMapping 或者 @PostMapping\n * 对内也推荐 单机版 不需要设置 Mapping 但是调用方法得从Controller写起\n *\n * 这样写法虽然比较绕,但是当单体项目想要改造微服务架构时 时非常容易的\n *\n * @author #(data.authorName)\n * @date #(currTime)\n */\npublic interface #(data.model.tableHumpName)RestApi {\n\n    /** 标题 */\n    String TITLE = \"#(data.codeTitle)\";\n    /** 子标题 */\n    String SUB_TITLE = \"#(data.codeTitleBrief)\";\n\n    /**\n    * #(data.codeTitle) 查一条\n    * @param model 模型\n    * @return ResultWrapper\n    */\n    @GetMapping(\"/get\")\n    ResultWrapper<#(data.model.tableHumpName)Model> get(#(data.model.tableHumpName)Model model);\n\n    /**\n    * #(data.codeTitle) 查询分页\n    * @param pageNo 当前页\n    * @param pageSize 每页条数\n    * @param request request\n    * @return ResultWrapper\n    */\n    @GetMapping(\"/findPage\")\n    ResultWrapper<?> findPage(\n        @RequestParam(name = \"pageNo\", defaultValue = \"1\") Integer pageNo,\n        @RequestParam(name = \"pageSize\", defaultValue = \"10\") Integer pageSize,\n        HttpServletRequest request\n    );\n\n    /**\n    * #(data.codeTitle) 新增\n    * @param model 模型\n    * @return ResultWrapper\n    */\n    @PostMapping(\"/insert\")\n    ResultWrapper<?> insert(@RequestBody #(data.model.tableHumpName)Model model);\n\n    /**\n    * #(data.codeTitle) 修改\n    * @param model 模型\n    * @return ResultWrapper\n    */\n    @PostMapping(\"/update\")\n    ResultWrapper<?> update(@RequestBody #(data.model.tableHumpName)Model model);\n\n    /**\n    * #(data.codeTitle) 删除\n    * @param id ID\n    * @return ResultWrapper\n    */\n    @PostMapping(\"/del\")\n    ResultWrapper<?> del(String id);\n\n    /**\n    * #(data.codeTitle) 批量删除\n    * @param ids ID 数组\n    * @return ResultWrapper\n    */\n    @PostMapping(\"/delAll\")\n    ResultWrapper<?> delAll(String ids);\n\n    /**\n     * #(data.codeTitle) Excel 导出认证\n     *\n     * @param type 类型\n     * @param request request\n     */\n    @GetMapping(\"/excel/auth/{type}\")\n    ResultWrapper<String> exportExcelAuth(\n            @PathVariable(\"type\") String type,\n            HttpServletRequest request);\n\n    /**\n     * #(data.codeTitle) Excel 导出\n     *\n     * @param certificate 凭证\n     * @param response response\n     */\n    @GetMapping(\"/excel/export/{certificate}\")\n    void exportExcel(\n            @PathVariable(\"certificate\") String certificate,\n            HttpServletResponse response);\n\n    /**\n    * #(data.codeTitle) Excel 导入\n    * @param request 文件流 request\n    * @return ResultWrapper\n    */\n    @PostMapping(\"/importExcel\")\n    ResultWrapper<?> importExcel(MultipartHttpServletRequest request);\n\n}', '0', 0, 1, '2025-06-02 17:30:27', 1, '2025-06-02 17:30:27');
INSERT INTO `gen_template_detail` VALUES (1929470656151597058, 1398253704724828162, '0', '${packageName}/${moduleName}/${subModuleName}/web', '${model.tableHumpName}RestController.java', '#if(data.subModuleName != null && data.subModuleName != \"\")\npackage #(data.packageName+\".\"+data.moduleName+\".\"+data.subModuleName).web;\n#else\npackage #(data.packageName+\".\"+data.moduleName).web;\n#end\n\nimport java.util.Optional;\nimport cn.hutool.core.convert.Convert;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport lombok.extern.slf4j.Slf4j;\nimport #(apiPath).base.result.ResultWrapper;\nimport org.opsli.common.annotation.ApiRestController;\nimport org.opsli.core.base.controller.BaseRestController;\nimport org.opsli.core.persistence.Page;\nimport org.opsli.core.persistence.querybuilder.QueryBuilder;\nimport org.opsli.core.persistence.querybuilder.WebQueryBuilder;\nimport org.springframework.web.multipart.MultipartHttpServletRequest;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.opsli.core.log.enums.*;\nimport org.opsli.core.log.annotation.OperateLogger;\n\n#if(data.subModuleName != null && data.subModuleName != \"\")\nimport #(data.packageName+\".\"+data.moduleName+\".\"+data.subModuleName).entity.#(data.model.tableHumpName);\nimport #(apiPath).wrapper.#(data.moduleName+\".\"+data.subModuleName).#(data.model.tableHumpName)Model;\nimport #(data.packageName+\".\"+data.moduleName+\".\"+data.subModuleName).service.I#(data.model.tableHumpName)Service;\nimport #(apiPath).web.#(data.moduleName+\".\"+data.subModuleName).#(data.model.tableHumpName)RestApi;\n#else\nimport #(data.packageName+\".\"+data.moduleName).entity.#(data.model.tableHumpName);\nimport #(apiPath).wrapper.#(data.moduleName).#(data.model.tableHumpName)Model;\nimport #(data.packageName+\".\"+data.moduleName).service.I#(data.model.tableHumpName)Service;\nimport #(apiPath).web.#(data.moduleName).#(data.model.tableHumpName)RestApi;\n#end\n\n/**\n * #(data.codeTitle) Controller\n *\n * @author #(data.authorName)\n * @date #(currTime)\n */\n@Tag(name = #(data.model.tableHumpName)RestApi.TITLE)\n@Slf4j\n#if(data.subModuleName != null && data.subModuleName != \"\")\n@ApiRestController(\"/{ver}/#(data.moduleName)/#(data.subModuleName)\")\n#else\n@ApiRestController(\"/{ver}/#(data.moduleName)\")\n#end\npublic class #(data.model.tableHumpName)RestController extends BaseRestController<#(data.model.tableHumpName), #(data.model.tableHumpName)Model, I#(data.model.tableHumpName)Service>\n    implements #(data.model.tableHumpName)RestApi {\n\n\n    /**\n     * #(data.codeTitleBrief) 查一条\n     * @param model 模型\n     * @return ResultWrapper\n     */\n    @Operation(summary = \"获得单条#(data.codeTitleBrief) - ID\")\n    #if(data.subModuleName != null && data.subModuleName != \"\")\n    @PreAuthorize(\"hasAuthority(\'#(data.moduleName.toLowerCase())_#(data.subModuleName.toLowerCase())_select\')\")\n    #else\n    @PreAuthorize(\"hasAuthority(\'#(data.moduleName.toLowerCase())_select\')\")\n    #end\n    @Override\n    public ResultWrapper<#(data.model.tableHumpName)Model> get(#(data.model.tableHumpName)Model model) {\n        // 如果系统内部调用 则直接查数据库\n        if(model != null && model.getIzApi() != null && model.getIzApi()){\n            model = IService.get(model);\n        }\n        return ResultWrapper.getSuccessResultWrapper(model);\n    }\n\n    /**\n     * #(data.codeTitleBrief) 查询分页\n     * @param pageNo 当前页\n     * @param pageSize 每页条数\n     * @param request request\n     * @return ResultWrapper\n     */\n    @Operation(summary = \"获得分页数据 - 查询构造器\")\n    #if(data.subModuleName != null && data.subModuleName != \"\")\n    @PreAuthorize(\"hasAuthority(\'#(data.moduleName.toLowerCase())_#(data.subModuleName.toLowerCase())_select\')\")\n    #else\n    @PreAuthorize(\"hasAuthority(\'#(data.moduleName.toLowerCase())_select\')\")\n    #end\n    @Override\n    public ResultWrapper<?> findPage(Integer pageNo, Integer pageSize, HttpServletRequest request) {\n\n        QueryBuilder<#(data.model.tableHumpName)> queryBuilder = new WebQueryBuilder<>(IService.getEntityClass(), request.getParameterMap());\n        Page<#(data.model.tableHumpName), #(data.model.tableHumpName)Model> page = new Page<>(pageNo, pageSize);\n        page.setQueryWrapper(queryBuilder.build());\n        page = IService.findPage(page);\n\n        return ResultWrapper.getSuccessResultWrapper(page.getPageData());\n    }\n\n    /**\n     * #(data.codeTitleBrief) 新增\n     * @param model 模型\n     * @return ResultWrapper\n     */\n    @Operation(summary = \"新增#(data.codeTitleBrief)数据\")\n    #if(data.subModuleName != null && data.subModuleName != \"\")\n    @PreAuthorize(\"hasAuthority(\'#(data.moduleName.toLowerCase())_#(data.subModuleName.toLowerCase())_insert\')\")\n    #else\n    @PreAuthorize(\"hasAuthority(\'#(data.moduleName.toLowerCase())_insert\')\")\n    #end\n    @OperateLogger(description = \"新增#(data.codeTitleBrief)数据\",\n            module = ModuleEnum.MODULE_UNKNOWN, operationType = OperationTypeEnum.INSERT, db = true)\n    @Override\n    public ResultWrapper<?> insert(#(data.model.tableHumpName)Model model) {\n        // 调用新增方法\n        IService.insert(model);\n        return ResultWrapper.getSuccessResultWrapperByMsg(\"新增#(data.codeTitleBrief)成功\");\n    }\n\n    /**\n     * #(data.codeTitleBrief) 修改\n     * @param model 模型\n     * @return ResultWrapper\n     */\n    @Operation(summary = \"修改#(data.codeTitleBrief)数据\")\n    #if(data.subModuleName != null && data.subModuleName != \"\")\n    @PreAuthorize(\"hasAuthority(\'#(data.moduleName.toLowerCase())_#(data.subModuleName.toLowerCase())_update\')\")\n    #else\n    @PreAuthorize(\"hasAuthority(\'#(data.moduleName.toLowerCase())_update\')\")\n    #end    \n    @OperateLogger(description = \"修改#(data.codeTitleBrief)数据\",\n            module = ModuleEnum.MODULE_UNKNOWN, operationType = OperationTypeEnum.UPDATE, db = true)\n    @Override\n    public ResultWrapper<?> update(#(data.model.tableHumpName)Model model) {\n        // 调用修改方法\n        IService.update(model);\n        return ResultWrapper.getSuccessResultWrapperByMsg(\"修改#(data.codeTitleBrief)成功\");\n    }\n\n\n    /**\n     * #(data.codeTitleBrief) 删除\n     * @param id ID\n     * @return ResultVo\n     */\n    @Operation(summary = \"删除#(data.codeTitleBrief)数据\")\n    #if(data.subModuleName != null && data.subModuleName != \"\")\n    @PreAuthorize(\"hasAuthority(\'#(data.moduleName.toLowerCase())_#(data.subModuleName.toLowerCase())_delete\')\")\n    #else\n    @PreAuthorize(\"hasAuthority(\'#(data.moduleName.toLowerCase())_delete\')\")\n    #end   \n    @OperateLogger(description = \"删除#(data.codeTitleBrief)数据\",\n            module = ModuleEnum.MODULE_UNKNOWN, operationType = OperationTypeEnum.DELETE, db = true)\n    @Override\n    public ResultWrapper<?> del(String id){\n        IService.delete(id);\n        return ResultWrapper.getSuccessResultWrapperByMsg(\"删除#(data.codeTitleBrief)成功\");\n    }\n\n    /**\n     * #(data.codeTitleBrief) 批量删除\n     * @param ids ID 数组\n     * @return ResultVo\n     */\n    @Operation(summary = \"批量删除#(data.codeTitleBrief)数据\")\n    #if(data.subModuleName != null && data.subModuleName != \"\")\n    @PreAuthorize(\"hasAuthority(\'#(data.moduleName.toLowerCase())_#(data.subModuleName.toLowerCase())_delete\')\")\n    #else\n    @PreAuthorize(\"hasAuthority(\'#(data.moduleName.toLowerCase())_delete\')\")\n    #end   \n    @OperateLogger(description = \"批量删除#(data.codeTitleBrief)数据\",\n            module = ModuleEnum.MODULE_UNKNOWN, operationType = OperationTypeEnum.DELETE, db = true)\n    @Override\n    public ResultWrapper<?> delAll(String ids){\n        String[] idArray = Convert.toStrArray(ids);\n        IService.deleteAll(idArray);\n        return ResultWrapper.getSuccessResultWrapperByMsg(\"批量删除#(data.codeTitleBrief)成功\");\n    }\n\n    /**\n     * #(data.codeTitleBrief) Excel 导出认证\n     *\n     * @param type 类型\n     * @param request request\n     */\n    @Operation(summary = \"Excel 导出认证\")\n    #if(data.subModuleName != null && data.subModuleName != \"\")\n    @PreAuthorize(\"hasAnyAuthority(\'#(data.moduleName.toLowerCase())_#(data.subModuleName.toLowerCase())_export\', \'#(data.moduleName.toLowerCase())_#(data.subModuleName.toLowerCase())_import\')\")\n    #else\n    @PreAuthorize(\"hasAnyAuthority(\'#(data.moduleName.toLowerCase())_export\', \'#(data.moduleName.toLowerCase())_import\')\")\n    #end\n    @Override\n    public ResultWrapper<String> exportExcelAuth(String type, HttpServletRequest request) {\n        Optional<String> certificateOptional =\n                super.excelExportAuth(type, #(data.model.tableHumpName)RestApi.SUB_TITLE, request);\n        if(!certificateOptional.isPresent()){\n            return ResultWrapper.getErrorResultWrapper();\n        }\n        return ResultWrapper.getSuccessResultWrapper(certificateOptional.get());\n    }\n\n\n    /**\n     * #(data.codeTitleBrief) Excel 导出\n     * @param response response\n     */\n    @Operation(summary = \"导出Excel\")\n    #if(data.subModuleName != null && data.subModuleName != \"\")\n    @PreAuthorize(\"hasAuthority(\'#(data.moduleName.toLowerCase())_#(data.subModuleName.toLowerCase())_export\')\")\n    #else\n    @PreAuthorize(\"hasAuthority(\'#(data.moduleName.toLowerCase())_export\')\")\n    #end\n    @OperateLogger(description = \"#(data.codeTitleBrief) 导出Excel\",\n            module = ModuleEnum.MODULE_UNKNOWN, operationType = OperationTypeEnum.SELECT, db = true)\n    @Override\n    public void exportExcel(String certificate, HttpServletResponse response) {\n        // 导出Excel\n        super.excelExport(certificate, response);\n    }\n\n    /**\n     * #(data.codeTitleBrief) Excel 导入\n     * 注:这里 RequiresPermissions 引入的是 Shiro原生鉴权注解\n     * @param request 文件流 request\n     * @return ResultVo\n     */\n    @Operation(summary = \"导入Excel\")\n    #if(data.subModuleName != null && data.subModuleName != \"\")\n    @PreAuthorize(\"hasAuthority(\'#(data.moduleName.toLowerCase())_#(data.subModuleName.toLowerCase())_import\')\")\n    #else\n    @PreAuthorize(\"hasAuthority(\'#(data.moduleName.toLowerCase())_import\')\")\n    #end   \n    @OperateLogger(description = \"#(data.codeTitleBrief) Excel 导入\",\n            module = ModuleEnum.MODULE_UNKNOWN, operationType = OperationTypeEnum.INSERT, db = true)\n    @Override\n    public ResultWrapper<?> importExcel(MultipartHttpServletRequest request) {\n        return super.importExcel(request);\n    }\n\n}', '0', 0, 1, '2025-06-02 17:30:27', 1, '2025-06-02 17:30:27');
INSERT INTO `gen_template_detail` VALUES (1929470656260648962, 1398253704724828162, '0', '${packageName}/${moduleName}/${subModuleName}/service/impl', '${model.tableHumpName}ServiceImpl.java', '#if(data.subModuleName != null && data.subModuleName != \"\")\npackage #(data.packageName+\".\"+data.moduleName+\".\"+data.subModuleName).service.impl;\n#else\npackage #(data.packageName+\".\"+data.moduleName).service.impl;\n#end\n\n\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.opsli.core.base.service.impl.CrudServiceImpl;\n\n#if(data.subModuleName != null && data.subModuleName != \"\")\nimport #(data.packageName+\".\"+data.moduleName+\".\"+data.subModuleName).entity.#(data.model.tableHumpName);\nimport #(apiPath).wrapper.#(data.moduleName+\".\"+data.subModuleName).#(data.model.tableHumpName)Model;\nimport #(data.packageName+\".\"+data.moduleName+\".\"+data.subModuleName).service.I#(data.model.tableHumpName)Service;\nimport #(data.packageName+\".\"+data.moduleName+\".\"+data.subModuleName).mapper.#(data.model.tableHumpName)Mapper;\n#else\nimport #(data.packageName+\".\"+data.moduleName).entity.#(data.model.tableHumpName);\nimport #(apiPath).wrapper.#(data.moduleName).#(data.model.tableHumpName)Model;\nimport #(data.packageName+\".\"+data.moduleName).service.I#(data.model.tableHumpName)Service;\nimport #(data.packageName+\".\"+data.moduleName).mapper.#(data.model.tableHumpName)Mapper;\n#end\n\n\n/**\n * #(data.codeTitle) Service Impl\n *\n * @author #(data.authorName)\n * @date #(currTime)\n */\n@Service\npublic class #(data.model.tableHumpName)ServiceImpl extends CrudServiceImpl<#(data.model.tableHumpName)Mapper, #(data.model.tableHumpName), #(data.model.tableHumpName)Model>\n    implements I#(data.model.tableHumpName)Service {\n\n    @Autowired(required = false)\n    private #(data.model.tableHumpName)Mapper mapper;\n\n}', '0', 0, 1, '2025-06-02 17:30:27', 1, '2025-06-02 17:30:27');
INSERT INTO `gen_template_detail` VALUES (1929470656348729345, 1398253704724828162, '0', '${packageName}/${moduleName}/${subModuleName}/service', 'I${model.tableHumpName}Service.java', '#if(data.subModuleName != null && data.subModuleName != \"\")\npackage #(data.packageName+\".\"+data.moduleName+\".\"+data.subModuleName).service;\n#else\npackage #(data.packageName+\".\"+data.moduleName).service;\n#end\n\nimport org.opsli.core.base.service.interfaces.CrudServiceInterface;\n\n\n#if(data.subModuleName != null && data.subModuleName != \"\")\nimport #(data.packageName+\".\"+data.moduleName+\".\"+data.subModuleName).entity.#(data.model.tableHumpName);\nimport #(apiPath).wrapper.#(data.moduleName+\".\"+data.subModuleName).#(data.model.tableHumpName)Model;\n#else\nimport #(data.packageName+\".\"+data.moduleName).entity.#(data.model.tableHumpName);\nimport #(apiPath).wrapper.#(data.moduleName).#(data.model.tableHumpName)Model;\n#end\n\n/**\n * #(data.codeTitle) Service\n *\n * @author #(data.authorName)\n * @date #(currTime)\n */\npublic interface I#(data.model.tableHumpName)Service extends CrudServiceInterface<#(data.model.tableHumpName), #(data.model.tableHumpName)Model> {\n\n}', '0', 0, 1, '2025-06-02 17:30:27', 1, '2025-06-02 17:30:27');
INSERT INTO `gen_template_detail` VALUES (1929470656596193281, 1398253704724828162, '1', 'src/api/${moduleName}/${subModuleName}', '${model.tableHumpName}ManagementApi.js', 'import request from \"@/utils/request\";\nimport { downloadFileByData } from \"@/utils/download\";\n\nexport function getList(data) {\n  return request({\n    #if(data.subModuleName != null && data.subModuleName != \"\")\n    url: \"/api/v1/#(data.moduleName)/#(data.subModuleName)/findPage\",\n    #else\n    url: \"/api/v1/#(data.moduleName)/findPage\",\n    #end\n    method: \"get\",\n    params: data,\n  });\n}\n\nexport function doInsert(data) {\n  return request({\n    #if(data.subModuleName != null && data.subModuleName != \"\")\n    url: \"/api/v1/#(data.moduleName)/#(data.subModuleName)/insert\",\n    #else\n    url: \"/api/v1/#(data.moduleName)/insert\",\n    #end\n    method: \"post\",\n    data,\n  });\n}\n\nexport function doUpdate(data) {\n  return request({\n    #if(data.subModuleName != null && data.subModuleName != \"\")\n    url: \"/api/v1/#(data.moduleName)/#(data.subModuleName)/update\",\n    #else\n    url: \"/api/v1/#(data.moduleName)/update\",\n    #end\n    method: \"post\",\n    data,\n  });\n}\n\nexport function doDelete(data) {\n  return request({\n    #if(data.subModuleName != null && data.subModuleName != \"\")\n    url: \"/api/v1/#(data.moduleName)/#(data.subModuleName)/del\",\n    #else\n    url: \"/api/v1/#(data.moduleName)/del\",\n    #end\n    method: \"post\",\n    params: data,\n  });\n}\n\nexport function doDeleteAll(data) {\n  return request({\n    #if(data.subModuleName != null && data.subModuleName != \"\")\n    url: \"/api/v1/#(data.moduleName)/#(data.subModuleName)/delAll\",\n    #else\n    url: \"/api/v1/#(data.moduleName)/delAll\",\n    #end\n    method: \"post\",\n    params: data,\n  });\n}\n\n/**\n * 导出Excel 目前只支持一层参数传递\n * @param params 参数\n * @returns file\n */\n export async function doExportExcel(params) {\n    #if(data.subModuleName != null && data.subModuleName != \"\")\n    let authURL = \"/api/v1/#(data.moduleName)/#(data.subModuleName)/excel/auth/export\";\n    #else\n    let authURL = \"/api/v1/#(data.moduleName)/excel/auth/export\";\n    #end\n\n    #if(data.subModuleName != null && data.subModuleName != \"\")\n    let downloadURL = \"/api/v1/#(data.moduleName)/#(data.subModuleName)/excel/export/\";\n    #else\n    let downloadURL = \"/api/v1/#(data.moduleName)/excel/export/\";\n    #end\n\n\n    // 认证\n    const { data } = await request({\n      url: authURL,\n      method: \"get\",\n      params: params,\n    });\n  \n    if (data) {\n      // 下载文件\n      downloadFileByData(downloadURL + data, params);\n    }\n  }\n  \n  /**\n   * 下载模版\n   * @returns file\n   */\n  export async function doDownloadTemplate() {\n    #if(data.subModuleName != null && data.subModuleName != \"\")\n    let authURL = \"/api/v1/#(data.moduleName)/#(data.subModuleName)/excel/auth/import-template-export\";\n    #else\n    let authURL = \"/api/v1/#(data.moduleName)/excel/auth/import-template-export\";\n    #end\n\n    #if(data.subModuleName != null && data.subModuleName != \"\")\n    let downloadURL = \"/api/v1/#(data.moduleName)/#(data.subModuleName)/excel/export/\";\n    #else\n    let downloadURL = \"/api/v1/#(data.moduleName)/excel/export/\";\n    #end\n\n    // 认证\n    const { data } = await request({\n      url: authURL,\n      method: \"get\",\n    });\n  \n    if (data) {\n      // 下载文件\n      downloadFileByData(downloadURL + data, {});\n    }\n  }\n\n\n/**\n * 导入Excel\n * @returns file\n */\nexport function doImportExcel(data) {\n  return request({\n    #if(data.subModuleName != null && data.subModuleName != \"\")\n    url: \"/api/v1/#(data.moduleName)/#(data.subModuleName)/importExcel\",\n    #else\n    url: \"/api/v1/#(data.moduleName)/importExcel\",\n    #end\n    method: \"post\",\n    // 最长超时时间 3 分钟\n    timeout: 180000,\n    headers: {\n      \"Content-Type\": \"multipart/form-data\"\n    },\n    data,\n  });\n}', '0', 0, 1, '2025-06-02 17:30:27', 1, '2025-06-02 17:30:27');
INSERT INTO `gen_template_detail` VALUES (1929470656734605313, 1398253704724828162, '1', 'src/views/modules/${moduleName}/${subModuleName}/components', '${model.tableHumpName}ManagementEdit.vue', '<template>\n  <el-dialog\n    :title=\"title\"\n    :visible.sync=\"dialogFormVisible\"\n    :close-on-click-modal=\"false\"\n    width=\"800px\"\n    @close=\"close\"\n  >\n    <el-form ref=\"form\" :model=\"form\" :rules=\"rules\" label-width=\"105px\">\n      <el-row :gutter=\"10\" >\n      #for(column : data.model.formList)\n        ### 文本框\n        #if(column.showType == \"0\")\n        <el-col :xs=\"24\" :sm=\"24\" :md=\"24\" :lg=\"12\" :xl=\"12\">\n          <el-form-item label=\"#(column.fieldComments)\" prop=\"#(column.fieldHumpName)\">\n            <el-input v-model=\"form.#(column.fieldHumpName)\" autocomplete=\"off\"></el-input>\n          </el-form-item>\n        </el-col>\n        ### 文本域\n        #else if(column.showType == \"1\")\n        <el-col :xs=\"24\" :sm=\"24\" :md=\"24\" :lg=\"12\" :xl=\"12\">\n          <el-form-item label=\"#(column.fieldComments)\" prop=\"#(column.fieldHumpName)\">\n            <el-input type=\"textarea\" v-model=\"form.#(column.fieldHumpName)\" autocomplete=\"off\"></el-input>\n          </el-form-item>\n        </el-col>\n        ### 字典\n        #else if(column.showType == \"2\")\n        <el-col :xs=\"24\" :sm=\"24\" :md=\"24\" :lg=\"12\" :xl=\"12\">\n          <el-form-item label=\"#(column.fieldComments)\" prop=\"#(column.fieldHumpName)\">\n            <el-select v-model=\"form.#(column.fieldHumpName)\" clearable\n                       placeholder=\"请选择\" style=\"width: 100%\">\n              <el-option\n                      v-for=\"item in dict.#(column.dictTypeCode)\"\n                      :key=\"item.dictValue\"\n                      :label=\"item.dictName\"\n                      :value=\"item.dictValue\"\n              ></el-option>\n            </el-select>\n          </el-form-item>\n        </el-col>\n        ### 日期时间\n        #else if(column.showType == \"3\")\n        <el-col :xs=\"24\" :sm=\"24\" :md=\"24\" :lg=\"12\" :xl=\"12\">\n          <el-form-item label=\"#(column.fieldComments)\" prop=\"#(column.fieldHumpName)\">\n            <el-date-picker\n                    v-model=\"form.#(column.fieldHumpName)\"\n                    type=\"datetime\"\n                    placeholder=\"选择#(column.fieldComments)\"\n                    style=\"width: 100%\"\n            ></el-date-picker>\n          </el-form-item>\n        </el-col>\n        ### 日期\n        #else if(column.showType == \"4\")\n        <el-col :xs=\"24\" :sm=\"24\" :md=\"24\" :lg=\"12\" :xl=\"12\">\n          <el-form-item label=\"#(column.fieldComments)\" prop=\"#(column.fieldHumpName)\">\n            <el-date-picker\n                    v-model=\"form.#(column.fieldHumpName)\"\n                    type=\"date\"\n                    placeholder=\"选择#(column.fieldComments)\"\n                    style=\"width: 100%\"\n            ></el-date-picker>\n          </el-form-item>\n        </el-col>\n        #end\n\n      #end\n      </el-row>\n\n    </el-form>\n    <div slot=\"footer\" class=\"dialog-footer\">\n      <el-button @click=\"close\">取 消</el-button>\n      <el-button type=\"primary\" @click=\"save\">确 定</el-button>\n    </div>\n  </el-dialog>\n</template>\n\n<script>\n  #if(data.subModuleName != null && data.subModuleName != \"\")\n  import { doInsert, doUpdate } from \"@/api/#(data.moduleName)/#(data.subModuleName)/#(data.model.tableHumpName)ManagementApi\";\n  #else\n  import { doInsert, doUpdate } from \"@/api/#(data.moduleName)/#(data.model.tableHumpName)ManagementApi\";\n  #end\n  import { isNull } from \"@/utils/validate\";\n  import { formateDate } from \"@/utils/format\";\n  import { validatorRule } from \"@/utils/validateRlue\";\n\n  export default {\n    name: \"#(data.model.tableHumpName)ManagementEdit\",\n    data() {\n\n      return {\n        form: {\n          // 设置默认值\n          version: 0\n        },\n        dict: {},\n        rules: {\n          #for(columnList : data.model.formList)\n          #for(column : columnList)\n            #if(column.validateTypeList != null && column.validateTypeList.size() > 0)\n              #(column.fieldHumpName): [\n                #for(typeNotComma : column.validateTypeList)\n                #if(typeNotComma == \"IS_NOT_NULL\")\n                { required: true, trigger: \"blur\", message: \"#(column.fieldComments)非空\" },\n                #end\n                #end\n                #for(typeNotComma : column.validateTypeList)\n                #if(typeNotComma != \"IS_NOT_NULL\")\n                { required: false, trigger: \"blur\", validator: validatorRule.#(typeNotComma) },\n                #end\n                #end\n              ],\n            #end\n          #end\n          #end\n        },\n        title: \"\",\n        dialogFormVisible: false,\n      };\n    },\n    created() {\n\n    },\n    mounted() {\n      // 加载字典值\n      #for(column : data.model.columnList)\n      #if(column.dictTypeCode != null && column.dictTypeCode != \"\")\n      this.dict.#(column.dictTypeCode) = this.$getDictList(\"#(column.dictTypeCode)\");\n      #end\n      #end\n    },\n    methods: {\n      showEdit(row) {\n        if (!row) {\n          this.title = \"添加\";\n        } else {\n          this.title = \"编辑\";\n          this.form = Object.assign({}, row);\n        }\n        this.dialogFormVisible = true;\n      },\n      close() {\n        this.dialogFormVisible = false;\n        this.$refs[\"form\"].resetFields();\n        this.form = this.$options.data().form;\n      },\n      save() {\n        this.$refs[\"form\"].validate(async (valid) => {\n          if (valid) {\n            // 处理数据\n            this.handlerFormData(this.form);\n\n            // 修改\n            if (!isNull(this.form.id)) {\n              const { msg } = await doUpdate(this.form);\n                this.$baseMessage(msg, \"success\");\n            } else {\n              const { msg } = await doInsert(this.form);\n              this.$baseMessage(msg, \"success\");\n            }\n\n            await this.$emit(\"fetchData\");\n            this.close();\n          } else {\n            return false;\n          }\n        });\n      },\n      // 处理 form数据\n      handlerFormData(formData){\n        if(!isNull(formData)){\n          for(let key in formData){\n            // 对于时间类进行处理\n            if(\"[object Date]\" === Object.prototype.toString.call(formData[key])){\n              formData[key] = formateDate(formData[key], \'yyyy-MM-dd hh:mm:ss\');\n            }\n          }\n        }\n      },\n    },\n  };\n</script>\n', '0', 0, 1, '2025-06-02 17:30:27', 1, '2025-06-02 17:30:27');
INSERT INTO `gen_template_detail` VALUES (1929470656873017346, 1398253704724828162, '1', 'src/views/modules/${moduleName}/${subModuleName}/components', '${model.tableHumpName}ManagementImport.vue', '<template>\n  <el-dialog\n    :title=\"title\"\n    :visible.sync=\"dialogFormVisible\"\n    :close-on-click-modal=\"false\"\n    width=\"800px\"\n    class=\"import-excel\"\n    @close=\"close\"\n  >\n    <el-upload\n      ref=\"excelImport\"\n      drag\n      accept=\".xls,.xlsx\"\n      style=\"width: 100%\"\n      :action=\"importExcelUrl\"\n      :multiple=\"false\"\n      :before-upload=\"beforeUpload\"\n      :http-request=\"handleImport\"\n      :on-success=\"onSuccess\"\n      :on-error=\"onError\"\n      :on-progress=\"onProcess\"\n    >\n      <i class=\"el-icon-upload\"></i>\n      <div class=\"el-upload__text\">将文件拖到此处,或<em>点击导入</em></div>\n      <div class=\"el-upload__tip\" slot=\"tip\">只能上传xls/xlsx文件,且不超过5MB</div>\n    </el-upload>\n\n    <div slot=\"footer\" class=\"dialog-footer\">\n      <el-button type=\"primary\" @click=\"downloadExcelTemplate\">下载模版</el-button>\n      <el-button @click=\"close\">关 闭</el-button>\n    </div>\n  </el-dialog>\n</template>\n\n<script>\n  #if(data.subModuleName != null && data.subModuleName != \"\")\n  import { doDownloadTemplate, doImportExcel } from \"@/api/#(data.moduleName)/#(data.subModuleName)/#(data.model.tableHumpName)ManagementApi\";\n  #else\n  import { doDownloadTemplate, doImportExcel } from \"@/api/#(data.moduleName)/#(data.model.tableHumpName)ManagementApi\";\n  #end\n  import {isNull} from \"@/utils/validate\";\n  import {random} from \"@/utils\";\n\n  export default {\n    name: \"#(data.model.tableHumpName)ManagementImport\",\n    data() {\n      return {\n        title: \"导入Excel\",\n        importExcelUrl: \'\',\n        dialogFormVisible: false,\n        loadProgress: 0, // 动态显示进度条\n        progressFlag: false, // 关闭进度条,\n        progressMap: {}\n      };\n    },\n    created() {},\n    mounted() {},\n    methods: {\n      show() {\n        this.dialogFormVisible = true;\n      },\n      close() {\n        this.dialogFormVisible = false;\n        this.$refs[\"excelImport\"].clearFiles();\n      },\n      // 下载模版\n      downloadExcelTemplate() {\n        doDownloadTemplate();\n      },\n      // 上传成功\n      onSuccess(response, file, fileList){\n        this.successProcess(file.uid);\n        this.$emit(\"fetchData\");\n      },\n      // 上传失败\n      onError(err, file, fileList){\n        this.errorProcess(file.uid);\n      },\n      // 进度条\n      onProcess(event, file, fileList) {\n        file.status = \'uploading\';\n        file.percentage = 0;\n        this.progressMap[file.uid] = {\n          file: file,\n        }\n        this.autoLoadingProcess(file.uid);\n      },\n\n      // 导入文件限制验证\n      beforeUpload(file) {\n        let testMsg = file.name.substring(file.name.lastIndexOf(\'.\')+1)\n        const extension = testMsg === \'xls\'\n        const extension2 = testMsg === \'xlsx\'\n        const isLt2M = file.size / 1024 / 1024 < 5\n        if(!extension && !extension2) {\n          this.$baseMessage(\'上传文件只能是 xlsxlsx格式!\', \"warning\");\n        }\n        if(!isLt2M) {\n          this.$baseMessage(\'上传文件大小不能超过 5MB!\', \"warning\");\n        }\n        return (extension || extension2) && isLt2M\n      },\n      // 自定义导入\n      handleImport(params){\n        if(!isNull(params)){\n          let blobObject = new Blob([params.file]);\n          let formData = new window.FormData()\n          formData.append(\"file\", blobObject);\n          const ret = doImportExcel(formData);\n          ret.then((v) => {\n            const {msg,data} = v;\n            this.$baseMessage(msg, \"success\");\n            // 成功\n            params.onSuccess();\n          }).catch( (e) =>{\n            // 失败\n            params.onError();\n          });\n          // 上传进度\n          params.onProgress();\n        }else{\n          params.onError();\n        }\n      },\n\n      // ==============\n\n      successProcess(fileUid) {\n        let tmp = this.progressMap[fileUid];\n        if(tmp !== null && tmp !== undefined){\n          try {\n            window.clearTimeout(tmp.timer);\n          }catch (e){}\n          tmp.file.status = \'success\';\n          tmp.file.percentage = 100;\n          delete this.progressMap[fileUid];\n        }\n      },\n      errorProcess(fileUid) {\n        let tmp = this.progressMap[fileUid];\n        if(tmp !== null && tmp !== undefined){\n          try {\n            window.clearTimeout(tmp.timer);\n          }catch (e){}\n          tmp.file.status = \'fail\';\n          delete this.progressMap[fileUid];\n        }\n      },\n      autoLoadingProcess(fileUid) {\n        const that = this;\n        let tmp = this.progressMap[fileUid];\n        if(tmp !== null && tmp !== undefined){\n          if(tmp.file.percentage >= 99) {\n            try {\n              window.clearTimeout(tmp.timer);\n            }catch (e){}\n          }else {\n            // 如果大于 99 则 停止\n            if(tmp.file.percentage + random(1, 12) > 99){\n              tmp.file.percentage = 99;\n            }else{\n              // 进度随机增长 1 - 12\n              tmp.file.percentage += random(1, 12);\n            }\n\n            // 递归增加百分比 递归时间为 随机 1-5秒\n            tmp.timer = window.setTimeout(function (){\n              that.autoLoadingProcess(fileUid);\n            }, random(1000, 5000));\n          }\n        }\n      },\n\n    },\n  };\n</script>\n', '0', 0, 1, '2025-06-02 17:30:27', 1, '2025-06-02 17:30:27');
INSERT INTO `gen_template_detail` VALUES (1929470657053372417, 1398253704724828162, '1', 'src/views/modules/${moduleName}/${subModuleName}', 'index.vue', '<template>\n  <div class=\"tenantManagement-container\">\n\n    <el-collapse-transition>\n    <div class=\"more-query\" v-show=\"this.moreQueryFlag\">\n      <!-- 更多查找 -->\n      <vab-query-form>\n        <vab-query-form-left-panel :span=\"24\">\n          <el-form :inline=\"true\" :model=\"queryForm\" @submit.native.prevent>\n            #for(column : data.model.moreQueryList)\n\n            ### 字典\n            #if(column.showType == \"2\")\n            <el-form-item>\n              <el-select v-model=\"queryForm.#(column.fieldHumpName+\'_\'+column.queryType)\" placeholder=\"请选择#(column.fieldComments)\" clearable style=\"width: 100%\">\n                <el-option\n                      v-for=\"item in dict.#(column.dictTypeCode)\"\n                      :key=\"item.dictValue\"\n                      :label=\"item.dictName\"\n                      :value=\"item.dictValue\"\n                ></el-option>\n              </el-select>\n            </el-form-item>\n            #else if(column.showType == \"3\")\n            ### 时间\n            <el-form-item>\n              <el-date-picker\n                      v-model=\"#(column.fieldHumpName)DatePicker\"\n                      type=\"datetimerange\"\n                      :picker-options=\"pickerOptions\"\n                      range-separator=\"至\"\n                      start-placeholder=\"开始#(column.fieldComments)\"\n                      end-placeholder=\"结束#(column.fieldComments)\"\n                      align=\"right\">\n              </el-date-picker>\n            </el-form-item>\n            #else if(column.showType == \"4\")\n            ### 日期\n            <el-form-item>\n              <el-date-picker\n                      v-model=\"#(column.fieldHumpName)DatePicker\"\n                      type=\"daterange\"\n                      align=\"right\"\n                      range-separator=\"至\"\n                      start-placeholder=\"开始#(column.fieldComments)\"\n                      end-placeholder=\"结束#(column.fieldComments)\"\n              ></el-date-picker>\n            </el-form-item>\n            #else\n            #if(column.queryType == \"EQ\" || column.queryType == \"LIKE\")\n            <el-form-item>\n              <el-input\n                      v-model.trim=\"queryForm.#(column.fieldHumpName)_#(column.queryType)\"\n                      placeholder=\"请输入#(column.fieldComments)\"\n                      clearable\n              />\n            </el-form-item>\n            #else if(column.queryType == \"RANGE\")\n            <el-col :span=\"12\" >\n            <el-form-item style=\"text-align: center\">\n              <el-input\n                      v-model.trim=\"queryForm.#(column.fieldHumpName)_BEGIN\"\n                      placeholder=\"#(column.fieldComments)开始\"\n                      clearable\n                      style=\"float: left;width: calc(50% - 6px)\"\n              />\n              <div style=\"float:left;width: 12px\">-</div>\n              <el-input\n                      v-model.trim=\"queryForm.#(column.fieldHumpName)_END\"\n                      placeholder=\"#(column.fieldComments)结束\"\n                      clearable\n                      style=\"float: right;width: calc(50% - 6px)\"\n              />\n            </el-form-item>\n            </el-col>\n            #end\n            #end\n            #end\n\n\n          </el-form>\n        </vab-query-form-left-panel>\n\n      </vab-query-form>\n      <el-divider></el-divider>\n    </div>\n    </el-collapse-transition>\n\n    <!-- 主要操作  -->\n    <vab-query-form>\n      <vab-query-form-left-panel :span=\"10\">\n        <el-button\n            #if(data.subModuleName != null && data.subModuleName != \"\")\n            v-if=\"$perms(\'#(data.moduleName.toLowerCase())_#(data.subModuleName.toLowerCase())_insert\')\"\n            #else\n            v-if=\"$perms(\'#(data.moduleName.toLowerCase())_insert\')\"\n            #end\n            icon=\"el-icon-plus\"\n            type=\"primary\"\n            @click=\"handleInsert\"\n        > 添加 </el-button>\n\n        <el-button\n            #if(data.subModuleName != null && data.subModuleName != \"\")\n            v-if=\"$perms(\'#(data.moduleName.toLowerCase())_#(data.subModuleName.toLowerCase())_import\')\"\n            #else\n            v-if=\"$perms(\'#(data.moduleName.toLowerCase())_import\')\"\n            #end\n            icon=\"el-icon-upload2\"\n            type=\"warning\"\n            @click=\"handleImportExcel\"\n        > 导入 </el-button>\n\n        <el-button\n            #if(data.subModuleName != null && data.subModuleName != \"\")\n            v-if=\"$perms(\'#(data.moduleName.toLowerCase())_#(data.subModuleName.toLowerCase())_export\')\"\n            #else\n            v-if=\"$perms(\'#(data.moduleName.toLowerCase())_export\')\"\n            #end\n            icon=\"el-icon-download\"\n            type=\"warning\"\n            @click=\"handleExportExcel\"\n        > 导出 </el-button>\n\n        <el-button\n            #if(data.subModuleName != null && data.subModuleName != \"\")\n            v-if=\"$perms(\'#(data.moduleName.toLowerCase())_#(data.subModuleName.toLowerCase())_delete\')\"\n            #else\n            v-if=\"$perms(\'#(data.moduleName.toLowerCase())_delete\')\"\n            #end\n            :disabled=\"!selectRows.length > 0\"\n            icon=\"el-icon-delete\"\n            type=\"danger\"\n            @click=\"handleDelete\"\n        > 批量删除 </el-button>\n\n      </vab-query-form-left-panel>\n      <vab-query-form-right-panel :span=\"14\">\n        <el-form :inline=\"true\" :model=\"queryForm\" @submit.native.prevent>\n          ### 代码生成器 简要只展示2个\n          #for(column : data.model.briefQueryList)\n\n          ### 字典\n          #if(column.showType == \"2\")\n          <el-form-item>\n            <el-select v-model=\"queryForm.#(column.fieldHumpName+\'_\'+column.queryType)\" placeholder=\"请选择#(column.fieldComments)\" clearable style=\"width: 100%\">\n              <el-option\n                      v-for=\"item in dict.#(column.dictTypeCode)\"\n                      :key=\"item.dictValue\"\n                      :label=\"item.dictName\"\n                      :value=\"item.dictValue\"\n              ></el-option>\n            </el-select>\n          </el-form-item>\n          #else if(column.showType == \"3\")\n          ### 时间\n          <el-form-item>\n            <el-date-picker\n                    v-model=\"#(column.fieldHumpName)DatePicker\"\n                    type=\"datetimerange\"\n                    :picker-options=\"pickerOptions\"\n                    range-separator=\"至\"\n                    start-placeholder=\"开始#(column.fieldComments)\"\n                    end-placeholder=\"结束#(column.fieldComments)\"\n                    align=\"right\">\n            </el-date-picker>\n          </el-form-item>\n          #else if(column.showType == \"4\")\n          ### 日期\n          <el-form-item>\n            <el-date-picker\n                    v-model=\"#(column.fieldHumpName)DatePicker\"\n                    type=\"daterange\"\n                    align=\"right\"\n                    range-separator=\"至\"\n                    start-placeholder=\"开始#(column.fieldComments)\"\n                    end-placeholder=\"结束#(column.fieldComments)\"\n            ></el-date-picker>\n          </el-form-item>\n          #else\n          #if(column.queryType == \"EQ\" || column.queryType == \"LIKE\")\n          <el-form-item>\n            <el-input\n                    v-model.trim=\"queryForm.#(column.fieldHumpName)_#(column.queryType)\"\n                    placeholder=\"请输入#(column.fieldComments)\"\n                    clearable\n            />\n          </el-form-item>\n          #else if(column.queryType == \"RANGE\")\n          <el-col :span=\"12\" >\n          <el-form-item style=\"text-align: center\">\n            <el-input\n                    v-model.trim=\"queryForm.#(column.fieldHumpName)_BEGIN\"\n                    placeholder=\"#(column.fieldComments)开始\"\n                    clearable\n                    style=\"float: left;width: calc(50% - 6px)\"\n            />\n            <div style=\"float:left;width: 12px\">-</div>\n            <el-input\n                    v-model.trim=\"queryForm.#(column.fieldHumpName)_END\"\n                    placeholder=\"#(column.fieldComments)结束\"\n                    clearable\n                    style=\"float: right;width: calc(50% - 6px)\"\n            />\n          </el-form-item>\n          </el-col>\n          #end\n          #end\n          #end\n\n          <el-form-item>\n            <el-button icon=\"el-icon-search\" type=\"primary\" @click=\"queryData\">\n              查询\n            </el-button>\n\n            #if(data.model.moreQueryList != null && data.model.moreQueryList.size() > 0)\n            <el-button icon=\"el-icon-search\" @click=\"moreQuery\">\n              更多\n            </el-button>\n            #end\n\n          </el-form-item>\n        </el-form>\n      </vab-query-form-right-panel>\n    </vab-query-form>\n\n    <el-table\n      v-loading=\"listLoading\"\n      :data=\"list\"\n      :element-loading-text=\"elementLoadingText\"\n      @selection-change=\"setSelectRows\"\n    >\n      <el-table-column show-overflow-tooltip type=\"selection\"></el-table-column>\n\n      <el-table-column show-overflow-tooltip label=\"序号\" width=\"95\">\n        <template slot-scope=\"scope\">\n          {{(queryForm.pageNo - 1) * queryForm.pageSize + scope.$index + 1}}\n        </template>\n      </el-table-column>\n\n      #for(column : data.model.columnList)\n      ### 字典\n      #if(column.showType == \"2\" && column.izShowList == \"1\")\n      <el-table-column\n              show-overflow-tooltip\n              prop=\"#(column.fieldHumpName)\"\n              label=\"#(column.fieldComments)\"\n      >\n\n        <template slot-scope=\"scope\">\n          <span>\n            {{ $getDictNameByValue(\'#(column.dictTypeCode)\', scope.row.#(column.fieldHumpName)) }}\n          </span>\n        </template>\n\n      </el-table-column>\n\n      #else\n      #if(column.izShowList == \"1\")\n      <el-table-column\n              show-overflow-tooltip\n              prop=\"#(column.fieldHumpName)\"\n              label=\"#(column.fieldComments)\"\n      ></el-table-column>\n\n      #end\n      #end\n      #end\n\n      <el-table-column\n        show-overflow-tooltip\n        label=\"操作\"\n        width=\"200\"\n        #if(data.subModuleName != null && data.subModuleName != \"\")\n        v-if=\"$perms(\'#(data.moduleName.toLowerCase())_#(data.subModuleName.toLowerCase())_update\') || $perms(\'#(data.moduleName.toLowerCase())_#(data.subModuleName.toLowerCase())_delete\')\"\n        #else\n        v-if=\"$perms(\'#(data.moduleName.toLowerCase())_update\') || $perms(\'#(data.moduleName.toLowerCase())_delete\')\"\n        #end\n      >\n        <template v-slot=\"scope\">\n          <el-button\n            #if(data.subModuleName != null && data.subModuleName != \"\")\n            v-if=\"$perms(\'#(data.moduleName.toLowerCase())_#(data.subModuleName.toLowerCase())_update\')\"\n            #else\n            v-if=\"$perms(\'#(data.moduleName.toLowerCase())_update\')\"\n            #end\n            type=\"text\"\n            @click=\"handleUpdate(scope.row)\"\n          > 编辑 </el-button>\n          \n          <el-divider direction=\"vertical\"></el-divider>\n          \n          <el-button\n            #if(data.subModuleName != null && data.subModuleName != \"\")\n            v-if=\"$perms(\'#(data.moduleName.toLowerCase())_#(data.subModuleName.toLowerCase())_delete\')\"\n            #else\n            v-if=\"$perms(\'#(data.moduleName.toLowerCase())_delete\')\"\n            #end\n            type=\"text\"\n            @click=\"handleDelete(scope.row)\"\n          > 删除 </el-button>\n        </template>\n\n      </el-table-column>\n    </el-table>\n    <el-pagination\n      background\n      :current-page=\"queryForm.pageNo\"\n      :page-size=\"queryForm.pageSize\"\n      :layout=\"layout\"\n      :total=\"total\"\n      @size-change=\"handleSizeChange\"\n      @current-change=\"handleCurrentChange\"\n    ></el-pagination>\n\n    <edit ref=\"edit\" @fetchData=\"fetchData\"></edit>\n    <import ref=\"import\" @fetchData=\"fetchData\" ></import>\n\n  </div>\n</template>\n\n<script>\n  #if(data.subModuleName != null && data.subModuleName != \"\")\n  import { getList, doDelete, doDeleteAll, doExportExcel } from \"@/api/#(data.moduleName)/#(data.subModuleName)/#(data.model.tableHumpName)ManagementApi\";\n  #else\n  import { getList, doDelete, doDeleteAll, doExportExcel } from \"@/api/#(data.moduleName)/#(data.model.tableHumpName)ManagementApi\";\n  #end\n  import Edit from \"./components/#(data.model.tableHumpName)ManagementEdit\";\n  import Import from \"./components/#(data.model.tableHumpName)ManagementImport\";\n\n  import { vueButtonClickBan } from \"@/utils\";\n  import { isNotNull } from \"@/utils/valiargs\";\n  import { formateDate } from \"@/utils/format\";\n\n  export default {\n    name: \"#(data.model.tableHumpName)Management\",\n    components: { Edit, Import },\n    data() {\n      return {\n        list: null,\n        listLoading: true,\n        layout: \"total, prev, pager, next, sizes, jumper\",\n        total: 0,\n        selectRows: \"\",\n        elementLoadingText: \"正在加载...\",\n        moreQueryFlag: false,\n        queryForm: {\n          pageNo: 1,\n          pageSize: 10,\n          ### 代码生成器 简要2个\n          #for(column : data.model.briefQueryList)\n          ### 字典\n          #if(column.showType == \"2\")\n          #(column.fieldHumpName)_EQ: \"\",\n          #else if(column.showType == \"3\" || column.showType == \"4\")\n          ### 日期\n          #(column.fieldHumpName)_BEGIN: \"\",\n          #(column.fieldHumpName)_END: \"\",\n          #else\n          #if(column.queryType == \"EQ\" || column.queryType == \"LIKE\")\n          #(column.fieldHumpName)_#(column.queryType): \"\",\n          #else if(column.queryType == \"RANGE\")\n          #(column.fieldHumpName)_BEGIN: \"\",\n          #(column.fieldHumpName)_END: \"\",\n          #end\n          #end\n          #end\n          ### 代码生成器 更多\n          #for(column : data.model.moreQueryList)\n          ### 字典\n          #if(column.showType == \"2\")\n          #(column.fieldHumpName)_EQ: \"\",\n          #else if(column.showType == \"3\" || column.showType == \"4\")\n          ### 日期\n          #(column.fieldHumpName)_BEGIN: \"\",\n          #(column.fieldHumpName)_END: \"\",\n          #else\n          #if(column.queryType == \"EQ\" || column.queryType == \"LIKE\")\n          #(column.fieldHumpName)_#(column.queryType): \"\",\n          #else if(column.queryType == \"RANGE\")\n          #(column.fieldHumpName)_BEGIN: \"\",\n          #(column.fieldHumpName)_END: \"\",\n          #end\n          #end\n          #end\n        },\n        ### 代码生成器 简要2个\n        #for(column : data.model.briefQueryList)\n        ### 日期\n        #if(column.showType == \"3\" || column.showType == \"4\")\n        #(column.fieldHumpName)DatePicker: [],\n        #end\n        #end\n        ### 代码生成器 更多\n        #for(column : data.model.moreQueryList)\n        ### 日期\n        #if(column.showType == \"3\" || column.showType == \"4\")\n        #(column.fieldHumpName)DatePicker: [],\n        #end\n        #end\n        dict:{},\n        pickerOptions: {\n          shortcuts: [{\n            text: \'最近一周\',\n            onClick(picker) {\n              const end = new Date();\n              const start = new Date();\n              start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);\n              picker.$emit(\'pick\', [start, end]);\n            }\n          }, {\n            text: \'最近一个月\',\n            onClick(picker) {\n              const end = new Date();\n              const start = new Date();\n              start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);\n              picker.$emit(\'pick\', [start, end]);\n            }\n          }, {\n            text: \'最近三个月\',\n            onClick(picker) {\n              const end = new Date();\n              const start = new Date();\n              start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);\n              picker.$emit(\'pick\', [start, end]);\n            }\n          }]\n        },\n      };\n    },\n    created() {\n      this.fetchData();\n    },\n    mounted() {\n      #for(column : data.model.columnList)\n      #if(column.dictTypeCode != null && column.dictTypeCode != \"\")\n      this.dict.#(column.dictTypeCode) = this.$getDictList(\"#(column.dictTypeCode)\");\n      #end\n      #end\n    },\n    methods: {\n      setSelectRows(val) {\n        this.selectRows = val;\n      },\n      handleInsert(row) {\n        this.$refs[\"edit\"].showEdit();\n      },\n      handleUpdate(row) {\n        if (row.id) {\n          this.$refs[\"edit\"].showEdit(row);\n        }\n      },\n      handleDelete(row) {\n        if (row.id) {\n          this.$baseConfirm(\"你确定要删除当前项吗\", null, async () => {\n            const { msg } = await doDelete({ id: row.id });\n            this.$baseMessage(msg, \"success\");\n            await this.fetchData();\n          });\n        } else {\n          if (this.selectRows.length > 0) {\n            const ids = this.selectRows.map((item) => item.id).join();\n            this.$baseConfirm(\"你确定要删除选中项吗\", null, async () => {\n              const { msg } = await doDeleteAll({ ids });\n              this.$baseMessage(msg, \"success\");\n              await this.fetchData();\n            });\n          } else {\n            this.$baseMessage(\"未选中任何行\", \"error\");\n            return false;\n          }\n        }\n      },\n      // 导出excel\n      handleExportExcel(el){\n        // 导出按钮防抖处理 默认限制为10秒\n        vueButtonClickBan(el, 10);\n\n        // 执行导出\n        doExportExcel(this.queryForm);\n      },\n      // 导入excel\n      handleImportExcel(){\n        this.$refs[\"import\"].show();\n      },\n\n\n      handleSizeChange(val) {\n        this.queryForm.pageSize = val;\n        this.fetchData();\n      },\n      handleCurrentChange(val) {\n        this.queryForm.pageNo = val;\n        this.fetchData();\n      },\n      moreQuery(){\n        this.moreQueryFlag = !this.moreQueryFlag;\n      },\n      queryData() {\n        ### 代码生成器 简要2个\n        #for(column : data.model.briefQueryList)\n        ### 日期\n        #if(column.showType == \"3\" || column.showType == \"4\")\n        if(isNotNull(this.#(column.fieldHumpName)DatePicker) && this.#(column.fieldHumpName)DatePicker.length === 2){\n          this.queryForm.#(column.fieldHumpName)_BEGIN =\n                  this.#(column.fieldHumpName)DatePicker.length === 0 ? \"\" : formateDate(this.#(column.fieldHumpName)DatePicker[0], \'yyyy-MM-dd hh:mm:ss\');\n          this.queryForm.#(column.fieldHumpName)_END =\n                  this.#(column.fieldHumpName)DatePicker.length === 0 ? \"\" : formateDate(this.#(column.fieldHumpName)DatePicker[1], \'yyyy-MM-dd hh:mm:ss\');\n        }else{\n          this.queryForm.#(column.fieldHumpName)_BEGIN = \"\";\n          this.queryForm.#(column.fieldHumpName)_END = \"\";\n        }        #end\n        #end\n        ### 代码生成器 更多\n        #for(column : data.model.moreQueryList)\n        ### 日期\n        #if(column.showType == \"3\" || column.showType == \"4\")\n        if(isNotNull(this.#(column.fieldHumpName)DatePicker) && this.#(column.fieldHumpName)DatePicker.length === 2){\n          this.queryForm.#(column.fieldHumpName)_BEGIN =\n                  this.#(column.fieldHumpName)DatePicker.length === 0 ? \"\" : formateDate(this.#(column.fieldHumpName)DatePicker[0], \'yyyy-MM-dd hh:mm:ss\');\n          this.queryForm.#(column.fieldHumpName)_END =\n                  this.#(column.fieldHumpName)DatePicker.length === 0 ? \"\" : formateDate(this.#(column.fieldHumpName)DatePicker[1], \'yyyy-MM-dd hh:mm:ss\');\n        }else{\n          this.queryForm.#(column.fieldHumpName)_BEGIN = \"\";\n          this.queryForm.#(column.fieldHumpName)_END = \"\";\n        }\n        #end\n        #end\n\n        this.queryForm.pageNo = 1;\n        this.fetchData();\n      },\n      async fetchData() {\n        this.listLoading = true;\n        const { data } = await getList(this.queryForm);\n        if(isNotNull(data)){\n          this.list = data.rows;\n          this.total = data.total;\n        }\n        setTimeout(() => {\n            this.listLoading = false;\n        }, 300);\n      },\n    },\n  };\n</script>\n', '0', 0, 1, '2025-06-02 17:30:27', 1, '2025-06-02 17:30:27');

清空当前Redis缓存

redis-client -> flushdb

4.7 其他

如何还有疏漏 请联系群主即时补充

五、加油快Run起来了


     ____     ____    _____    __     ____
    / __ \   / __ \  / ___/   / /    /  _/
   / / / /  / /_/ /  \__ \   / /     / /
  / /_/ /  / ____/  ___/ /  / /___ _/ /
  \____/  /_/      /____/  /_____//___/


:: opsli   :: Running Spring Boot 3.4.6 ::
:: https://www.opsli.com ::
:: OPSLI 快速开发平台 ::
----------------------------------------------------------
OPSLI 快速开发平台 框架启动成功! 相关URLs:
项目地址:               http://127.0.0.1:7000/opsli-boot/
Doc文档:               http://127.0.0.1:7000/opsli-boot/doc.html
----------------------------------------------------------
文档更新时间: 2025-06-09 16:42   作者:超级管理员