[
  {
    "path": ".gitignore",
    "content": "######################################################################\n# Build Tools\n\n.gradle\n/build/\n!gradle/wrapper/gradle-wrapper.jar\n\ntarget/\n!.mvn/wrapper/maven-wrapper.jar\n\n######################################################################\n# IDE\n\n### STS ###\n.apt_generated\n.classpath\n.factorypath\n.project\n.settings\n.springBeans\n\n### IntelliJ IDEA ###\n.idea\n*.iws\n*.iml\n*.ipr\n\n### NetBeans ###\nnbproject/private/\nbuild/*\nnbbuild/\ndist/\nnbdist/\n.nb-gradle/\n\n######################################################################\n# Others\n*.log\n*.xml.versionsBackup\n*.swp\n\n!*/build/*.java\n!*/build/*.html\n!*/build/*.xml\n\n/files/"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\r\n\r\nCopyright (c) 2023 小太阳\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in all\r\ncopies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\nSOFTWARE.\r\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n\t<img alt=\"logo\" src=\"minimalist-vue3/src/assets/logo.png\" width=\"80px\" height=\"80px\">\n</div>\n<div  align=\"center\">\n    <h1>极简多租户管理系统</h1>\n    <span>基于SpringBoot3+Vue3前后端分离的Java快速开发脚手架</span>\n</div>\n\n## 项目简介\n\n极简多租户管理系统是一个多租户管理系统，基于SpringBoot3+Vue3的前后端分离的后台开发脚手架，具备一些常用的基础功能。\n## 项目地址\n\n演示地址：[https://www.jjian.com.cn](https://www.jjian.com.cn)\n\n文档地址：[https://apebbs.cn/docs/minimalist-saas/1.0.0/introduction.html](https://apebbs.cn/docs/minimalist-saas/1.0.0/introduction.html)\n\nGitee：[https://gitee.com/marlife/minimalist-saas](https://gitee.com/marlife/minimalist-saas)\n\nGithub：[https://github.com/lmq2582609/minimalist-saas](https://github.com/lmq2582609/minimalist-saas)\n\n管理员账号/密码： `admin`/`111111`  \n租户账号/密码：`dongdong`/`111111`  \n\n## 技术选型\n### 前端\n\n| 名称          | 版本     | 说明                      |\n| ----------- | ------ | ----------------------- |\n| Vue         | 3.2.47 | 用于构建用户界面的 JavaScript 框架 |\n| Vite        | 4.3.2  | 前端构建工具                  |\n| Arco-Design | 2.45.3 | 字节出的 Vue3 UI 组件库        |\n| Windicss    | 3.5.6  | CSS 框架                  |\n| Vue-Router  | 4.1.6  | Vue官方路由                 |\n| Vueuse      | 10.1.2 | 基于Composition API的实用函数库 |\n| Axios       | 1.4.0  | 基于Promise的HTTP客户端       |\n| Pinia       | 2.0.36 | Vue 状态管理                |\n| Vue-Cropper | 1.0.9  | 图片裁剪                    |\n| Nprogress   | 0.2.0  | 进度条                     |\n| Tinymce     | 6.6.0  | 富文本编辑器                  |\n\n### 后端\n\n| 名称                         | 版本     | 说明                      |\n| -------------------------- | ------ | ----------------------- |\n| Java                       | 17     | 无需多言                    |\n| SpringBoot                 | 3.1.1  | Java开发框架                |\n| Redisson                   | 3.36.0 | Redis Java客户端           |\n| Mysql                      | 8.0.33 | Mysql数据库驱动              |\n| Mybatis-flex               | 1.9.7  | ORM框架                   |\n| Hutool                     | 5.8.32 | Java工具库                 |\n| Satoken                    | 1.39.0 | Java权限认证框架              |\n| Knife4j                    | 4.1.0  | Swagger2和OpenAPI3增强解决方案 |\n| Mica-xss                   | 3.3.2  | xss防护                   |\n## 中间件\n\n| 名称    | 版本     | 说明       |\n| ----- | ------ | -------- |\n| mysql | 8.0.24 | 关系型数据库   |\n| redis | 7.2.4  | NoSQL数据库 |\n## 演示图\n\n<table>\n    <tr>\n        <td><img src=\"resources/images/login-page.png\"/></td>\n        <td><img src=\"resources/images/index-page.png\"/></td>\n    </tr>\n    <tr>\n        <td><img src=\"resources/images/user-page.png\"/></td>\n        <td><img src=\"resources/images/role-page.png\"/></td>\n    </tr>\n    <tr>\n        <td><img src=\"resources/images/perm-page.png\"/></td>\n        <td><img src=\"resources/images/dept-page.png\"/></td>\n    </tr>\n    <tr>\n        <td><img src=\"resources/images/post-page.png\"/></td>\n        <td><img src=\"resources/images/dict-page.png\"/></td>\n    </tr>\n    <tr>\n        <td><img src=\"resources/images/file-page.png\"/></td>\n        <td><img src=\"resources/images/package-page.png\"/></td>\n    </tr>\n    <tr>\n        <td><img src=\"resources/images/tenant-page.png\"/></td>\n        <td><img src=\"resources/images/notice-page.png\"/></td>\n    </tr>\n    <tr>\n        <td><img src=\"resources/images/config-page.png\"/></td>\n        <td><img src=\"resources/images/storage-page.png\"/></td>\n    </tr>\n    <tr>\n        <td><img src=\"resources/images/user-pro-page.png\"/></td>\n        <td><img src=\"resources/images/swagger-page.png\"/></td>\n    </tr>\n</table>\n"
  },
  {
    "path": "minimalist-application/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd\">\n    <parent>\n        <groupId>com.cn</groupId>\n        <artifactId>minimalist-saas</artifactId>\n        <version>1.0.0-SNAPSHOT</version>\n    </parent>\n\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>minimalist-application</artifactId>\n    <name>minimalist-application Maven Webapp</name>\n    <description>启动</description>\n    <packaging>jar</packaging>\n\n    <dependencies>\n\n        <dependency>\n            <groupId>com.cn</groupId>\n            <artifactId>minimalist-basic</artifactId>\n            <version>1.0.0-SNAPSHOT</version>\n        </dependency>\n\n    </dependencies>\n\n    <build>\n        <!-- 项目打包名称 -->\n        <finalName>${project.package.name}</finalName>\n        <plugins>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n                <executions>\n                    <execution>\n                        <goals>\n                            <!--创建一个自动可执行的jar或war文件 -->\n                            <goal>repackage</goal>\n                        </goals>\n                    </execution>\n                </executions>\n                <configuration>\n                    <!-- 打包指定启动类 -->\n                    <mainClass>com.minimalist.application.MinimalistBasicApplication</mainClass>\n                    <!-- 打包时忽略的包 -->\n                    <excludes>\n                        <exclude>\n                            <groupId>org.projectlombok</groupId>\n                            <artifactId>lombok</artifactId>\n                        </exclude>\n                    </excludes>\n                </configuration>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <configuration>\n                    <source>${maven.compiler.source}</source>\n                    <target>${maven.compiler.target}</target>\n                    <encoding>${project.package.encoding}</encoding>\n                    <compilerArgs>--enable-preview</compilerArgs>\n                </configuration>\n            </plugin>\n        </plugins>\n\n        <resources>\n            <resource>\n                <directory>src/main/resources</directory>\n                <filtering>true</filtering>\n            </resource>\n        </resources>\n    </build>\n\n    <!-- 项目中依赖的包从哪里下载 -->\n    <repositories>\n        <repository>\n            <id>spring-milestones</id>\n            <name>Spring Milestones</name>\n            <url>https://repo.spring.io/milestone</url>\n            <snapshots>\n                <enabled>false</enabled>\n            </snapshots>\n        </repository>\n    </repositories>\n\n    <!-- 项目中所使用的maven插件从哪里下载 -->\n    <pluginRepositories>\n        <pluginRepository>\n            <id>spring-milestones</id>\n            <name>Spring Milestones</name>\n            <url>https://repo.spring.io/milestone</url>\n            <snapshots>\n                <enabled>false</enabled>\n            </snapshots>\n        </pluginRepository>\n    </pluginRepositories>\n\n    <profiles>\n        <!-- 开发环境配置文件 -->\n        <profile>\n            <id>dev</id>\n            <activation>\n                <!--默认激活dev 环境-->\n                <activeByDefault>true</activeByDefault>\n            </activation>\n            <properties>\n                <!-- spring.profiles.active是在application.yml自定义的字段(名字随便起)，自定义字段可以有多个，能对应上就行-->\n                <spring.profiles.active>dev</spring.profiles.active>\n            </properties>\n        </profile>\n\n        <!-- 生产环境配置文件 -->\n        <profile>\n            <id>prod</id>\n            <properties>\n                <spring.profiles.active>prod</spring.profiles.active>\n            </properties>\n        </profile>\n    </profiles>\n\n</project>\n"
  },
  {
    "path": "minimalist-application/src/main/java/com/minimalist/application/MinimalistBasicApplication.java",
    "content": "package com.minimalist.application;\n\nimport org.mybatis.spring.annotation.MapperScan;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\n\n@MapperScan(\"com.minimalist.**.mapper\")\n@SpringBootApplication(scanBasePackages = {\"com.minimalist.*\", \"cn.hutool.extra.spring\"})\npublic class MinimalistBasicApplication {\n\n    public static void main(String[] args) {\n        SpringApplication.run(MinimalistBasicApplication.class, args);\n    }\n\n}\n"
  },
  {
    "path": "minimalist-application/src/main/java/com/minimalist/application/config/GlobalExceptionHandler.java",
    "content": "package com.minimalist.application.config;\n\nimport cn.dev33.satoken.exception.NotLoginException;\nimport cn.dev33.satoken.exception.NotPermissionException;\nimport com.minimalist.basic.config.exception.BusinessException;\nimport com.minimalist.basic.entity.enums.RespEnum;\nimport jakarta.validation.ConstraintViolation;\nimport jakarta.validation.ConstraintViolationException;\nimport lombok.extern.slf4j.Slf4j;\nimport net.dreamlu.mica.xss.core.XssException;\nimport org.springframework.core.NestedExceptionUtils;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.http.converter.HttpMessageNotReadableException;\nimport org.springframework.validation.BindingResult;\nimport org.springframework.validation.FieldError;\nimport org.springframework.web.bind.MethodArgumentNotValidException;\nimport org.springframework.web.bind.annotation.ExceptionHandler;\nimport org.springframework.web.bind.annotation.RestControllerAdvice;\nimport org.springframework.web.method.annotation.MethodArgumentConversionNotSupportedException;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.StringJoiner;\n\n/**\n * 全局异常处理\n */\n@Slf4j\n@RestControllerAdvice\npublic class GlobalExceptionHandler {\n\n    /**\n     * 参数异常\n     * @param e 异常\n     * @return 响应\n     */\n    @ExceptionHandler(value = {IllegalArgumentException.class, MethodArgumentNotValidException.class, ConstraintViolationException.class})\n    public ResponseEntity<Object> paramsException(Exception e) {\n        if (e instanceof IllegalArgumentException) {\n            log.warn(e.getMessage(), e);\n            return ResponseEntity.status(RespEnum.PARAM_ERROR.getCode()).body(RespEnum.PARAM_ERROR.getDesc());\n        } else if (e instanceof MethodArgumentNotValidException) {\n            //参数异常处理\n            log.warn(e.getMessage(), e);\n            BindingResult bindingResult = ((MethodArgumentNotValidException) e).getBindingResult();\n            List<FieldError> errors = bindingResult.getFieldErrors();\n            StringJoiner sj = new StringJoiner(\"，\");\n            errors.forEach(error -> sj.add(error.getDefaultMessage()));\n            return ResponseEntity.status(RespEnum.PARAM_ERROR.getCode()).body(sj.toString());\n        } else if (e instanceof ConstraintViolationException) {\n            //参数异常处理\n            log.warn(e.getMessage(), e);\n            Set<ConstraintViolation<?>> constraintViolations = ((ConstraintViolationException) e).getConstraintViolations();\n            StringJoiner sj = new StringJoiner(\"，\");\n            constraintViolations.forEach(c -> sj.add(c.getMessageTemplate()));\n            return ResponseEntity.status(RespEnum.PARAM_ERROR.getCode()).body(sj.toString());\n        } else {\n            return exception(e);\n        }\n    }\n\n    /**\n     * 自定义异常\n     * @param e 异常\n     * @return 响应\n     */\n    @ExceptionHandler(value = {BusinessException.class})\n    public ResponseEntity<Object> customException(Exception e) {\n        //业务异常\n        if (e instanceof BusinessException) {\n            //记录日志\n            log(e);\n            //响应 业务异常\n            return ResponseEntity.status(((BusinessException) e).getCode()).body(e.getMessage());\n        } else {\n            return exception(e);\n        }\n    }\n\n    /**\n     * 未登录异常处理\n     */\n    @ExceptionHandler(value = {NotLoginException.class})\n    public ResponseEntity<Object> customNotLoginException(Exception e) {\n        return ResponseEntity.status(RespEnum.REQUEST_UNAUTH.getCode()).body(RespEnum.REQUEST_UNAUTH.getDesc());\n    }\n\n    /**\n     * 无权限异常处理\n     */\n    @ExceptionHandler(value = {NotPermissionException.class})\n    public ResponseEntity<Object> customNotPermissionException(Exception e) {\n        return ResponseEntity.status(RespEnum.NO_OPERATION_PERMISSION.getCode()).body(RespEnum.NO_OPERATION_PERMISSION.getDesc());\n    }\n\n    /**\n     * XSS异常\n     * @param e 异常\n     * @return 响应\n     */\n    @ExceptionHandler(value = {HttpMessageNotReadableException.class, MethodArgumentConversionNotSupportedException.class})\n    public ResponseEntity<Object> xssException(Exception e) {\n        Throwable rootCause = NestedExceptionUtils.getRootCause(e);\n        if (rootCause instanceof XssException) {\n            //记录日志\n            log(e);\n            return ResponseEntity.status(RespEnum.PARAM_XSS_ERROR.getCode()).body(RespEnum.PARAM_XSS_ERROR.getDesc());\n        } else {\n            return exception(e);\n        }\n    }\n\n    /**\n     * 未知异常\n     * @param e 异常\n     * @return 响应\n     */\n    @ExceptionHandler(value = {Exception.class})\n    public ResponseEntity<Object> exception(Exception e) {\n        //记录日志\n        log(e);\n        //响应 系统异常\n        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(RespEnum.FAILED.getDesc());\n    }\n\n    private void log(Exception e) {\n        //记录日志\n        log.error(e.getMessage(), e);\n        //邮件、通知、告警\n\n    }\n\n}\n"
  },
  {
    "path": "minimalist-application/src/main/java/com/minimalist/application/config/satoken/SaTokenConfigure.java",
    "content": "package com.minimalist.application.config.satoken;\n\nimport cn.dev33.satoken.interceptor.SaInterceptor;\nimport cn.dev33.satoken.router.SaHttpMethod;\nimport cn.dev33.satoken.router.SaRouter;\nimport cn.dev33.satoken.stp.StpUtil;\nimport com.minimalist.basic.config.exception.BusinessException;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.web.servlet.config.annotation.InterceptorRegistry;\nimport org.springframework.web.servlet.config.annotation.WebMvcConfigurer;\n\n@Configuration\npublic class SaTokenConfigure implements WebMvcConfigurer {\n\n    /**\n     * 注册 Sa-Token 拦截器\n     */\n    @Override\n    public void addInterceptors(InterceptorRegistry registry) {\n//        /** 演示环境，可以删除 ------- start */\n//        SaInterceptor saInterceptor = new SaInterceptor(handle -> {\n//            //校验登录\n//            StpUtil.checkLogin();\n//            // 根据请求类型匹配\n//            SaRouter.match(SaHttpMethod.POST, SaHttpMethod.DELETE, SaHttpMethod.PUT)\n//                    .check(() -> {\n//                        throw new BusinessException(\"演示环境，只能查询，不能进行操作\");\n//                    });\n//        });\n//        registry.addInterceptor(saInterceptor).addPathPatterns(\"/**\")\n//                .excludePathPatterns(\"/basic/user/logout\");\n//        /** 演示环境，可以删除 ------- end */\n\n\n        //上面演示环境删除后，下面的注视需要放开\n\n        //全局拦截配置，必须登陆后才可访问，如果某个接口需要跳过拦截，需要在接口上增加@SaIgnore注解\n        SaInterceptor saInterceptor = new SaInterceptor(handle -> StpUtil.checkLogin());\n        registry.addInterceptor(saInterceptor).addPathPatterns(\"/**\");\n    }\n\n}\n\n"
  },
  {
    "path": "minimalist-application/src/main/java/com/minimalist/application/config/satoken/SaTokenInterfaceImpl.java",
    "content": "package com.minimalist.application.config.satoken;\n\nimport cn.dev33.satoken.stp.StpInterface;\nimport cn.hutool.core.collection.CollectionUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.minimalist.basic.config.redis.RedisManager;\nimport com.minimalist.basic.utils.RedisKeyConstant;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Component;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n * sa-token，自定义权限加载接口实现类\n */\n@Component\npublic class SaTokenInterfaceImpl implements StpInterface {\n\n    @Autowired\n    private RedisManager redisManager;\n\n    /**\n     * 返回指定账号id所拥有的权限码集合，loginId即登陆时传入的UserId\n     * @param loginId 用户ID\n     * @param loginType 账号体系标识\n     * @return 权限编码集合\n     */\n    @Override\n    public List<String> getPermissionList(Object loginId, String loginType) {\n        //从redis获取权限数据，在请求getUserInfo接口时会set权限数据\n        Set<String> permCodes = redisManager.get(StrUtil.indexedFormat(RedisKeyConstant.USER_PERM_CACHE_KEY, Long.parseLong(loginId.toString())));\n        return CollectionUtil.newArrayList(permCodes);\n    }\n\n    /**\n     * 返回指定账号id所拥有的角色标识集合，loginId即登陆时传入的ID\n     * @param loginId 用户ID\n     * @param loginType 账号体系标识\n     * @return 角色编码集合\n     */\n    @Override\n    public List<String> getRoleList(Object loginId, String loginType) {\n        //从redis获取角色数据，在请求getUserInfo接口时会set角色数据\n        Set<String> roleCodes = redisManager.get(StrUtil.indexedFormat(RedisKeyConstant.USER_ROLE_CACHE_KEY, Long.parseLong(loginId.toString())));\n        return CollectionUtil.newArrayList(roleCodes);\n    }\n\n}\n"
  },
  {
    "path": "minimalist-application/src/main/resources/application-dev.yml",
    "content": "mybatis-flex:\n    configuration:\n        map-underscore-to-camel-case: true\n        auto-mapping-behavior: full\n    mapper-locations:\n        - classpath*:mappers/**/*.xml\n    global-config:\n        # 逻辑删除字段\n        logic-delete-column: deleted\n    # 别名包扫描路径\n    type-aliases-package: com.minimalist.**.entity\n\n# springdoc-openapi 项目文档配置\nspringdoc:\n    title: 极简多租户开发平台\n    authorName: 小太阳\n    authorUrl: https://apebbs.cn\n    authorEmail: 438562332@qq.com\n    swagger-ui:\n        path: /swagger-ui.html\n        tags-sorter: alpha\n        operations-sorter: alpha\n    api-docs:\n        path: /v3/api-docs\n    group-configs:\n        - group: '1.全部'\n          paths-to-match: '/**'\n          packages-to-scan: com.minimalist\n        - group: '3.basic基础模块'\n          paths-to-match: '/basic/**'\n          packages-to-scan: com.minimalist.basic\n# knife4j的增强配置，不需要增强可以不配\nknife4j:\n    # 启用增强模式\n    enable: true\n    setting:\n        language: zh_cn\n        swagger-model-name: 实体说明\nspring:\n    datasource:\n        dynamic:\n            primary: master\n            strict: false\n            hikari:\n                # 最小空闲连接数量\n                minimum-idle: 20\n                # 连接池最大连接数，默认是10\n                maximum-pool-size: 100\n                # 空闲连接存活最大时间，默认600000\n                idle-timeout: 30000\n                # 此属性控制池中连接的最长生命周期，值0表示无限生命周期，默认1800000即30分钟\n                max-lifetime: 60000\n                # 数据库连接超时时间,默认30秒，即30000\n                connection-timeout: 30000\n                # 测试连接\n                connection-test-query: \"SELECT 1\"\n            datasource:\n                master:\n                    type: com.zaxxer.hikari.HikariDataSource\n                    driver-class-name: com.mysql.cj.jdbc.Driver\n                    username: root\n                    password: 123456\n                    url: jdbc:mysql://localhost:3306/minimalist?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowMultiQueries=true&useAffectedRows=true&rewriteBatchedStatements=true\n\n    servlet:\n        multipart:\n            # 单个文件大小\n            max-file-size: 100MB\n            # 总文件大小\n            max-request-size: 256MB\n    data:\n        # redis配置\n        redis:\n            # Redis数据库索引（默认为0）\n            database: 3\n            # Redis服务器地址\n            host: 127.0.0.1\n            # Redis服务器连接端口\n            port: 6379\n            # Redis服务器连接密码（默认为空）\n            password: 1\n            # 读取超时时间\n            timeout: 10s\n            # 连接超时时间\n            connect-timeout: 5s\n            lettuce:\n                pool:\n                    enabled: true\n                    # 连接池最大连接数\n                    max-active: 200\n                    # 连接池最大阻塞等待时间（使用负值表示没有限制）\n                    max-wait: -1ms\n                    # 连接池中的最大空闲连接\n                    max-idle: 10\n                    # 连接池中的最小空闲连接\n                    min-idle: 0\n\nredisson:\n    address: redis://${spring.data.redis.host}:${spring.data.redis.port}\n    password: ${spring.data.redis.password}\n    database: 3\n    pingInterval: 10000\n    connectionPoolSize: 100\n    connectionMinimumIdleSize: 0\n# 防XSS，使用说明文档请访问：https://gitee.com/596392912/mica 找到其中的mica-xss中的 README.md 查看\nmica:\n    xss:\n        # 开启xss\n        enabled: true\n        # 【全局】是否去除文本首尾空格\n        trim-text: true\n        # mode有三个模式：clear清理（默认）、escape转义、validate校验，\n        # validate校验模式，校验未通过会抛出异常，可使用全局异常捕获进行处理\n        # clear模式，会将不允许的数据清理掉\n        # escape模式，会将不允许的数据转义\n        # 出错抛出的异常：\n        # json处理默认使用jackson，jackson校验未通过异常：HttpMessageNotReadableException\n        # form表单校验未通过异常：MethodArgumentConversionNotSupportedException\n        mode: clear\n        # 是否保留换行\n        pretty-print: false\n        # 是否转义\n        enable-escape: false\n        # 需要拦截的路由\n        path-patterns: /**\n        # 需要放行的路由，可以配置，也可以使用 @XssCleanIgnore 注解对某个方法和类进行忽略\n        # 若需要对实体类的某个字段忽略，使用 @JsonDeserialize(using = XssCleanDeserializer.class) 注解忽略\n        path-exclude-patterns:\n\n############## Sa-Token 配置 (文档: https://sa-token.cc) ##############\nsa-token:\n    # token 名称，默认：satoken\n    token-name: Authentication\n    # token 有效期（单位：秒） 604800秒=7天，默认2592000秒=30天，-1=永久有效\n    timeout: 604800\n    # token 最低活跃频率（单位：秒），如果 token 超过此时间没有访问系统就会被冻结，默认-1 代表不限制，永不冻结\n    active-timeout: -1\n    # 是否允许同一账号多地同时登录 （为 true 时允许一起登录, 为 false 时新登录挤掉旧登录）\n    is-concurrent: true\n    # 在多人登录同一账号时，是否共用一个 token （为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token）\n    is-share: false\n    # token 风格（默认可取值：uuid、simple-uuid、random-32、random-64、random-128、tik）\n    token-style: simple-uuid\n    # 是否输出操作日志\n    is-log: true\n\n# 日志配置\nlogging:\n    level:\n        com.minimalist: debug\n        org.springframework: warn\n    config: classpath:logback-dev.xml"
  },
  {
    "path": "minimalist-application/src/main/resources/application.yml",
    "content": "server:\n    port: 9090\n    servlet:\n        context-path: /minimalist\n        encoding:\n            charset: UTF-8\n            force: true\nspring:\n    profiles:\n        active: @spring.profiles.active@"
  },
  {
    "path": "minimalist-application/src/main/resources/logback-dev.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<configuration debug=\"true\">\n\n    <!-- 日志存放路径 -->\n    <property name=\"LOG_PATH\" value=\"./logs\"/>\n\n    <!-- 日志输出格式 -->\n    <property name=\"LOG_PATTERN\" value=\"%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{traceId}] %-5level [%thread] %logger{36} - %msg%n\" />\n\n    <!-- 控制台输出 -->\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <encoder>\n            <pattern>${LOG_PATTERN}</pattern>\n        </encoder>\n    </appender>\n\n    <!-- 系统输出 -->\n    <appender name=\"FILE\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <!-- 日志文件输出路径 -->\n        <file>${LOG_PATH}/minimalist.log</file>\n        <!-- 循环政策：基于时间创建日志文件 -->\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">\n            <fileNamePattern>${LOG_PATH}/minimalist-%d{yyyy-MM-dd}.log</fileNamePattern>\n            <!-- 保留历史日志文件的天数 30天 -->\n            <maxHistory>30</maxHistory>\n        </rollingPolicy>\n        <encoder>\n            <pattern>${LOG_PATTERN}</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"info\">\n        <appender-ref ref=\"FILE\" />\n        <appender-ref ref=\"STDOUT\" />\n    </root>\n\n    <!-- 系统模块日志级别控制  -->\n    <logger name=\"com.minimalist\" level=\"info\" />\n    <!-- Spring日志级别控制  -->\n    <logger name=\"org.springframework\" level=\"warn\" />\n\n</configuration>"
  },
  {
    "path": "minimalist-basic/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>minimalist-saas</artifactId>\n        <groupId>com.cn</groupId>\n        <version>1.0.0-SNAPSHOT</version>\n    </parent>\n\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>minimalist-basic</artifactId>\n    <name>minimalist-basic</name>\n    <description>基础模块</description>\n    <packaging>jar</packaging>\n\n    <dependencies>\n\n        <!-- spring-web -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-web</artifactId>\n        </dependency>\n\n        <!-- aop -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-aop</artifactId>\n        </dependency>\n\n        <!-- swagger-knife4j -->\n        <dependency>\n            <groupId>com.github.xiaoymin</groupId>\n            <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>\n            <version>${knife4j.version}</version>\n        </dependency>\n\n        <!-- mysql -->\n        <dependency>\n            <groupId>mysql</groupId>\n            <artifactId>mysql-connector-java</artifactId>\n            <version>${mysql.version}</version>\n        </dependency>\n\n        <!-- lombok -->\n        <dependency>\n            <groupId>org.projectlombok</groupId>\n            <artifactId>lombok</artifactId>\n        </dependency>\n        <!-- 参数校验 -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-validation</artifactId>\n        </dependency>\n\n        <!-- Sa-Token 权限认证，在线文档：https://sa-token.cc -->\n        <dependency>\n            <groupId>cn.dev33</groupId>\n            <artifactId>sa-token-spring-boot3-starter</artifactId>\n            <version>${satoken.version}</version>\n        </dependency>\n\n        <!-- Sa-Token 整合 Redis （使用 jackson 序列化方式） -->\n        <dependency>\n            <groupId>cn.dev33</groupId>\n            <artifactId>sa-token-redis-jackson</artifactId>\n            <version>${satoken.version}</version>\n        </dependency>\n\n        <!-- 提供Redis连接池 -->\n        <dependency>\n            <groupId>org.apache.commons</groupId>\n            <artifactId>commons-pool2</artifactId>\n        </dependency>\n\n        <!-- redisson -->\n        <dependency>\n            <groupId>org.redisson</groupId>\n            <artifactId>redisson</artifactId>\n            <version>${redisson.version}</version>\n        </dependency>\n\n        <!-- hutool工具类 -->\n        <dependency>\n            <groupId>cn.hutool</groupId>\n            <artifactId>hutool-all</artifactId>\n            <version>${hutool.version}</version>\n        </dependency>\n\n        <!-- mica-xss -->\n        <dependency>\n            <groupId>net.dreamlu</groupId>\n            <artifactId>mica-xss</artifactId>\n            <version>${mica-xss.version}</version>\n        </dependency>\n\n        <!-- 缩略图处理 -->\n        <dependency>\n            <groupId>net.coobird</groupId>\n            <artifactId>thumbnailator</artifactId>\n            <version>${thumbnailator.version}</version>\n        </dependency>\n\n        <!-- webp-imageio 配合thumbnailator做缩略图处理，不引入回出现No suitable ImageReader found for source data. -->\n        <dependency>\n            <groupId>org.sejda.imageio</groupId>\n            <artifactId>webp-imageio</artifactId>\n            <version>${webp-imageio.version}</version>\n        </dependency>\n\n        <!-- mybatis-flex -->\n        <dependency>\n            <groupId>com.mybatis-flex</groupId>\n            <artifactId>mybatis-flex-spring-boot3-starter</artifactId>\n            <version>${mybatis-flex.version}</version>\n        </dependency>\n\n        <!-- mybatis-flex APT服务 -->\n        <dependency>\n            <groupId>com.mybatis-flex</groupId>\n            <artifactId>mybatis-flex-processor</artifactId>\n            <version>${mybatis-flex.version}</version>\n            <scope>provided</scope>\n        </dependency>\n\n        <!-- mybatis-flex 代码生成 -->\n        <dependency>\n            <groupId>com.mybatis-flex</groupId>\n            <artifactId>mybatis-flex-codegen</artifactId>\n            <version>${mybatis-flex.codegen.version}</version>\n        </dependency>\n\n        <!-- hikariCP连接池 -->\n        <dependency>\n            <groupId>com.zaxxer</groupId>\n            <artifactId>HikariCP</artifactId>\n            <version>${mybatis-flex.hikariCP.version}</version>\n        </dependency>\n\n        <!-- 多数据源 -->\n        <dependency>\n            <groupId>com.baomidou</groupId>\n            <artifactId>dynamic-datasource-spring-boot3-starter</artifactId>\n            <version>${dynamic-datasource.version}</version>\n        </dependency>\n\n        <!-- 七牛云 -->\n        <dependency>\n            <groupId>com.qiniu</groupId>\n            <artifactId>qiniu-java-sdk</artifactId>\n            <version>${qiniu.version}</version>\n        </dependency>\n\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/async/AsyncConfig.java",
    "content": "package com.minimalist.basic.config.async;\n\nimport org.slf4j.MDC;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.scheduling.annotation.AsyncConfigurer;\nimport org.springframework.scheduling.annotation.EnableAsync;\nimport org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;\nimport java.util.Map;\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.ThreadPoolExecutor;\n\n/**\n * EnableAsync 开启 @Async 支持\n * 自定义Async线程池，跨线程传递traceId\n */\n@EnableAsync\n@Configuration\npublic class AsyncConfig implements AsyncConfigurer {\n\n    /**\n     * 核心线程数 = cpu核心数 + 1\n     */\n    private final int coreSize = Runtime.getRuntime().availableProcessors() + 1;\n\n    @Override\n    public Executor getAsyncExecutor() {\n        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();\n        executor.setCorePoolSize(coreSize);\n        executor.setMaxPoolSize(coreSize * 2);\n        executor.setQueueCapacity(100);  //队列数\n        executor.setThreadNamePrefix(\"Async-Thread-\");\n        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());\n        executor.setTaskDecorator(runnable -> {\n            Map<String, String> copyOfContextMap = MDC.getCopyOfContextMap();\n            return () -> {\n                try {\n                    MDC.setContextMap(copyOfContextMap);\n                    runnable.run();\n                } finally {\n                    MDC.clear();\n                }\n            };\n        });\n        executor.initialize();\n        return executor;\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/async/ThreadPoolConfig.java",
    "content": "package com.minimalist.basic.config.async;\n\nimport org.slf4j.MDC;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;\nimport java.util.Map;\nimport java.util.concurrent.ThreadPoolExecutor;\n\n@Configuration\npublic class ThreadPoolConfig {\n\n    /**\n     * 核心线程数 = cpu核心数 + 1\n     */\n    private final int coreSize = Runtime.getRuntime().availableProcessors() + 1;\n\n    @Bean(name = \"threadPoolExecutor\")\n    public ThreadPoolTaskExecutor threadPoolExecutor() {\n        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();\n        executor.setCorePoolSize(coreSize);\n        executor.setMaxPoolSize(coreSize * 2);\n        executor.setQueueCapacity(100);\n        executor.setThreadNamePrefix(\"ThreadPool-\");\n        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());\n        executor.setTaskDecorator(runnable -> {\n            Map<String, String> copyOfContextMap = MDC.getCopyOfContextMap();\n            return () -> {\n                try {\n                    MDC.setContextMap(copyOfContextMap);\n                    runnable.run();\n                } finally {\n                    MDC.clear();\n                }\n            };\n        });\n        executor.initialize();\n        return executor;\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/convert/FileSizeDeserializer.java",
    "content": "package com.minimalist.basic.config.convert;\n\nimport cn.hutool.core.io.unit.DataSizeUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.fasterxml.jackson.core.JacksonException;\nimport com.fasterxml.jackson.core.JsonParser;\nimport com.fasterxml.jackson.databind.DeserializationContext;\nimport com.fasterxml.jackson.databind.JsonDeserializer;\nimport java.io.IOException;\n\n/**\n * 接收数据时，对String类型的文件大小进行处理\n */\npublic class FileSizeDeserializer extends JsonDeserializer<Long> {\n\n    @Override\n    public Long deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JacksonException {\n        String text = jsonParser.getText();\n        return StrUtil.isBlank(text) ? null : DataSizeUtil.parse(text);\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/convert/FileSizeSerializer.java",
    "content": "package com.minimalist.basic.config.convert;\n\nimport cn.hutool.core.io.FileUtil;\nimport cn.hutool.core.util.ObjectUtil;\nimport com.fasterxml.jackson.core.JsonGenerator;\nimport com.fasterxml.jackson.databind.JsonSerializer;\nimport com.fasterxml.jackson.databind.SerializerProvider;\n\nimport java.io.IOException;\n\n/**\n * 返回数据时，对Long类型的文件大小进行处理\n */\npublic class FileSizeSerializer extends JsonSerializer<Long> {\n\n    @Override\n    public void serialize(Long value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {\n        if (ObjectUtil.isNotNull(value)) {\n            jsonGenerator.writeString(FileUtil.readableFileSize(value));\n        } else {\n            jsonGenerator.writeNull();\n        }\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/convert/LongArrJsonSerializer.java",
    "content": "package com.minimalist.basic.config.convert;\n\nimport cn.hutool.core.collection.CollectionUtil;\nimport com.fasterxml.jackson.core.JsonGenerator;\nimport com.fasterxml.jackson.databind.JsonSerializer;\nimport com.fasterxml.jackson.databind.SerializerProvider;\n\nimport java.io.IOException;\nimport java.util.Collection;\n\n/**\n * 返回数据时，对Long类型的集合转换成String后再返回，防止Long类型精度丢失\n */\npublic class LongArrJsonSerializer extends JsonSerializer<Collection<Long>> {\n\n    @Override\n    public void serialize(Collection<Long> values, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {\n        if (CollectionUtil.isNotEmpty(values)) {\n            String [] strArr = values.stream().map(l -> Long.toString(l)).toArray(String[]::new);\n            jsonGenerator.writeArray(strArr, 0, strArr.length);\n        } else {\n            jsonGenerator.writeNull();\n        }\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/dataConfig/SystemConfigInit.java",
    "content": "package com.minimalist.basic.config.dataConfig;\n\nimport com.minimalist.basic.service.ConfigService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.ApplicationArguments;\nimport org.springframework.boot.ApplicationRunner;\nimport org.springframework.stereotype.Component;\n\n@Component\npublic class SystemConfigInit implements ApplicationRunner {\n\n    @Autowired\n    private ConfigService configService;\n\n    @Override\n    public void run(ApplicationArguments args) throws Exception {\n        //初始化将系统参数缓存到Map中\n        configService.refreshConfigCache();\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/eDict/BeanMethod.java",
    "content": "package com.minimalist.basic.config.eDict;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\nimport java.lang.reflect.Method;\n\n/**\n * 存放class和method\n * @param <T>\n */\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\npublic class BeanMethod<T> {\n\n    /**\n     * class\n     */\n    private T bean;\n\n    /**\n     * method\n     */\n    private Method method;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/eDict/EDict.java",
    "content": "package com.minimalist.basic.config.eDict;\n\nimport java.lang.annotation.*;\n\n/**\n * 额外字典数据处理\n */\n@Target({ElementType.METHOD})\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\npublic @interface EDict {\n\n    /**\n     * 字典类型\n     */\n    String dictType() default \"\";\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/eDict/EDictConstant.java",
    "content": "package com.minimalist.basic.config.eDict;\n\nimport cn.hutool.core.map.MapUtil;\nimport org.springframework.stereotype.Component;\nimport java.util.Map;\n\n@Component\npublic class EDictConstant {\n\n    /** 存储额外字典数据处理的方法 */\n    public static final Map<String, BeanMethod<?>> dictMethodMap = MapUtil.newConcurrentHashMap();\n\n    /** 部门字典type */\n    public static final String DEPT_LIST = \"dict-dept-list\";\n\n    /** 岗位字典type */\n    public static final String POST_LIST = \"dict-post-list\";\n\n    /** 角色字典type */\n    public static final String ROLE_LIST = \"dict-role-list\";\n\n    /** 用户字典type */\n    public static final String USER_LIST = \"dict-user-list\";\n\n    /** 租户字典type */\n    public static final String TENANT_LIST = \"dict-tenant-list\";\n\n    /** 租户套餐字典type */\n    public static final String TENANT_PACKAGE_LIST = \"tenant-package-list\";\n\n    /** 存储方式字典type */\n    public static final String STORAGE_LIST = \"storage-list\";\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/eDict/EDictInit.java",
    "content": "package com.minimalist.basic.config.eDict;\n\nimport cn.hutool.core.util.ObjectUtil;\nimport cn.hutool.core.util.StrUtil;\nimport org.springframework.aop.support.AopUtils;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.ApplicationArguments;\nimport org.springframework.boot.ApplicationRunner;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.core.annotation.Order;\nimport org.springframework.stereotype.Component;\n\nimport java.lang.reflect.Method;\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\n/**\n * 额外字典处理，初始化加载字典处理方法\n */\n@Order(-1)\n@Component\npublic class EDictInit implements ApplicationRunner {\n\n    @Autowired\n    private ApplicationContext applicationContext;\n\n    @Override\n    public void run(ApplicationArguments args) throws Exception {\n        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();\n        //getBean，找到含有额外字典处理的注解方法\n        for (String beanDefinitionName : beanDefinitionNames) {\n            Object bean = applicationContext.getBean(beanDefinitionName);\n            postProcessAfterInitialization(bean);\n        }\n        System.out.println(\"-------------------- 初始化自定义字典完毕 --------------------\");\n    }\n\n    public void postProcessAfterInitialization(Object bean) {\n        //Spring代理类中的方法\n        Method[] proxyMethods = bean.getClass().getDeclaredMethods();\n        //Spring代理类中的方法转Map，key：方法名，value：代理方法\n        Map<String, Method> proxyMethodMap = Arrays.stream(proxyMethods).collect(Collectors.toMap(Method::getName, Function.identity(), (v1, v2) -> v1));\n        //判断是否是cglib动态代理\n        boolean cglibProxy = AopUtils.isCglibProxy(bean);\n        if (cglibProxy) {\n            //获取Spring代理类，再获取代理目标类\n            Class<?> superclass = bean.getClass().getSuperclass();\n            //获取代理目标类中的方法，目的是为了获取到 ExtraDict 自定义注解\n            Method[] methods = superclass.getDeclaredMethods();\n            for (Method method : methods) {\n                cacheBeanMethod(method, proxyMethodMap, bean);\n            }\n        } else {\n            //不是cglib动态代理，可能是其他代理，暂时还没遇到其他代理扫描不到的情况\n            for (Method method : proxyMethods) {\n                cacheBeanMethod(method, proxyMethodMap, bean);\n            }\n        }\n    }\n\n    private void cacheBeanMethod(Method method, Map<String, Method> proxyMethodMap, Object bean) {\n        //获取自定义注解\n        EDict eDict = method.getDeclaredAnnotation(EDict.class);\n        if (ObjectUtil.isNotNull(eDict) && StrUtil.isNotBlank(eDict.dictType())) {\n            //从代理方法中查找并缓存代理方法，如果代理方法不存在，则缓存代理目标类方法\n            Method proxyMethod = proxyMethodMap.get(method.getName());\n            if (ObjectUtil.isNotNull(proxyMethod)) {\n                EDictConstant.dictMethodMap.put(eDict.dictType(), new BeanMethod<>(bean, proxyMethod));\n            } else {\n                EDictConstant.dictMethodMap.put(eDict.dictType(), new BeanMethod<>(bean, method));\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/exception/BusinessException.java",
    "content": "package com.minimalist.basic.config.exception;\n\nimport com.minimalist.basic.entity.enums.RespEnum;\nimport lombok.Data;\n\nimport java.io.Serializable;\n\n@Data\npublic class BusinessException extends RuntimeException implements Serializable {\n\n    private static final long serialVersionUID = 1L;\n\n    /** 状态码 */\n    private Integer code;\n\n    /** 异常信息 */\n    private String message;\n\n    public BusinessException(Integer code, String message) {\n        this.code = code;\n        this.message = message;\n    }\n\n    public BusinessException(String message) {\n        this.code = RespEnum.FAILED.getCode();\n        this.message = message;\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/fileHandler/FileManager.java",
    "content": "package com.minimalist.basic.config.fileHandler;\n\nimport cn.hutool.core.map.MapUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.minimalist.basic.config.exception.BusinessException;\nimport com.minimalist.basic.config.fileHandler.handler.FileHandler;\nimport com.minimalist.basic.entity.po.MDict;\nimport com.minimalist.basic.mapper.MDictMapper;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.stereotype.Component;\nimport java.util.Map;\nimport java.util.Optional;\n\n@Component\npublic class FileManager {\n\n    @Autowired\n    private ApplicationContext applicationContext;\n\n    @Autowired\n    private MDictMapper dictMapper;\n\n\n    /**\n     * 根据存储类型获取对应的处理类\n     * @param storageType 存储类型\n     * @return 处理类\n     */\n    public FileHandler getFileHandler(String storageType) {\n        if (StrUtil.isBlank(storageType)) {\n            throw new BusinessException(\"处理文件时，存储平台参数为空\");\n        }\n        Map<String, FileHandler> beansMaps = applicationContext.getBeansOfType(FileHandler.class);\n        if (MapUtil.isEmpty(beansMaps)) {\n            throw new BusinessException(\"处理文件时，未找到指定的处理方法\");\n        }\n        return beansMaps.values().stream()\n                .filter(bean -> bean.isHandler(storageType))\n                .findFirst()\n                .orElseThrow(() -> new BusinessException(\"未找到 [\" + storageType + \"] 的处理方法\"));\n    }\n\n    /**\n     * 根据文件来源获取存储路径\n     * @param fileSource 文件来源\n     * @return 路径\n     */\n    public String getPathByFileSource(Integer fileSource) {\n        MDict dict = dictMapper.selectDictByDictTypeAndKey(\"file-source-path\", String.valueOf(fileSource));\n        return Optional.ofNullable(dict)\n                .map(MDict::getDictValue)\n                .orElse(\"common/\");\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/fileHandler/FileResourcesInit.java",
    "content": "package com.minimalist.basic.config.fileHandler;\n\nimport cn.hutool.json.JSONUtil;\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.config.fileHandler.entity.LocalFileEntity;\nimport com.minimalist.basic.entity.enums.StorageEnum;\nimport com.minimalist.basic.entity.po.MStorage;\nimport com.minimalist.basic.mapper.MStorageMapper;\nimport com.mybatisflex.core.query.QueryWrapper;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.core.annotation.Order;\nimport org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;\nimport org.springframework.web.servlet.config.annotation.WebMvcConfigurer;\nimport java.util.List;\n\n/**\n * 静态资源目录映射，只有本地存储需要映射，启动后执行一次\n * 如果在存储管理中，修改了本地存储的文件存储路径，那么程序需要重启才可以生效\n */\n@Order(-1)\n@Configuration\npublic class FileResourcesInit implements WebMvcConfigurer {\n\n    @Autowired\n    private MStorageMapper storageMapper;\n\n    @Override\n    public void addResourceHandlers(ResourceHandlerRegistry registry) {\n        //查询本地存储\n        List<MStorage> storageList = storageMapper.selectListByQuery(QueryWrapper.create()\n                .eq(MStorage::getStorageType, StorageEnum.StorageType.LOCAL.getCode())\n                .eq(MStorage::getStatus, StatusEnum.STATUS_1.getCode())\n        );\n        for (MStorage storage : storageList) {\n            LocalFileEntity localFileEntity = JSONUtil.toBean(storage.getStorageConfig(), LocalFileEntity.class);\n            registry.addResourceHandler(\"/files/**\").addResourceLocations(\"file:\" + localFileEntity.getStoragePath());\n        }\n        System.out.println(\"-------------------- 初始化静态资源映射完毕 --------------------\");\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/fileHandler/entity/LocalFileEntity.java",
    "content": "package com.minimalist.basic.config.fileHandler.entity;\n\nimport jakarta.validation.constraints.NotBlank;\nimport lombok.Data;\n\n@Data\npublic class LocalFileEntity {\n\n    @NotBlank(message = \"本地存储路径不能为空\")\n    private String storagePath;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/fileHandler/entity/MinIOFileEntity.java",
    "content": "package com.minimalist.basic.config.fileHandler.entity;\n\nimport jakarta.validation.constraints.NotBlank;\nimport lombok.Data;\n\n@Data\npublic class MinIOFileEntity {\n\n    @NotBlank(message = \"访问密钥不能为空\")\n    private String accessKey;\n\n    @NotBlank(message = \"私有密钥不能为空\")\n    private String secretKey;\n\n    @NotBlank(message = \"域名不能为空\")\n    private String endPoint;\n\n    @NotBlank(message = \"桶名称不能为空\")\n    private String bucketName;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/fileHandler/entity/QiNiuFileEntity.java",
    "content": "package com.minimalist.basic.config.fileHandler.entity;\n\nimport jakarta.validation.constraints.NotBlank;\nimport lombok.Data;\n\n@Data\npublic class QiNiuFileEntity {\n\n    @NotBlank(message = \"访问密钥不能为空\")\n    private String accessKey;\n\n    @NotBlank(message = \"私有密钥不能为空\")\n    private String secretKey;\n\n    @NotBlank(message = \"桶名称不能为空\")\n    private String bucketName;\n\n    @NotBlank(message = \"域名不能为空\")\n    private String endPoint;\n\n    @NotBlank(message = \"七牛云存储区域ID不能为空\")\n    private String regionId;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/fileHandler/handler/FileHandler.java",
    "content": "package com.minimalist.basic.config.fileHandler.handler;\n\nimport com.minimalist.basic.entity.po.MFile;\nimport com.minimalist.basic.entity.po.MStorage;\nimport org.springframework.web.multipart.MultipartFile;\n\npublic interface FileHandler {\n\n    /**\n     * 是否是对应的处理类\n     * @param storageType 存储类型\n     * @return 处理类\n     */\n    boolean isHandler(String storageType);\n\n    /**\n     * 参数校验\n     * @param storageConfig JSON存储配置信息\n     * @return 格式化后的配置信息\n     */\n    String valid(String storageConfig);\n\n    /**\n     * 单文件上传\n     * @param multipartFile 文件\n     * @param fileSource 文件来源\n     * @param storage 存储信息\n     * @return 文件信息\n     */\n    MFile uploadFile(MultipartFile multipartFile, Integer fileSource, MStorage storage);\n\n    /**\n     * 删除文件\n     * @param file 文件信息\n     * @return 是否删除成功\n     */\n    boolean deleteFile(MFile file, MStorage storage);\n\n    /**\n     * 移动文件\n     * @param file 文件信息\n     * @param storage 存储信息\n     * @return 是否成功\n     */\n    boolean moveFile(MFile file, MStorage storage);\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/fileHandler/handler/impl/LocalFileHandler.java",
    "content": "package com.minimalist.basic.config.fileHandler.handler.impl;\n\nimport cn.hutool.core.io.FileUtil;\nimport cn.hutool.core.io.file.FileNameUtil;\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.util.IdUtil;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.json.JSONUtil;\nimport com.minimalist.basic.config.exception.BusinessException;\nimport com.minimalist.basic.config.fileHandler.FileManager;\nimport com.minimalist.basic.config.fileHandler.entity.LocalFileEntity;\nimport com.minimalist.basic.config.fileHandler.handler.FileHandler;\nimport com.minimalist.basic.entity.enums.FileEnum;\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.entity.enums.StorageEnum;\nimport com.minimalist.basic.entity.po.MFile;\nimport com.minimalist.basic.entity.po.MStorage;\nimport com.minimalist.basic.mapper.MFileMapper;\nimport com.minimalist.basic.mapper.MStorageMapper;\nimport com.minimalist.basic.utils.CommonConstant;\nimport com.minimalist.basic.utils.UnqIdUtil;\nimport com.minimalist.basic.utils.ValidateUtil;\nimport com.mybatisflex.core.query.QueryWrapper;\nimport jakarta.servlet.http.HttpServletRequest;\nimport lombok.extern.slf4j.Slf4j;\nimport net.coobird.thumbnailator.Thumbnails;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Service;\nimport org.springframework.web.context.request.RequestContextHolder;\nimport org.springframework.web.context.request.ServletRequestAttributes;\nimport org.springframework.web.multipart.MultipartFile;\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.time.LocalDateTime;\n\n@Slf4j\n@Service\npublic class LocalFileHandler implements FileHandler {\n\n    @Value(\"${server.servlet.context-path}\")\n    private String contextPath;\n\n    @Autowired\n    private FileManager fileManager;\n\n    @Autowired\n    private MFileMapper fileMapper;\n\n    @Autowired\n    private MStorageMapper storageMapper;\n\n    /**\n     * 是否是对应的处理类\n     * @param storageType 存储类型\n     * @return 处理类\n     */\n    @Override\n    public boolean isHandler(String storageType) {\n        return StorageEnum.StorageType.LOCAL.getCode().equals(storageType);\n    }\n\n    /**\n     * 参数校验\n     * @param storageConfig JSON存储配置信息\n     */\n    @Override\n    public String valid(String storageConfig) {\n        LocalFileEntity localFileEntity = JSONUtil.toBean(storageConfig, LocalFileEntity.class);\n        ValidateUtil.valid(localFileEntity);\n        //格式化路径\n        String normalize = FileUtil.normalize(localFileEntity.getStoragePath());\n        localFileEntity.setStoragePath(normalize);\n        return JSONUtil.toJsonStr(localFileEntity);\n    }\n\n    /**\n     * 单文件上传\n     * @param multipartFile 文件\n     * @param fileSource 文件来源\n     * @param storage 存储信息\n     * @return 文件信息\n     */\n    @Override\n    public MFile uploadFile(MultipartFile multipartFile, Integer fileSource, MStorage storage) {\n        //根据文件来源，获取相对路径\n        String fileSourcePath = fileManager.getPathByFileSource(fileSource);\n        //本地存储路径\n        LocalFileEntity localFileEntity = JSONUtil.toBean(storage.getStorageConfig(), LocalFileEntity.class);\n        String storagePath = localFileEntity.getStoragePath();\n        //文件后缀\n        String fileSuffix = FileNameUtil.extName(multipartFile.getOriginalFilename());\n        //新文件名\n        String newFileName = IdUtil.objectId() + \".\" + fileSuffix;\n        //拼接路径\n        String path = FileUtil.normalize(storagePath + \"/\" + fileSourcePath + newFileName);\n        //request请求信息\n        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();\n        String url = request.getScheme() + \"://\" + request.getServerName() + \":\" + request.getServerPort() + contextPath + \"/files/\";\n        //返回的文件信息\n        MFile fileInfo = new MFile();\n        fileInfo.setFileId(UnqIdUtil.uniqueId());\n        fileInfo.setFileName(multipartFile.getOriginalFilename());\n        fileInfo.setNewFileName(newFileName);\n        fileInfo.setFileSize(multipartFile.getSize());\n        fileInfo.setFileType(multipartFile.getContentType());\n        fileInfo.setFileBasePath(storagePath);\n        fileInfo.setFilePath(FileUtil.normalize(storagePath + \"/\" + fileSourcePath));\n        fileInfo.setFileUrl(url + fileSourcePath + newFileName);\n        fileInfo.setFileSource(fileSource);\n        fileInfo.setStorageId(storage.getStorageId());\n        //如果未指定文件来源，将状态置为正常\n        //因为这是从文件选择组件中上传的文件，不置为正常，在选择文件时查询条件是正常的才能被查出来\n        if (fileSource < CommonConstant.ZERO) {\n            fileInfo.setStatus(StatusEnum.STATUS_1.getCode());\n        }\n        try {\n            log.info(\"上传文件，路径：{}\", path);\n            File file = FileUtil.touch(path);\n            multipartFile.transferTo(file);\n            if (StrUtil.isNotBlank(multipartFile.getContentType()) && multipartFile.getContentType().contains(\"image\")) {\n                //如果是图片，生成缩略图\n                String thFileName = \"thumbnail-\" + newFileName;\n                String thPath = FileUtil.normalize(storagePath + \"/\" + fileSourcePath + \"/\" + thFileName);\n                File thFile = FileUtil.touch(thPath);\n                Thumbnails.of(file)\n                        .scale(0.4)\n                        .toFile(thFile);\n                fileInfo.setFileThUrl(url + fileSourcePath + thFileName);\n                fileInfo.setFileThFilename(thFileName);\n                fileInfo.setFileThSize(thFile.length());\n            }\n        } catch (IOException e) {\n            FileUtil.del(path);\n            log.warn(\"上传文件，异常：\", e);\n            throw new BusinessException(FileEnum.ErrorMsg.FILE_UPLOAD_FAIL.getDesc());\n        }\n        return fileInfo;\n    }\n\n    /**\n     * 删除文件\n     * @param file 文件信息\n     * @return 是否删除成功\n     */\n    @Override\n    public boolean deleteFile(MFile file, MStorage storage) {\n        String filePath = file.getFilePath() + file.getNewFileName();\n        boolean result = FileUtil.del(filePath);\n        //如果有缩略图，删除缩略图\n        if (StrUtil.isNotBlank(file.getFileThFilename())) {\n            String fileThPath = file.getFilePath() + file.getFileThFilename();\n            FileUtil.del(fileThPath);\n        }\n        return result;\n    }\n\n    /**\n     * 移动文件\n     * @param file 文件信息\n     * @param storage 存储信息\n     * @return 是否成功\n     */\n    @Override\n    public boolean moveFile(MFile file, MStorage storage) {\n        //根据文件来源，获取相对路径\n        String fileSourcePath = fileManager.getPathByFileSource(file.getFileSource());\n        //本地存储路径\n        LocalFileEntity localFileEntity = JSONUtil.toBean(storage.getStorageConfig(), LocalFileEntity.class);\n        String storagePath = localFileEntity.getStoragePath();\n        //request请求信息\n        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();\n        String url = request.getScheme() + \"://\" + request.getServerName() + \":\" + request.getServerPort() + contextPath + \"/files/\";\n        //源文件路径\n        Path sourcePath = Paths.get(FileUtil.normalize(file.getFilePath() + \"/\" + file.getNewFileName()));\n        //目标文件路径\n        Path targetPath = Paths.get(FileUtil.normalize(storagePath + \"/\" + fileSourcePath + \"/\" + file.getNewFileName()));\n        try {\n            FileUtil.move(sourcePath, targetPath, true);\n            //修改文件url\n            file.setFileUrl(url + fileSourcePath + file.getNewFileName());\n            //如果有缩略图，需要将缩略图移动\n            if (StrUtil.isNotBlank(file.getFileThFilename())) {\n                //源文件路径\n                Path sourcePathTh = Paths.get(FileUtil.normalize(file.getFilePath() + \"/\" + file.getFileThFilename()));\n                //目标文件路径\n                Path targetPathTh = Paths.get(FileUtil.normalize(storagePath + \"/\" + fileSourcePath + \"/\" + file.getFileThFilename()));\n                //移动缩略图\n                FileUtil.move(sourcePathTh, targetPathTh, true);\n                //修改缩略图url\n                file.setFileThUrl(url + fileSourcePath + file.getFileThFilename());\n            }\n            //修改文件路径\n            file.setFilePath(FileUtil.normalize(storagePath + \"/\" + fileSourcePath));\n            return true;\n        } catch (Exception e) {\n            log.warn(\"移动文件，异常：\", e);\n        }\n        return false;\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/fileHandler/handler/impl/MinIOFileHandler.java",
    "content": "package com.minimalist.basic.config.fileHandler.handler.impl;\n\nimport cn.hutool.json.JSONUtil;\nimport com.minimalist.basic.config.fileHandler.entity.MinIOFileEntity;\nimport com.minimalist.basic.config.fileHandler.handler.FileHandler;\nimport com.minimalist.basic.entity.enums.StorageEnum;\nimport com.minimalist.basic.entity.po.MFile;\nimport com.minimalist.basic.entity.po.MStorage;\nimport com.minimalist.basic.utils.ValidateUtil;\nimport org.springframework.stereotype.Service;\nimport org.springframework.web.multipart.MultipartFile;\n\n@Service\npublic class MinIOFileHandler implements FileHandler {\n\n    /**\n     * 是否是对应的处理类\n     * @param storageType 存储类型\n     * @return 处理类\n     */\n    @Override\n    public boolean isHandler(String storageType) {\n        return StorageEnum.StorageType.MINIO.getCode().equals(storageType);\n    }\n\n    /**\n     * 参数校验\n     * @param storageConfig JSON存储配置信息\n     */\n    @Override\n    public String valid(String storageConfig) {\n        MinIOFileEntity minIOFileEntity = JSONUtil.toBean(storageConfig, MinIOFileEntity.class);\n        ValidateUtil.valid(minIOFileEntity);\n        return storageConfig;\n    }\n\n    /**\n     * 单文件上传\n     * @param multipartFile 文件\n     * @param fileSource 文件来源\n     * @param storage 存储信息\n     * @return 文件信息\n     */\n    @Override\n    public MFile uploadFile(MultipartFile multipartFile, Integer fileSource, MStorage storage) {\n        //没做，做了需要引jar包，可自行实现\n        return null;\n    }\n\n    /**\n     * 删除文件\n     * @param file 文件信息\n     * @return 是否删除成功\n     */\n    @Override\n    public boolean deleteFile(MFile file, MStorage storage) {\n        //没做，做了需要引jar包，可自行实现\n        return false;\n    }\n\n    /**\n     * 移动文件\n     * @param file    文件信息\n     * @param storage 存储信息\n     * @return 是否成功\n     */\n    @Override\n    public boolean moveFile(MFile file, MStorage storage) {\n        //没做，做了需要引jar包，可自行实现\n        return false;\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/fileHandler/handler/impl/QiNiuFileHandler.java",
    "content": "package com.minimalist.basic.config.fileHandler.handler.impl;\n\nimport cn.hutool.core.io.file.FileNameUtil;\nimport cn.hutool.core.util.IdUtil;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.json.JSONUtil;\nimport com.minimalist.basic.config.exception.BusinessException;\nimport com.minimalist.basic.config.fileHandler.FileManager;\nimport com.minimalist.basic.config.fileHandler.entity.QiNiuFileEntity;\nimport com.minimalist.basic.config.fileHandler.handler.FileHandler;\nimport com.minimalist.basic.entity.enums.FileEnum;\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.entity.enums.StorageEnum;\nimport com.minimalist.basic.entity.po.MFile;\nimport com.minimalist.basic.entity.po.MStorage;\nimport com.minimalist.basic.mapper.MFileMapper;\nimport com.minimalist.basic.mapper.MStorageMapper;\nimport com.minimalist.basic.utils.*;\nimport com.qiniu.common.QiniuException;\nimport com.qiniu.http.Response;\nimport com.qiniu.storage.BucketManager;\nimport com.qiniu.storage.Configuration;\nimport com.qiniu.storage.Region;\nimport com.qiniu.storage.UploadManager;\nimport com.qiniu.util.Auth;\nimport com.qiniu.util.StringMap;\nimport lombok.extern.slf4j.Slf4j;\nimport net.coobird.thumbnailator.Thumbnails;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Service;\nimport org.springframework.web.multipart.MultipartFile;\nimport java.io.ByteArrayOutputStream;\n\n@Slf4j\n@Service\npublic class QiNiuFileHandler implements FileHandler {\n\n    @Autowired\n    private FileManager fileManager;\n\n    @Autowired\n    private MFileMapper fileMapper;\n\n    @Autowired\n    private MStorageMapper storageMapper;\n\n    /**\n     * 是否是对应的处理类\n     * @param storageType 存储类型\n     * @return 处理类\n     */\n    @Override\n    public boolean isHandler(String storageType) {\n        return StorageEnum.StorageType.QINIU.getCode().equals(storageType);\n    }\n\n    /**\n     * 参数校验\n     * @param storageConfig JSON存储配置信息\n     */\n    @Override\n    public String valid(String storageConfig) {\n        QiNiuFileEntity qiNiuFileEntity = JSONUtil.toBean(storageConfig, QiNiuFileEntity.class);\n        ValidateUtil.valid(qiNiuFileEntity);\n        return storageConfig;\n    }\n\n    /**\n     * 单文件上传\n     * @param multipartFile 文件\n     * @param fileSource 文件来源\n     * @param storage 存储信息\n     * @return 文件信息\n     */\n    @Override\n    public MFile uploadFile(MultipartFile multipartFile, Integer fileSource, MStorage storage) {\n        //根据文件来源，获取相对路径\n        String fileSourcePath = fileManager.getPathByFileSource(fileSource);\n        //文件后缀\n        String fileSuffix = FileNameUtil.extName(multipartFile.getOriginalFilename());\n        //新文件名\n        String newFileName = IdUtil.objectId() + \".\" + fileSuffix;\n        //文件信息\n        QiNiuFileEntity qnConfig = JSONUtil.toBean(storage.getStorageConfig(), QiNiuFileEntity.class);\n        MFile fileInfo = new MFile();\n        fileInfo.setFileId(UnqIdUtil.uniqueId());\n        fileInfo.setFileName(multipartFile.getOriginalFilename());\n        fileInfo.setNewFileName(newFileName);\n        fileInfo.setFileSize(multipartFile.getSize());\n        fileInfo.setFileType(multipartFile.getContentType());\n        //基础路径 = 租户ID\n        String basePath =  String.valueOf(TenantUtil.getTenantId());\n        fileInfo.setFileBasePath(basePath);\n        String fileKey = basePath + \"/\" + fileSourcePath + newFileName;\n        fileInfo.setFilePath(basePath + \"/\" + fileSourcePath);\n        fileInfo.setFileUrl(TextUtil.urlNormalize(qnConfig.getEndPoint() + \"/\" + fileKey));\n        fileInfo.setFileSource(fileSource);\n        fileInfo.setStorageId(storage.getStorageId());\n        //如果未指定文件来源，将状态置为正常\n        //因为这是从文件选择组件中上传的文件，不置为正常，在选择文件时查询条件是正常的才能被查出来\n        if (fileSource < CommonConstant.ZERO) {\n            fileInfo.setStatus(StatusEnum.STATUS_1.getCode());\n        }\n        try {\n            Auth auth = Auth.create(qnConfig.getAccessKey(), qnConfig.getSecretKey());\n            String upToken = auth.uploadToken(qnConfig.getBucketName());\n            Configuration cfg = new Configuration(Region.createWithRegionId(qnConfig.getRegionId()));\n            UploadManager uploadManager = new UploadManager(cfg);\n            StringMap params = new StringMap();\n            params.put(\"tenantId\", basePath);\n            Response response = uploadManager.put(multipartFile.getInputStream(), fileKey, upToken, params, null);\n            if (!response.isOK()) {\n                log.error(\"上传文件失败：{}\", JSONUtil.toJsonStr(response));\n                throw new BusinessException(FileEnum.ErrorMsg.FILE_UPLOAD_FAIL.getDesc());\n            }\n            //生成图片缩略图\n            if (StrUtil.isNotBlank(multipartFile.getContentType()) && multipartFile.getContentType().contains(\"image\")) {\n                ByteArrayOutputStream thumbnailOutputStream = new ByteArrayOutputStream();\n                Thumbnails.of(multipartFile.getInputStream())\n                        .scale(0.4)\n                        .toOutputStream(thumbnailOutputStream);\n                byte[] fileByte = thumbnailOutputStream.toByteArray();\n                String thFileName = \"thumbnail-\" + newFileName;\n                String thumbnailsFileKey = basePath + \"/\" + fileSourcePath + thFileName;\n                Response thumbnailsResponse = uploadManager.put(fileByte, thumbnailsFileKey, upToken, params, null, false);\n                if (thumbnailsResponse.isOK()) {\n                    fileInfo.setFileThUrl(qnConfig.getEndPoint() + \"/\" + thumbnailsFileKey);\n                    fileInfo.setFileThFilename(thFileName);\n                    fileInfo.setFileThSize((long) fileByte.length);\n                } else {\n                    log.error(\"上传缩略图失败：{}\", JSONUtil.toJsonStr(thumbnailsResponse));\n                    throw new BusinessException(FileEnum.ErrorMsg.FILE_THUMBNAILS_UPLOAD_FAIL.getDesc());\n                }\n            }\n        } catch (Exception e) {\n            //删除刚上传的图片\n            deleteFile(fileInfo, storage);\n            log.error(\"上传文件，异常：\", e);\n            throw new BusinessException(FileEnum.ErrorMsg.FILE_UPLOAD_FAIL.getDesc());\n        }\n        return fileInfo;\n    }\n\n    /**\n     * 删除文件\n     * @param file 文件信息\n     * @return 是否删除成功\n     */\n    @Override\n    public boolean deleteFile(MFile file, MStorage storage) {\n        try {\n            QiNiuFileEntity qnConfig = JSONUtil.toBean(storage.getStorageConfig(), QiNiuFileEntity.class);\n            Auth auth = Auth.create(qnConfig.getAccessKey(), qnConfig.getSecretKey());\n            Configuration cfg = new Configuration(Region.createWithRegionId(qnConfig.getRegionId()));\n            BucketManager bucketManager = new BucketManager(auth, cfg);\n            Response response = bucketManager.delete(qnConfig.getBucketName(), file.getFilePath() + file.getNewFileName());\n            if (!response.isOK()) {\n                log.warn(\"删除文件失败：{}\", JSONUtil.toJsonStr(response));\n                return false;\n            }\n            //如果存在缩略图，删除\n            if (StrUtil.isNotBlank(file.getFileThFilename())) {\n                //删除缩略图\n                bucketManager.delete(qnConfig.getBucketName(), file.getFilePath() + file.getFileThFilename());\n            }\n            return true;\n        } catch (Exception e) {\n            log.error(\"删除文件失败：\", e);\n        }\n        return false;\n    }\n\n    /**\n     * 移动文件\n     * @param file 文件信息\n     * @param storage 存储信息\n     * @return 是否成功\n     */\n    public boolean moveFile(MFile file, MStorage storage) {\n        try {\n            QiNiuFileEntity qnConfig = JSONUtil.toBean(storage.getStorageConfig(), QiNiuFileEntity.class);\n            Auth auth = Auth.create(qnConfig.getAccessKey(), qnConfig.getSecretKey());\n            Configuration cfg = new Configuration(Region.createWithRegionId(qnConfig.getRegionId()));\n            BucketManager bucketManager = new BucketManager(auth, cfg);\n            //源文件名\n            String fromKey = file.getFilePath() + file.getNewFileName();\n            //基础路径 = 租户ID\n            String basePath = String.valueOf(TenantUtil.getTenantId());\n            //根据文件来源，获取相对路径\n            String fileSourcePath = fileManager.getPathByFileSource(file.getFileSource());\n            //目标文件名\n            String toKey = basePath + \"/\" + fileSourcePath + file.getNewFileName();\n            //移动文件\n            Response response = bucketManager.move(qnConfig.getBucketName(), fromKey, qnConfig.getBucketName(), toKey, true);\n            if (!response.isOK()) {\n                log.warn(\"移动文件失败：{}\", JSONUtil.toJsonStr(response));\n                return false;\n            }\n            //修改文件url\n            file.setFileUrl(TextUtil.urlNormalize(qnConfig.getEndPoint() + \"/\" + toKey));\n            //如果有缩略图，需要将缩略图移动\n            if (StrUtil.isNotBlank(file.getFileThFilename())) {\n                //源文件名\n                String fromKeyTh = file.getFilePath() + file.getFileThFilename();\n                //目标文件名\n                String toKeyTh = basePath + \"/\" + fileSourcePath + file.getFileThFilename();\n                //移动文件\n                Response responseTh = bucketManager.move(qnConfig.getBucketName(), fromKeyTh, qnConfig.getBucketName(), toKeyTh, true);\n                if (!responseTh.isOK()) {\n                    log.warn(\"移动文件失败：{}\", JSONUtil.toJsonStr(responseTh));\n                    return false;\n                }\n                //修改缩略图url\n                file.setFileThUrl(TextUtil.urlNormalize(qnConfig.getEndPoint() + \"/\" + toKeyTh));\n            }\n            //修改文件路径\n            file.setFilePath(basePath + \"/\" + fileSourcePath);\n            return true;\n        } catch (QiniuException ex) {\n            log.error(\"移动文件失败，错误码：{}，错误信息：{}\", ex.code(), ex.response.toString());\n            log.error(\"移动文件失败：\", ex);\n        } catch (Exception e) {\n            log.error(\"移动文件失败：\", e);\n        }\n        return false;\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/mybatis/GeneratorCodeHandler.java",
    "content": "package com.minimalist.basic.config.mybatis;\n\nimport com.minimalist.basic.config.mybatis.bo.BaseEntity;\nimport com.mybatisflex.codegen.Generator;\nimport com.mybatisflex.codegen.config.GlobalConfig;\nimport com.mybatisflex.codegen.config.TableConfig;\nimport com.zaxxer.hikari.HikariDataSource;\n\n/**\n * 配置Mybatis-Flex生成代码\n */\npublic class GeneratorCodeHandler {\n\n    public static final String url = \"jdbc:mysql://127.0.0.1:3306/minimalist?characterEncoding=utf-8\";\n\n    public static final String username = \"root\";\n\n    public static final String password = \"123456\";\n\n    public static void main(String[] args) {\n        //配置数据源\n        HikariDataSource dataSource = new HikariDataSource();\n        dataSource.setJdbcUrl(url);\n        dataSource.setUsername(username);\n        dataSource.setPassword(password);\n        //项目根目录\n        String projectPath = System.getProperty(\"user.dir\");\n        //生成配置\n        GlobalConfig globalConfig = new GlobalConfig();\n        //设置根包\n        globalConfig.setBasePackage(\"com.minimalist.basic\");\n        //生成路径\n        globalConfig.setSourceDir(projectPath + \"\\\\generator\\\\java\");\n\n        /* ------------------------ 实体配置 ------------------------ */\n\n        //生成实体\n        globalConfig.setEntityGenerateEnable(true);\n        //使用lombok\n        globalConfig.setEntityWithLombok(true);\n        //设置项目的JDK版本，项目的JDK为14及以上时建议设置该项，小于14则可以不设置\n        globalConfig.setEntityJdkVersion(17);\n        //设置生成 entity 包名\n        globalConfig.setEntityPackage(\"com.minimalist.basic.entity.po\");\n        //自动填充\n        TableConfig tableConfig = new TableConfig();\n        tableConfig.setInsertListenerClass(InsertFullColumnHandler.class);\n        tableConfig.setUpdateListenerClass(UpdateFullColumnHandler.class);\n        globalConfig.setTableConfig(tableConfig);\n        //设置实体类的父类\n        globalConfig.setEntitySuperClass(BaseEntity.class);\n        //父类字段忽略\n        globalConfig.getStrategyConfig()\n                .setIgnoreColumns(\"id\", \"deleted\", \"create_time\", \"create_id\", \"update_id\", \"update_time\", \"version\");\n\n        /* ------------------------ mapper 配置 ------------------------ */\n\n        //设置生成 mapper\n        globalConfig.setMapperGenerateEnable(true);\n        //mapper包\n        globalConfig.setMapperPackage(\"com.minimalist.basic.mapper\");\n        //生成mapper xml文件\n        globalConfig.enableMapperXml();\n        //生成mapper.xml路径\n        globalConfig.setMapperXmlPath(projectPath + \"/generator/resources/mapper\");\n\n        /* ------------------------ 生成哪些表 ------------------------ */\n        globalConfig.setGenerateTable(\n                \"m_config\",\n                \"m_dept\",\n                \"m_dict\",\n                \"m_file\",\n                \"m_notice\",\n                \"m_perms\",\n                \"m_post\",\n                \"m_role\",\n                \"m_role_dept\",\n                \"m_role_perm\",\n                \"m_storage\",\n                \"m_tenant\",\n                \"m_tenant_package\",\n                \"m_tenant_package_perm\",\n                \"m_user\",\n                \"m_user_dept\",\n                \"m_user_post\",\n                \"m_user_role\"\n        );\n        //是否生成service和controller\n        globalConfig.setServiceGenerateEnable(true);\n        globalConfig.setServiceImplGenerateEnable(true);\n        globalConfig.setControllerGenerateEnable(true);\n\n        //通过 datasource 和 globalConfig 创建代码生成器\n        Generator generatorHandler = new Generator(dataSource, globalConfig);\n        generatorHandler.generate();\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/mybatis/InsertFullColumnHandler.java",
    "content": "package com.minimalist.basic.config.mybatis;\n\nimport cn.dev33.satoken.stp.SaTokenInfo;\nimport cn.dev33.satoken.stp.StpUtil;\nimport cn.hutool.core.util.ObjectUtil;\nimport com.minimalist.basic.config.mybatis.bo.BaseEntity;\nimport com.minimalist.basic.utils.CommonConstant;\nimport com.mybatisflex.annotation.InsertListener;\nimport org.springframework.stereotype.Component;\n\n/**\n * Mybatis-Flex 插入时字段自动填充。注：xml中和Db+Row插入不会生效需要手动set\n */\n@Component\npublic class InsertFullColumnHandler implements InsertListener {\n\n    @Override\n    public void onInsert(Object entity) {\n        BaseEntity t = (BaseEntity) entity;\n        SaTokenInfo tokenInfo = StpUtil.getTokenInfo();\n        if (ObjectUtil.isNotNull(tokenInfo) && ObjectUtil.isNotNull(tokenInfo.getLoginId())) {\n            long userId = Long.parseLong(tokenInfo.getLoginId().toString());\n            t.setCreateId(userId);\n            t.setUpdateId(userId);\n        } else {\n            t.setCreateId((long) CommonConstant.ZERO);\n            t.setUpdateId((long) CommonConstant.ZERO);\n        }\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/mybatis/MyBatisFlexConfiguration.java",
    "content": "package com.minimalist.basic.config.mybatis;\n\nimport com.minimalist.basic.utils.TenantUtil;\nimport com.mybatisflex.core.audit.AuditManager;\nimport com.mybatisflex.core.tenant.TenantManager;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.context.annotation.Configuration;\n\n@Configuration\npublic class MyBatisFlexConfiguration {\n\n    private static final Logger logger = LoggerFactory.getLogger(\"mybatis-flex-sql\");\n\n    public MyBatisFlexConfiguration() {\n        //开启审计功能\n        AuditManager.setAuditEnable(true);\n        //设置 SQL 审计收集器\n        AuditManager.setMessageCollector(auditMessage ->\n                logger.info(\"{},{} ms\", auditMessage.getFullSql(), auditMessage.getElapsedTime())\n        );\n\n        //获取租户ID，目前支持返回一个租户ID\n        TenantManager.setTenantFactory(() -> {\n            //校验系统多租户是否开启\n            if (!TenantUtil.checkTenantOnOff()) {\n                //未打开，忽略多租户\n                return null;\n            }\n\n            //放回当前要操作的租户ID\n            return new Object[]{ TenantUtil.getTenantId() };\n        });\n\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/mybatis/UpdateFullColumnHandler.java",
    "content": "package com.minimalist.basic.config.mybatis;\n\nimport cn.dev33.satoken.stp.SaTokenInfo;\nimport cn.dev33.satoken.stp.StpUtil;\nimport cn.hutool.core.util.ObjectUtil;\nimport com.minimalist.basic.config.mybatis.bo.BaseEntity;\nimport com.minimalist.basic.utils.CommonConstant;\nimport com.mybatisflex.annotation.UpdateListener;\nimport org.springframework.stereotype.Component;\n\n/**\n * Mybatis-Flex 修改时字段自动填充。注：xml中和Db+Row插入不会生效需要手动set\n */\n@Component\npublic class UpdateFullColumnHandler implements UpdateListener {\n\n    @Override\n    public void onUpdate(Object entity) {\n        BaseEntity t = (BaseEntity) entity;\n        SaTokenInfo tokenInfo = StpUtil.getTokenInfo();\n        if (ObjectUtil.isNotNull(tokenInfo) && ObjectUtil.isNotNull(tokenInfo.getLoginId())) {\n            long userId = Long.parseLong(tokenInfo.getLoginId().toString());\n            t.setUpdateId(userId);\n        } else {\n            t.setUpdateId((long) CommonConstant.ZERO);\n        }\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/mybatis/bo/BaseEntity.java",
    "content": "package com.minimalist.basic.config.mybatis.bo;\n\nimport com.mybatisflex.annotation.Column;\nimport com.mybatisflex.annotation.Id;\nimport com.mybatisflex.annotation.KeyType;\nimport lombok.Getter;\nimport lombok.Setter;\nimport java.time.LocalDateTime;\n\n@Getter\n@Setter\npublic class BaseEntity {\n\n    /** ID自增 */\n    @Id(keyType = KeyType.Auto)\n    private Long id;\n\n    /** 逻辑删除 */\n    @Column(isLogicDelete = true)\n    private Boolean deleted;\n\n    /** 创建人ID */\n    private Long createId;\n\n    /** 更新人ID */\n    private Long updateId;\n\n    /** 创建时间 */\n    @Column(onInsertValue = \"now()\")\n    private LocalDateTime createTime;\n\n    /** 更新时间 */\n    @Column(onInsertValue = \"now()\", onUpdateValue = \"now()\")\n    private LocalDateTime updateTime;\n\n    /** 版本号 */\n    @Column(version = true, onInsertValue = \"0\", onUpdateValue = \"version + 1\")\n    private Integer version;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/mybatis/bo/PageReq.java",
    "content": "package com.minimalist.basic.config.mybatis.bo;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\n/**\n * 分页请求\n */\n@Getter\n@Setter\npublic class PageReq {\n\n    /** 页码 */\n    private Long pageNum = 1L;\n\n    /** 分页条数 默认10条 */\n    private Long pageSize = 10L;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/mybatis/bo/PageResp.java",
    "content": "package com.minimalist.basic.config.mybatis.bo;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\n\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * 分页响应\n */\n@Data\n@AllArgsConstructor\npublic class PageResp<T> {\n\n    /** 数据 */\n    private List<T> records = Collections.emptyList();\n\n    /** 总条数 */\n    private long total = 0;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/redis/RedisManager.java",
    "content": "package com.minimalist.basic.config.redis;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.redisson.api.RBucket;\nimport org.redisson.api.RLock;\nimport org.redisson.api.RTopic;\nimport org.redisson.api.RedissonClient;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Component;\nimport java.math.BigDecimal;\nimport java.util.concurrent.TimeUnit;\n\n@Slf4j\n@Component\npublic class RedisManager {\n\n    @Autowired\n    private RedissonClient redissonClient;\n\n    /**\n     * 获取缓存\n     * @param key 缓存key\n     * @param <T> 泛型\n     * @return 泛型\n     */\n    public <T> T get(String key) {\n        RBucket<T> bucket = redissonClient.getBucket(key);\n        return bucket.get();\n    }\n\n    /**\n     * 获取缓存 并删除\n     * @param key 缓存key\n     * @param <T> 泛型\n     * @return 泛型\n     */\n    public <T> T getAndDelete(String key) {\n        RBucket<T> bucket = redissonClient.getBucket(key);\n        return bucket.getAndDelete();\n    }\n\n    /**\n     * 删除缓存\n     * @param key 缓存key\n     */\n    public void delete(String key) {\n        redissonClient.getBucket(key).delete();\n    }\n\n    /**\n     * 存放缓存\n     * @param key 缓存key\n     * @param value 缓存value\n     * @param seconds 过期时间: 秒\n     * @param <T> 泛型\n     */\n    public <T> void set(String key, T value, int seconds) {\n        RBucket<T> bucket = redissonClient.getBucket(key);\n        bucket.set(value, seconds, TimeUnit.SECONDS);\n    }\n\n    /**\n     * 获取分布式锁\n     * @param key 缓存key\n     * @param waitTime 获取锁时等待的时间\n     * @param leaseTime 持有锁的时间\n     * @return true获取到锁，false未获取到\n     */\n    public boolean tryLock(String key, long waitTime, long leaseTime) {\n        RLock lock = redissonClient.getLock(key);\n        try {\n            return lock.tryLock(waitTime, leaseTime, TimeUnit.MILLISECONDS);\n        } catch (InterruptedException e) {\n            log.error(\"尝试获取分布式锁异常：\", e);\n        }\n        return false;\n    }\n\n    /**\n     * 释放锁\n     * @param key 缓存key\n     */\n    public void unLock(String key) {\n        RLock lock = redissonClient.getLock(key);\n        //检查是否已加锁，已加锁返回true\n        if (lock.isLocked()) {\n            //检查是否是当前线程加的锁，是同一线程则释放锁\n            if (lock.isHeldByCurrentThread()) {\n                lock.unlock();\n            }\n        }\n    }\n\n    /**\n     * 随机秒数\n     * @return 秒数\n     */\n    public int randomSeconds() {\n        return BigDecimal.valueOf(Math.random()).multiply(new BigDecimal(20)).intValue();\n    }\n\n    /**\n     * 发布消息\n     * @param topic 主题\n     * @param message 消息\n     */\n    public void publishMessage(String topic, String message) {\n        RTopic rTopic = redissonClient.getTopic(topic);\n        rTopic.publish(message);\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/redis/RedissonConfiguration.java",
    "content": "package com.minimalist.basic.config.redis;\n\nimport cn.hutool.core.util.StrUtil;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;\nimport org.redisson.Redisson;\nimport org.redisson.api.RedissonClient;\nimport org.redisson.codec.JsonJacksonCodec;\nimport org.redisson.config.Config;\nimport org.redisson.config.SingleServerConfig;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.core.env.Environment;\n\n@Configuration\npublic class RedissonConfiguration {\n\n    @Autowired\n    private Environment environment;\n\n    /**\n     * redisson单节点\n     * @return RedissonClient\n     */\n    @Bean\n    public RedissonClient getSingleRedissonClient() {\n        Config config = new Config();\n        String address = environment.getProperty(\"redisson.address\");\n        String password = environment.getProperty(\"redisson.password\");\n        Integer pingInterval = environment.getProperty(\"redisson.pingInterval\", Integer.class);\n        Integer connectionPoolSize = environment.getProperty(\"redisson.connectionPoolSize\", Integer.class);\n        Integer connectionMinimumIdleSize = environment.getProperty(\"redisson.connectionMinimumIdleSize\", Integer.class);\n        Integer database = environment.getProperty(\"redisson.database\", Integer.class);\n        SingleServerConfig singleServerConfig = config.useSingleServer()\n                .setAddress(address)\n                .setKeepAlive(true)\n                .setDatabase(database)\n                .setConnectionPoolSize(connectionPoolSize)\n                .setConnectionMinimumIdleSize(connectionMinimumIdleSize)\n                .setPingConnectionInterval(pingInterval);\n        if (StrUtil.isNotBlank(password)) {\n            singleServerConfig.setPassword(password);\n        }\n        //设置redisson序列化与反序列化，jackson增加java时间模块处理\n        JsonJacksonCodec jsonJacksonCodec = new JsonJacksonCodec();\n        ObjectMapper objectMapper = jsonJacksonCodec.getObjectMapper();\n        objectMapper.registerModule(new JavaTimeModule());\n        config.setCodec(jsonJacksonCodec);\n        return Redisson.create(config);\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/resubmit/ReSubmit.java",
    "content": "package com.minimalist.basic.config.resubmit;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n@Retention(RetentionPolicy.RUNTIME)\n@Target(ElementType.METHOD)\npublic @interface ReSubmit {\n\n    /**\n     * 持有锁的租期时间\n     * 第一次请求和第二次请求，时间相隔 leaseTime 毫秒算重复请求，将被拒绝\n     */\n    long leaseTime() default 3000;\n\n    /**\n     * 获取锁时，等待 waitTime 毫秒，超过 waitTimeout 毫秒，就不再尝试获取锁\n     * 默认 0 不等待，即获取锁时只要获取不到就返回\n     */\n    long waitTime() default 0;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/resubmit/ReSubmitAspect.java",
    "content": "package com.minimalist.basic.config.resubmit;\n\nimport cn.hutool.core.map.MapUtil;\nimport cn.hutool.core.util.ArrayUtil;\nimport cn.hutool.core.util.ClassUtil;\nimport cn.hutool.core.util.ObjectUtil;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.crypto.SecureUtil;\nimport cn.hutool.extra.servlet.JakartaServletUtil;\nimport cn.hutool.json.JSONObject;\nimport cn.hutool.json.JSONUtil;\nimport com.minimalist.basic.config.redis.RedisManager;\nimport com.minimalist.basic.entity.enums.RespEnum;\nimport com.minimalist.basic.utils.RedisKeyConstant;\nimport jakarta.servlet.http.HttpServletRequest;\nimport lombok.extern.slf4j.Slf4j;\nimport org.aspectj.lang.ProceedingJoinPoint;\nimport org.aspectj.lang.annotation.Around;\nimport org.aspectj.lang.annotation.Aspect;\nimport org.aspectj.lang.reflect.MethodSignature;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.stereotype.Component;\nimport org.springframework.web.context.request.RequestContextHolder;\nimport org.springframework.web.context.request.ServletRequestAttributes;\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Parameter;\nimport java.util.Comparator;\nimport java.util.TreeMap;\n\n/**\n * 防重复提交处理\n */\n@Slf4j\n@Aspect\n@Component\npublic class ReSubmitAspect {\n\n    @Autowired\n    private RedisManager redisManager;\n\n    @Around(\"@annotation(re)\")\n    public Object around(ProceedingJoinPoint joinPoint, ReSubmit re) throws Throwable {\n        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();\n        ReSubmit reSubmit = method.getAnnotation(ReSubmit.class);\n        //如果不需要防重复提交校验，则放行\n        if (ObjectUtil.isNull(reSubmit)) {\n            return joinPoint.proceed();\n        }\n        //获取请求参数\n        String lockKey = getReqParams(joinPoint, method);\n        //根据请求参数，加锁\n        boolean acquireLock = redisManager.tryLock(lockKey, reSubmit.waitTime(), reSubmit.leaseTime());\n        //重复提交\n        if (!acquireLock) {\n            return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(RespEnum.RESUBMIT_ERROR.getDesc());\n        }\n        try {\n            return joinPoint.proceed();\n        } finally {\n            redisManager.unLock(lockKey);\n        }\n    }\n\n    /**\n     * 获取请求参数\n     * @param joinPoint joinPoint\n     * @return 缓存key\n     */\n    private String getReqParams(ProceedingJoinPoint joinPoint, Method method) {\n        //获取请求参数\n        Object [] args = joinPoint.getArgs();\n        //方法的参数\n        Parameter[] parameters = method.getParameters();\n        //参数有序存放\n        TreeMap<String, Object> paramTreeMap = MapUtil.newTreeMap(Comparator.naturalOrder());\n        //参数转为key\n        if (ArrayUtil.isNotEmpty(args) && ArrayUtil.isNotEmpty(parameters) && args.length == parameters.length) {\n            //取参数\n            for (int i = 0; i < args.length; i++) {\n                //参数值\n                Object arg = args[i];\n                //参数\n                Parameter param = parameters[i];\n                //是否为基本数据类型，可能是单参数\n                boolean basicType = ClassUtil.isBasicType(arg.getClass());\n                if (basicType || arg instanceof String) {\n                    paramTreeMap.put(param.getName(), arg);\n                } else {\n                    //其余参数视为对象，转Map\n                    String json = JSONUtil.toJsonStr(arg);\n                    JSONObject jsonObject = JSONUtil.parseObj(json);\n                    paramTreeMap.putAll(jsonObject);\n                }\n            }\n            return StrUtil.indexedFormat(RedisKeyConstant.REPEAT_SUBMIT_KEY, SecureUtil.sha256(JSONUtil.toJsonStr(paramTreeMap)));\n        }\n        //获取IP\n        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();\n        //使用 IP:包.类名.方法名 生成一个key\n        String sha256 = SecureUtil.sha256(JakartaServletUtil.getClientIP(request) + \":\" + method.getDeclaringClass().getName() + \".\" + method.getName());\n        return StrUtil.indexedFormat(RedisKeyConstant.REPEAT_SUBMIT_KEY, sha256);\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/swagger/ParameterHandler.java",
    "content": "package com.minimalist.basic.config.swagger;\n\nimport cn.hutool.core.util.ObjectUtil;\nimport io.swagger.v3.oas.models.parameters.Parameter;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springdoc.core.customizers.ParameterCustomizer;\nimport org.springframework.core.MethodParameter;\n\n/**\n * 对单字段的枚举进行处理\n * 单字段对应的swagger注解：@Parameter\n */\n@Slf4j\npublic class ParameterHandler extends SchemaEnumHandler implements ParameterCustomizer {\n\n    @Override\n    public Parameter customize(Parameter property, MethodParameter parameter) {\n        //检查是否包含自定义注解\n        SchemaEnum schemaEnumAnnotation = getSchemaEnumAnnotation(parameter.getParameterAnnotations());\n        if (ObjectUtil.isNull(schemaEnumAnnotation)) {\n            return property;\n        }\n        String columnDescription = getColumnDescription(schemaEnumAnnotation, property.getDescription());\n        property.setDescription(columnDescription);\n        return property;\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/swagger/PropertyHandler.java",
    "content": "package com.minimalist.basic.config.swagger;\n\nimport cn.hutool.core.util.ObjectUtil;\nimport io.swagger.v3.core.converter.AnnotatedType;\nimport io.swagger.v3.oas.models.media.Schema;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springdoc.core.customizers.PropertyCustomizer;\n\n/**\n * 对实体类中的枚举进行处理\n * 实体类中对应的swagger注解：@Schema\n */\n@Slf4j\npublic class PropertyHandler extends SchemaEnumHandler implements PropertyCustomizer {\n\n    @Override\n    public Schema customize(Schema property, AnnotatedType type) {\n        //检查是否包含自定义注解\n        SchemaEnum schemaEnumAnnotation = getSchemaEnumAnnotation(type.getCtxAnnotations());\n        if (ObjectUtil.isNull(schemaEnumAnnotation)) {\n            return property;\n        }\n        String columnDescription = getColumnDescription(schemaEnumAnnotation, property.getDescription());\n        property.setDescription(columnDescription);\n        return property;\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/swagger/SchemaEnum.java",
    "content": "package com.minimalist.basic.config.swagger;\n\nimport java.lang.annotation.*;\n\n/**\n * swagger 实体中枚举值处理注解\n */\n@Target({ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER})\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\npublic @interface SchemaEnum {\n\n    /** 枚举中取key值的方法名 */\n    String keyMethodName() default \"getCode\";\n\n    /** 枚举中取value值的方法名 */\n    String valueMethodName() default \"getDesc\";\n\n    /** 枚举 */\n    Class<? extends Enum> implementation();\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/swagger/SchemaEnumHandler.java",
    "content": "package com.minimalist.basic.config.swagger;\n\nimport cn.hutool.core.util.ArrayUtil;\nimport cn.hutool.core.util.ObjectUtil;\nimport cn.hutool.core.util.StrUtil;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.core.annotation.AnnotationUtils;\n\nimport java.lang.annotation.Annotation;\nimport java.lang.reflect.Method;\nimport java.util.Optional;\nimport java.util.StringJoiner;\n\n@Slf4j\npublic class SchemaEnumHandler {\n\n    /**\n     * 查找注解\n     * @param annotations 注解数组\n     * @return SchemaEnum注解\n     */\n    protected SchemaEnum getSchemaEnumAnnotation(Annotation[] annotations) {\n        if (ArrayUtil.isEmpty(annotations)) {\n            return null;\n        }\n        for (Annotation annotation : annotations) {\n            SchemaEnum anno = AnnotationUtils.getAnnotation(annotation, SchemaEnum.class);\n            if (ObjectUtil.isNotNull(anno)) {\n                return anno;\n            }\n        }\n        return null;\n    }\n\n    /**\n     * 获取枚举里的方法，并执行取值\n     * @param enumClass 枚举\n     * @param method 方法名\n     * @return 值\n     */\n    protected Object getEnumMethodValue(Enum enumClass, String method) {\n        try {\n            Method m = enumClass.getClass().getMethod(method);\n            return m.invoke(enumClass);\n        } catch (Exception e) {\n            log.warn(\"swagger自定义处理，获取枚举中 {} 方法异常，\", method, e);\n        }\n        return null;\n    }\n\n    /**\n     * 根据注解获取描述\n     * @param schemaEnumAnnotation swagger自定义枚举处理注解\n     * @param sourceDescription 字段swagger原始描述\n     * @return 文字描述\n     */\n    protected String getColumnDescription(SchemaEnum schemaEnumAnnotation, String sourceDescription) {\n        //枚举\n        Class<? extends Enum> enumClass = schemaEnumAnnotation.implementation();\n        //将枚举转为文字描述\n        StringJoiner sj = new StringJoiner(\", \");\n        for (Enum enumConstant : enumClass.getEnumConstants()) {\n            Object key = getEnumMethodValue(enumConstant, schemaEnumAnnotation.keyMethodName());\n            Object value = getEnumMethodValue(enumConstant, schemaEnumAnnotation.valueMethodName());\n            sj.add(key + \": \" + value);\n        }\n        return Optional.ofNullable(sourceDescription)\n                .map(s -> s + \" -> \")\n                .orElse(\"\")\n                + sj;\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/swagger/SwaggerConfig.java",
    "content": "package com.minimalist.basic.config.swagger;\n\nimport io.swagger.v3.oas.models.OpenAPI;\nimport io.swagger.v3.oas.models.info.Contact;\nimport io.swagger.v3.oas.models.info.Info;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n@Configuration\npublic class SwaggerConfig {\n\n    /** 标题 */\n    @Value(\"${springdoc.title}\")\n    private String title;\n\n    /** 作者 */\n    @Value(\"${springdoc.authorName}\")\n    private String authorName;\n\n    /** 作者主页 */\n    @Value(\"${springdoc.authorUrl}\")\n    private String authorUrl;\n\n    /** 作者Email */\n    @Value(\"${springdoc.authorEmail}\")\n    private String authorEmail;\n\n    @Bean\n    public OpenAPI openApi() {\n        return new OpenAPI().info(new Info()\n                //文档页面标题\n                .title(title)\n                //描述\n                .description(title + \"接口文档\")\n                //版本号\n                .version(\"1.0.0-SNAPSHOT\")\n                //作者信息\n                .contact(new Contact().name(authorName).email(authorEmail).url(authorUrl)));\n    }\n\n    @Bean\n    @ConditionalOnMissingBean\n    public PropertyHandler propertyHandler() {\n        return new PropertyHandler();\n    }\n\n    @Bean\n    @ConditionalOnMissingBean\n    public ParameterHandler parameterHandler() {\n        return new ParameterHandler();\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/tenant/TenantDatasourceInterceptor.java",
    "content": "package com.minimalist.basic.config.tenant;\n\nimport cn.hutool.core.util.ObjectUtil;\nimport com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;\nimport com.minimalist.basic.entity.enums.TenantEnum;\nimport com.minimalist.basic.entity.vo.tenant.TenantVO;\nimport com.minimalist.basic.utils.CommonConstant;\nimport com.minimalist.basic.utils.TenantUtil;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.web.servlet.HandlerInterceptor;\n\n/**\n * 租户数据源切换拦截器\n */\n@Slf4j\npublic class TenantDatasourceInterceptor implements HandlerInterceptor {\n\n    @Override\n    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {\n        //校验系统多租户是否开启\n        if (!TenantUtil.checkTenantOnOff()) {\n            //未打开，忽略多租户，继续使用主数据源\n            return HandlerInterceptor.super.preHandle(request, response, handler);\n        }\n\n        String requestURI = request.getRequestURI();\n        log.info(\"切换数据源，访问非/basic接口，经过多数据源拦截器, 当前路径是：{}\", requestURI);\n\n        //获取要操作的租户ID\n        long tenantId = TenantUtil.getTenantId();\n\n        //如果是系统租户，使用主数据源\n        if (CommonConstant.ZERO == tenantId) {\n            log.info(\"切换数据源，[系统租户] => 继续使用主数据源，租户ID：{}\", tenantId);\n        } else {\n            //如果非系统租户，检查是否需要切换到租户数据源\n            TenantVO tenantVO = CommonConstant.tenantMap.get(tenantId);\n            //该租户数据隔离方式 != master，表示需要切换到租户自己的数据源\n            if (ObjectUtil.isNotNull(tenantVO) && !TenantEnum.MASTER.equals(tenantVO.getDatasource())) {\n                log.info(\"切换数据源，[其他租户] => 切换到租户数据源，租户ID：{}\", tenantId);\n                DynamicDataSourceContextHolder.push(String.valueOf(tenantId));\n            }\n        }\n        return HandlerInterceptor.super.preHandle(request, response, handler);\n    }\n\n    @Override\n    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {\n        //清空线程内数据源信息\n        DynamicDataSourceContextHolder.clear();\n        log.info(\"切换数据源，清空数据源\");\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/tenant/TenantIgnore.java",
    "content": "package com.minimalist.basic.config.tenant;\n\nimport java.lang.annotation.*;\n\n/**\n * 注解加到方法上，标识此方法忽略多租户（异步方法无效）\n */\n@Target({ElementType.METHOD})\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\npublic @interface TenantIgnore {\n\n    /** 多租户字段 */\n    String TENANT_ID = \"tenant_id\";\n\n    /** 租户切换，多租户字段 */\n    String CHANGE_TENANT_ID = \"change_tenant_id\";\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/tenant/TenantIgnoreAspect.java",
    "content": "package com.minimalist.basic.config.tenant;\n\nimport cn.hutool.core.util.ObjectUtil;\nimport com.mybatisflex.core.tenant.TenantManager;\nimport lombok.extern.slf4j.Slf4j;\nimport org.aspectj.lang.ProceedingJoinPoint;\nimport org.aspectj.lang.annotation.Around;\nimport org.aspectj.lang.annotation.Aspect;\nimport org.aspectj.lang.reflect.MethodSignature;\nimport org.springframework.stereotype.Component;\nimport java.lang.reflect.Method;\n\n/**\n * 忽略多租户处理\n */\n@Slf4j\n@Aspect\n@Component\npublic class TenantIgnoreAspect {\n\n    @Around(\"@annotation(tenantIgnore)\")\n    public Object around(ProceedingJoinPoint joinPoint, TenantIgnore tenantIgnore) throws Throwable {\n        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();\n        TenantIgnore it = method.getAnnotation(TenantIgnore.class);\n        if (ObjectUtil.isNull(it)) {\n            return joinPoint.proceed();\n        }\n        try {\n            //忽略多租户\n            TenantManager.ignoreTenantCondition();\n            //执行目标方法\n            return joinPoint.proceed();\n        } finally {\n            //恢复多租户\n            TenantManager.restoreTenantCondition();\n        }\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/tenant/TenantInit.java",
    "content": "package com.minimalist.basic.config.tenant;\n\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.util.ObjectUtil;\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.entity.po.MConfig;\nimport com.minimalist.basic.entity.po.MStorage;\nimport com.minimalist.basic.entity.po.MTenant;\nimport com.minimalist.basic.entity.po.MTenantDatasource;\nimport com.minimalist.basic.entity.vo.storage.StorageVO;\nimport com.minimalist.basic.entity.vo.tenant.TenantDatasourceVO;\nimport com.minimalist.basic.entity.vo.tenant.TenantVO;\nimport com.minimalist.basic.manager.TenantManager;\nimport com.minimalist.basic.mapper.MConfigMapper;\nimport com.minimalist.basic.mapper.MStorageMapper;\nimport com.minimalist.basic.mapper.MTenantDatasourceMapper;\nimport com.minimalist.basic.mapper.MTenantMapper;\nimport com.minimalist.basic.utils.CommonConstant;\nimport com.mybatisflex.core.query.QueryWrapper;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.ApplicationArguments;\nimport org.springframework.boot.ApplicationRunner;\nimport org.springframework.stereotype.Component;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\n@Component\npublic class TenantInit implements ApplicationRunner {\n\n    @Autowired\n    private MTenantMapper tenantMapper;\n\n    @Autowired\n    private MTenantDatasourceMapper tenantDatasourceMapper;\n\n    @Autowired\n    private TenantManager tenantManager;\n\n    @Autowired\n    private MStorageMapper storageMapper;\n\n    @Autowired\n    private MConfigMapper configMapper;\n\n    @Override\n    public void run(ApplicationArguments args) throws Exception {\n        //查询系统多租户开关\n        MConfig mConfig = configMapper.selectConfigByConfigKey(CommonConstant.SYSTEM_CONFIG_TENANT, StatusEnum.STATUS_1.getCode());\n        if (ObjectUtil.isNotNull(mConfig)) {\n            boolean tenantOnOff = Boolean.parseBoolean(mConfig.getConfigValue());\n            if (!tenantOnOff) {\n                //未打开多租户开关，忽略多租户\n                return;\n            }\n        }\n\n        List<MTenant> mTenants = tenantMapper.selectListByQuery(QueryWrapper.create()\n                .eq(MTenant::getStatus, StatusEnum.STATUS_1.getCode()));\n        //租户数据库隔离数据源\n        List<MTenantDatasource> tenantDatasourceList = tenantDatasourceMapper.selectAll();\n        Map<Long, MTenantDatasource> tenantDatasourceMap = tenantDatasourceList.stream().collect(Collectors.toMap(MTenantDatasource::getTenantId, Function.identity()));\n        //租户文件存储\n        List<MStorage> mStorages = storageMapper.selectListByQuery(QueryWrapper.create()\n                .eq(MStorage::getStatus, StatusEnum.STATUS_1.getCode()));\n        Map<Long, MStorage> storageMap = mStorages.stream().collect(Collectors.toMap(MStorage::getStorageId, Function.identity()));\n        for (MTenant tenant : mTenants) {\n            TenantVO tenantVO = BeanUtil.copyProperties(tenant, TenantVO.class);\n            //查询租户数据源\n            if (tenantDatasourceMap.containsKey(tenant.getTenantId())) {\n                //动态添加数据源\n                MTenantDatasource tenantDatasource = tenantDatasourceMap.get(tenant.getTenantId());\n                TenantDatasourceVO tenantDatasourceVO = BeanUtil.copyProperties(tenantDatasource, TenantDatasourceVO.class);\n                tenantManager.dynamicAddDatasource(tenant.getTenantId().toString(), tenantDatasourceVO);\n                //租户数据源信息\n                tenantVO.setTenantDatasource(tenantDatasourceVO);\n            }\n            //租户文件存储\n            MStorage storage = storageMap.get(tenant.getStorageId());\n            tenantVO.setStorage(BeanUtil.copyProperties(storage, StorageVO.class));\n            //缓存租户信息\n            CommonConstant.tenantMap.put(tenant.getTenantId(), tenantVO);\n        }\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/tenant/TenantWebMvcConfig.java",
    "content": "package com.minimalist.basic.config.tenant;\n\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.web.servlet.config.annotation.InterceptorRegistry;\nimport org.springframework.web.servlet.config.annotation.WebMvcConfigurer;\n\n@Configuration\npublic class TenantWebMvcConfig implements WebMvcConfigurer {\n\n    @Override\n    public void addInterceptors(InterceptorRegistry registry) {\n        registry.addInterceptor(new TenantDatasourceInterceptor())\n                //排除/basic接口对数据源的限制，/basic只能使用master数据源\n                .excludePathPatterns(\"/basic/**\")\n                .addPathPatterns(\"/**\");\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/trace/TraceIdInterceptor.java",
    "content": "package com.minimalist.basic.config.trace;\n\nimport cn.hutool.core.util.IdUtil;\nimport com.minimalist.basic.utils.CommonConstant;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport org.slf4j.MDC;\nimport org.springframework.stereotype.Component;\nimport org.springframework.web.servlet.HandlerInterceptor;\n\n/**\n * 链路追踪拦截器\n * 请求前添加traceId并放到响应头中，请求后将清除\n */\n@Component\npublic class TraceIdInterceptor implements HandlerInterceptor {\n\n    @Override\n    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {\n        String traceId = IdUtil.randomUUID();\n        MDC.put(CommonConstant.TRACE_ID, traceId);\n        response.setHeader(CommonConstant.TRACE_ID, traceId);\n        return HandlerInterceptor.super.preHandle(request, response, handler);\n    }\n\n    @Override\n    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {\n        MDC.clear();\n    }\n\n}"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/config/trace/WebMvcConfig.java",
    "content": "package com.minimalist.basic.config.trace;\n\nimport jakarta.annotation.Resource;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.web.servlet.config.annotation.InterceptorRegistry;\nimport org.springframework.web.servlet.config.annotation.WebMvcConfigurer;\n\n@Configuration\npublic class WebMvcConfig implements WebMvcConfigurer {\n\n    @Resource\n    private TraceIdInterceptor traceIdInterceptor;\n\n    @Override\n    public void addInterceptors(InterceptorRegistry registry) {\n        WebMvcConfigurer.super.addInterceptors(registry);\n        registry.addInterceptor(traceIdInterceptor);\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/controller/ConfigController.java",
    "content": "package com.minimalist.basic.controller;\n\nimport cn.dev33.satoken.annotation.SaCheckPermission;\nimport com.minimalist.basic.config.mybatis.bo.PageResp;\nimport com.minimalist.basic.entity.vo.config.ConfigQueryVO;\nimport com.minimalist.basic.entity.vo.config.ConfigVO;\nimport com.minimalist.basic.service.ConfigService;\nimport com.minimalist.basic.utils.Add;\nimport com.minimalist.basic.utils.Update;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.validation.constraints.NotNull;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\n@Validated\n@RestController\n@Tag(name = \"参数配置管理\")\n@RequestMapping(\"/basic/config\")\npublic class ConfigController {\n\n    @Autowired\n    private ConfigService configService;\n\n    @PostMapping(\"/addConfig\")\n    @SaCheckPermission(\"basic:config:add\")\n    @Operation(summary = \"添加参数\")\n    public ResponseEntity<Void> addConfig(@RequestBody @Validated(Add.class) ConfigVO configVO) {\n        configService.addConfig(configVO);\n        return ResponseEntity.ok().build();\n    }\n\n    @DeleteMapping(\"/deleteConfigByConfigId\")\n    @SaCheckPermission(\"basic:config:delete\")\n    @Operation(summary = \"删除参数 -> 根据参数ID删除\")\n    public ResponseEntity<Void> deleteConfigByConfigId(@RequestParam(\"configId\")\n                                                    @NotNull(message = \"参数ID不能为空\")\n                                                    @Parameter(name = \"configId\", required = true, description = \"参数ID\") Long configId) {\n        configService.deleteConfigByConfigId(configId);\n        return ResponseEntity.ok().build();\n    }\n\n    @PutMapping(\"/updateConfigByConfigId\")\n    @SaCheckPermission(\"basic:config:update\")\n    @Operation(summary = \"修改参数\")\n    public ResponseEntity<Void> updateConfigByConfigId(@RequestBody @Validated(Update.class) ConfigVO configVO) {\n        configService.updateConfigByConfigId(configVO);\n        return ResponseEntity.ok().build();\n    }\n\n    @GetMapping(\"/getPageConfigList\")\n    @SaCheckPermission(\"basic:config:get\")\n    @Operation(summary = \"查询参数配置列表(分页)\")\n    public ResponseEntity<PageResp<ConfigVO>> getPageConfigList(ConfigQueryVO queryVO) {\n        return ResponseEntity.ok(configService.getPageConfigList(queryVO));\n    }\n\n    @GetMapping(\"/getConfigByConfigId/{configId}\")\n    @SaCheckPermission(\"basic:config:get\")\n    @Operation(summary = \"根据参数ID查询参数\")\n    public ResponseEntity<ConfigVO> getConfigByConfigId(@PathVariable(value = \"configId\")\n                                                  @NotNull(message = \"参数ID不能为空\")\n                                                  @Parameter(name = \"configId\", description = \"参数ID\", required = true) Long configId) {\n        return ResponseEntity.ok(configService.getConfigByConfigId(configId));\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/controller/DeptController.java",
    "content": "package com.minimalist.basic.controller;\n\nimport cn.dev33.satoken.annotation.SaCheckPermission;\nimport com.minimalist.basic.entity.vo.dept.DeptQueryVO;\nimport com.minimalist.basic.entity.vo.dept.DeptVO;\nimport com.minimalist.basic.service.DeptService;\nimport com.minimalist.basic.utils.Add;\nimport com.minimalist.basic.utils.Update;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.validation.constraints.NotNull;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\nimport java.util.List;\n\n@Validated\n@RestController\n@RequestMapping(\"/basic/dept\")\n@Tag(name = \"部门管理\")\npublic class DeptController {\n\n    @Autowired\n    private DeptService deptService;\n\n    @PostMapping(\"/addDept\")\n    @SaCheckPermission(\"basic:dept:add\")\n    @Operation(summary = \"添加部门\")\n    public ResponseEntity<Void> addDept(@RequestBody @Validated(Add.class) DeptVO deptVO) {\n        deptService.addDept(deptVO);\n        return ResponseEntity.ok().build();\n    }\n\n    @DeleteMapping(\"/deleteDeptByDeptId\")\n    @SaCheckPermission(\"basic:dept:delete\")\n    @Operation(summary = \"删除部门 -> 根据部门ID删除\")\n    public ResponseEntity<Void> deleteDeptByDeptId(@RequestParam(\"deptId\")\n                                                   @NotNull(message = \"部门ID不能为空\")\n                                                   @Parameter(name = \"deptId\", required = true, description = \"部门ID\") Long deptId) {\n        deptService.deleteDeptByDeptId(deptId);\n        return ResponseEntity.ok().build();\n    }\n\n    @PutMapping(\"/updateDeptByDeptId\")\n    @SaCheckPermission(\"basic:dept:update\")\n    @Operation(summary = \"修改部门 -> 根据部门ID修改\")\n    public ResponseEntity<Void> updateDeptByDeptId(@RequestBody @Validated(Update.class) DeptVO deptVO) {\n        deptService.updateDeptByDeptId(deptVO);\n        return ResponseEntity.ok().build();\n    }\n\n    @GetMapping(\"/getDeptList\")\n    @SaCheckPermission(\"basic:dept:get\")\n    @Operation(summary = \"查询部门列表(不分页，获取全部数据) -> 部门管理使用\")\n    public ResponseEntity<List<DeptVO>> getDeptList(DeptQueryVO queryVO) {\n        return ResponseEntity.ok(deptService.getDeptList(queryVO));\n    }\n\n    @GetMapping(\"/getEnableDeptList\")\n    @SaCheckPermission(\"basic:dept:get\")\n    @Operation(summary = \"查询部门树 -> 只获取正常状态的部门\")\n    public ResponseEntity<List<DeptVO>> getEnableDeptList() {\n        return ResponseEntity.ok(deptService.getEnableDeptList());\n    }\n\n    @GetMapping(\"/getDeptByDeptId/{deptId}\")\n    @SaCheckPermission(\"basic:dept:get\")\n    @Operation(summary = \"根据部门ID查询部门\")\n    public ResponseEntity<DeptVO> getDeptByDeptId(@PathVariable(value = \"deptId\")\n                                                  @NotNull(message = \"部门ID不能为空\")\n                                                  @Parameter(name = \"deptId\", description = \"部门ID\", required = true) Long deptId) {\n        return ResponseEntity.ok(deptService.getDeptByDeptId(deptId));\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/controller/DictController.java",
    "content": "package com.minimalist.basic.controller;\n\nimport cn.dev33.satoken.annotation.SaCheckPermission;\nimport com.minimalist.basic.entity.vo.dict.*;\nimport com.minimalist.basic.service.DictService;\nimport com.minimalist.basic.config.mybatis.bo.PageResp;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\nimport java.util.List;\n\n@Validated\n@RestController\n@Tag(name = \"字典管理\")\n@RequestMapping(\"/basic/dict\")\npublic class DictController {\n\n    @Autowired\n    private DictService dictService;\n\n    @PostMapping(\"/addDict\")\n    @SaCheckPermission(\"basic:dict:add\")\n    @Operation(summary = \"添加字典\")\n    public ResponseEntity<Void> addDict(@RequestBody @Validated DictInfoVO dictInfoVO) {\n        dictService.addDict(dictInfoVO);\n        return ResponseEntity.ok().build();\n    }\n\n    @DeleteMapping(\"/deleteDictByDictId\")\n    @SaCheckPermission(\"basic:dict:delete\")\n    @Operation(summary = \"删除字典 -> 根据字典ID删除\")\n    public ResponseEntity<Void> deleteDictByDictId(@RequestParam(\"dictId\")\n                                            @NotNull(message = \"字典ID不能为空\")\n                                            @Parameter(name = \"dictId\", required = true, description = \"字典ID\") Long dictId) {\n        dictService.deleteDictByDictId(dictId);\n        return ResponseEntity.ok().build();\n    }\n\n    @DeleteMapping(\"/deleteDictByDictType\")\n    @SaCheckPermission(\"basic:dict:delete\")\n    @Operation(summary = \"删除字典 -> 根据字典类型删除\")\n    public ResponseEntity<Void> deleteDictByDictType(@RequestParam(\"dictType\")\n                                            @NotNull(message = \"字典类型不能为空\")\n                                            @Parameter(name = \"dictType\", required = true, description = \"字典类型\") String dictType) {\n        dictService.deleteDictByDictType(dictType);\n        return ResponseEntity.ok().build();\n    }\n\n    @PutMapping(\"/updateDictByDictId\")\n    @SaCheckPermission(\"basic:dict:update\")\n    @Operation(summary = \"修改字典\")\n    public ResponseEntity<Void> updateDictByDictId(@RequestBody @Validated DictInfoVO dictInfoVO) {\n        dictService.updateDictByDictId(dictInfoVO);\n        return ResponseEntity.ok().build();\n    }\n\n    @GetMapping(\"/getPageDictList\")\n    @SaCheckPermission(\"basic:dict:get\")\n    @Operation(summary = \"查询字典列表(分页)\")\n    public ResponseEntity<PageResp<DictVO>> getPageDictList(DictQueryVO queryVO) {\n        return ResponseEntity.ok(dictService.getPageDictList(queryVO));\n    }\n\n    @GetMapping(\"/getDictByDictType/{dictType}\")\n    @SaCheckPermission(\"basic:dict:get\")\n    @Operation(summary = \"根据字典类型查询字典 -> 用于字典管理页面\")\n    public ResponseEntity<DictInfoVO> getDictByDictType(@PathVariable(value = \"dictType\")\n                                            @Parameter(name = \"dictType\", description = \"字典类型\", required = true) String dictType) {\n        return ResponseEntity.ok(dictService.getDictByDictType(dictType));\n    }\n\n    @GetMapping(\"/getDictList/{dictTypes}\")\n    @Operation(summary = \"根据字典类型查询字典 -> 用于下拉框数据展示或编码转换\")\n    public ResponseEntity<List<DictCacheVO>> getDictList(@PathVariable(value = \"dictTypes\")\n                                                        @NotEmpty(message = \"字典类型不能为空\")\n                                         @Parameter(name = \"dictTypes\", description = \"字典类型列表，为空则查询所有字典数据\") List<String> dictType) {\n        return ResponseEntity.ok(dictService.getDictList(dictType));\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/controller/FileController.java",
    "content": "package com.minimalist.basic.controller;\n\nimport cn.dev33.satoken.annotation.SaCheckPermission;\nimport com.minimalist.basic.entity.vo.file.*;\nimport com.minimalist.basic.service.FileService;\nimport com.minimalist.basic.config.mybatis.bo.PageResp;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.validation.constraints.NotNull;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.List;\n\n@Validated\n@RestController\n@Tag(name = \"文件管理\")\n@RequestMapping(\"/basic/file\")\npublic class FileController {\n\n    @Autowired\n    private FileService fileService;\n\n    @PostMapping(\"/uploadFile\")\n    @SaCheckPermission(\"basic:file:upload\")\n    @Operation(summary = \"单文件上传\")\n    public ResponseEntity<FileUploadRespVO> uploadFile(@Validated FileUploadVO fileUploadVO) {\n        return ResponseEntity.ok(fileService.uploadFile(fileUploadVO));\n    }\n\n    @PostMapping(\"/uploadFileBatch\")\n    @SaCheckPermission(\"basic:file:upload\")\n    @Operation(summary = \"批量文件上传\")\n    public ResponseEntity<List<FileUploadRespVO>> uploadFileBatch(@Validated FileUploadBatchVO uploadBatchVO) {\n        return ResponseEntity.ok(fileService.uploadFileBatch(uploadBatchVO));\n    }\n\n    @DeleteMapping(\"/deleteFile\")\n    @SaCheckPermission(\"basic:file:delete\")\n    @Operation(summary = \"单文件删除\")\n    public ResponseEntity<Void> deleteFile(@RequestParam(\"fileId\")\n                                           @NotNull(message = \"文件ID不能为空\")\n                                           @Parameter(name = \"fileId\", required = true, description = \"fileId\") Long fileId) {\n        fileService.deleteFile(fileId);\n        return ResponseEntity.ok().build();\n    }\n\n    @GetMapping(\"/getPageFileList\")\n    @SaCheckPermission(\"basic:file:get\")\n    @Operation(summary = \"查询文件列表(分页)\")\n    public ResponseEntity<PageResp<FileVO>> getPageFileList(FileQueryVO queryVO) {\n        return ResponseEntity.ok(fileService.getPageFileList(queryVO));\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/controller/NoticeController.java",
    "content": "package com.minimalist.basic.controller;\n\nimport cn.dev33.satoken.annotation.SaCheckPermission;\nimport com.minimalist.basic.entity.vo.notice.NoticeQueryVO;\nimport com.minimalist.basic.entity.vo.notice.NoticeVO;\nimport com.minimalist.basic.service.NoticeService;\nimport com.minimalist.basic.config.mybatis.bo.PageResp;\nimport com.minimalist.basic.config.mybatis.bo.PageReq;\nimport com.minimalist.basic.utils.Add;\nimport com.minimalist.basic.utils.Update;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.validation.constraints.NotNull;\nimport net.dreamlu.mica.xss.core.XssCleanIgnore;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\n@Validated\n@RestController\n@Tag(name = \"通知、公告管理\")\n@RequestMapping(\"/basic/notice\")\npublic class NoticeController {\n\n    @Autowired\n    private NoticeService noticeService;\n\n    @XssCleanIgnore\n    @PostMapping(\"/addNotice\")\n    @SaCheckPermission(\"basic:notice:add\")\n    @Operation(summary = \"添加公告\")\n    public ResponseEntity<Void> addNotice(@RequestBody @Validated(Add.class) NoticeVO noticeVO) {\n        noticeService.addNotice(noticeVO);\n        return ResponseEntity.ok().build();\n    }\n\n    @DeleteMapping(\"/deleteNoticeByNoticeId\")\n    @SaCheckPermission(\"basic:notice:delete\")\n    @Operation(summary = \"删除公告 -> 根据公告ID删除\")\n    public ResponseEntity<Void> deleteNoticeByNoticeId(@RequestParam(\"noticeId\")\n                                                   @NotNull(message = \"公告ID不能为空\")\n                                                   @Parameter(name = \"noticeId\", required = true, description = \"公告ID\") Long noticeId) {\n        noticeService.deleteNoticeByNoticeId(noticeId);\n        return ResponseEntity.ok().build();\n    }\n\n    @XssCleanIgnore\n    @PutMapping(\"/updateNoticeByNoticeId\")\n    @SaCheckPermission(\"basic:notice:update\")\n    @Operation(summary = \"修改公告 -> 根据公告ID修改\")\n    public ResponseEntity<Void> updateNoticeByNoticeId(@RequestBody @Validated(Update.class) NoticeVO noticeVO) {\n        noticeService.updateNoticeByNoticeId(noticeVO);\n        return ResponseEntity.ok().build();\n    }\n\n    @GetMapping(\"/getPageNoticeList\")\n    @SaCheckPermission(\"basic:notice:get\")\n    @Operation(summary = \"查询公告列表(分页) -> 公告管理使用\")\n    public ResponseEntity<PageResp<NoticeVO>> getPageNoticeList(NoticeQueryVO queryVO) {\n        return ResponseEntity.ok(noticeService.getPageNoticeList(queryVO));\n    }\n\n    @GetMapping(\"/getNoticeByNoticeId/{noticeId}\")\n    @Operation(summary = \"根据公告ID查询公告\")\n    public ResponseEntity<NoticeVO> getNoticeByNoticeId(@PathVariable(value = \"noticeId\")\n                                                  @NotNull(message = \"公告ID不能为空\")\n                                                  @Parameter(name = \"noticeId\", description = \"公告ID\", required = true) Long noticeId) {\n        return ResponseEntity.ok(noticeService.getNoticeByNoticeId(noticeId));\n    }\n\n    @GetMapping(\"/getPageHomeNoticeList\")\n    @Operation(summary = \"查询公告列表(分页) -> 首页使用\")\n    public ResponseEntity<PageResp<NoticeVO>> getPageHomeNoticeList(PageReq pageReq) {\n        return ResponseEntity.ok(noticeService.getPageHomeNoticeList(pageReq));\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/controller/PermController.java",
    "content": "package com.minimalist.basic.controller;\n\nimport cn.dev33.satoken.annotation.SaCheckPermission;\nimport com.minimalist.basic.entity.vo.perm.PermQueryVO;\nimport com.minimalist.basic.entity.vo.perm.PermVO;\nimport com.minimalist.basic.service.PermService;\nimport com.minimalist.basic.utils.Add;\nimport com.minimalist.basic.utils.Update;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.validation.constraints.NotNull;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\nimport java.util.List;\n\n@Validated\n@RestController\n@RequestMapping(\"/basic/permission\")\n@Tag(name = \"权限管理\")\npublic class PermController {\n\n    @Autowired\n    private PermService permService;\n\n    @PostMapping(\"/addPerm\")\n    @SaCheckPermission(\"basic:perm:add\")\n    @Operation(summary = \"添加权限\")\n    public ResponseEntity<Void> addPerm(@RequestBody @Validated(Add.class) PermVO permVO) {\n        permService.addPerm(permVO);\n        return ResponseEntity.ok().build();\n    }\n\n    @DeleteMapping(\"/deletePermByPermId\")\n    @SaCheckPermission(\"basic:perm:delete\")\n    @Operation(summary = \"删除权限 -> 根据权限ID删除\")\n    public ResponseEntity<Void> deletePermByPermId(@RequestParam(\"permId\")\n                                                   @NotNull(message = \"权限ID不能为空\")\n                                                   @Parameter(name = \"permId\", required = true, description = \"权限ID\") Long permId) {\n        permService.deletePermByPermId(permId);\n        return ResponseEntity.ok().build();\n    }\n\n    @PutMapping(\"/updatePermByPermId\")\n    @SaCheckPermission(\"basic:perm:update\")\n    @Operation(summary = \"修改权限 -> 根据权限ID修改\")\n    public ResponseEntity<Void> updatePermByPermId(@RequestBody @Validated(Update.class) PermVO permVO) {\n        permService.updatePermByPermId(permVO);\n        return ResponseEntity.ok().build();\n    }\n\n    @GetMapping(\"/getPermList\")\n    @SaCheckPermission(\"basic:perm:get\")\n    @Operation(summary = \"查询权限列表(不分页，获取全部数据) -> 权限管理使用\")\n    public ResponseEntity<List<PermVO>> getPermList(PermQueryVO queryVO) {\n        return ResponseEntity.ok(permService.getPermList(queryVO));\n    }\n\n    @GetMapping(\"/getEnablePermList\")\n    @Operation(summary = \"查询系统租户权限列表 -> 只获取正常状态的权限\")\n    public ResponseEntity<List<PermVO>> getEnablePermList() {\n        return ResponseEntity.ok(permService.getEnablePermList());\n    }\n\n    @GetMapping(\"/getTenantEnablePermList\")\n    @Operation(summary = \"查询租户权限列表 -> 只获取正常状态的权限\")\n    public ResponseEntity<List<PermVO>> getTenantEnablePermList() {\n        return ResponseEntity.ok(permService.getTenantEnablePermList());\n    }\n\n    @GetMapping(\"/getPermByPermId/{permId}\")\n    @SaCheckPermission(\"basic:perm:get\")\n    @Operation(summary = \"根据权限ID查询权限\")\n    public ResponseEntity<PermVO> getPermByPermId(@PathVariable(value = \"permId\")\n                                                        @NotNull(message = \"权限ID不能为空\")\n                                                        @Parameter(name = \"permId\", description = \"权限ID\", required = true) Long permId) {\n        return ResponseEntity.ok(permService.getPermByPermId(permId));\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/controller/PostController.java",
    "content": "package com.minimalist.basic.controller;\n\nimport cn.dev33.satoken.annotation.SaCheckPermission;\nimport com.minimalist.basic.entity.vo.post.PostQueryVO;\nimport com.minimalist.basic.entity.vo.post.PostVO;\nimport com.minimalist.basic.service.PostService;\nimport com.minimalist.basic.config.mybatis.bo.PageResp;\nimport com.minimalist.basic.utils.Add;\nimport com.minimalist.basic.utils.Update;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.validation.constraints.NotNull;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\n@Validated\n@RestController\n@RequestMapping(\"/basic/post\")\n@Tag(name = \"岗位管理\")\npublic class PostController {\n\n    @Autowired\n    private PostService postService;\n\n    @PostMapping(\"/addPost\")\n    @SaCheckPermission(\"basic:post:add\")\n    @Operation(summary = \"添加岗位\")\n    public ResponseEntity<Void> addPost(@RequestBody @Validated(Add.class) PostVO postVO) {\n        postService.addPost(postVO);\n        return ResponseEntity.ok().build();\n    }\n\n    @DeleteMapping(\"/deletePostByPostId\")\n    @SaCheckPermission(\"basic:post:delete\")\n    @Operation(summary = \"删除岗位 -> 根据岗位ID删除\")\n    public ResponseEntity<Void> deletePostByPostId(@RequestParam(\"postId\")\n                                                   @NotNull(message = \"岗位ID不能为空\")\n                                                   @Parameter(name = \"postId\", required = true, description = \"岗位ID\") Long postId) {\n        postService.deletePostByPostId(postId);\n        return ResponseEntity.ok().build();\n    }\n\n    @PutMapping(\"/updatePostByPostId\")\n    @SaCheckPermission(\"basic:post:update\")\n    @Operation(summary = \"修改岗位 -> 根据岗位ID修改\")\n    public ResponseEntity<Void> updatePostByPostId(@RequestBody @Validated(Update.class) PostVO postVO) {\n        postService.updatePostByPostId(postVO);\n        return ResponseEntity.ok().build();\n    }\n\n    @GetMapping(\"/getPagePostList\")\n    @SaCheckPermission(\"basic:post:get\")\n    @Operation(summary = \"查询岗位列表(分页)\")\n    public ResponseEntity<PageResp<PostVO>> getPagePostList(PostQueryVO queryVO) {\n        return ResponseEntity.ok(postService.getPagePostList(queryVO));\n    }\n\n    @GetMapping(\"/getPostByPostId/{postId}\")\n    @SaCheckPermission(\"basic:post:get\")\n    @Operation(summary = \"根据岗位ID查询岗位\")\n    public ResponseEntity<PostVO> getPostByPostId(@PathVariable(value = \"postId\")\n                                                  @NotNull(message = \"岗位ID不能为空\")\n                                                  @Parameter(name = \"postId\", description = \"岗位ID\", required = true) Long postId) {\n        return ResponseEntity.ok(postService.getPostByPostId(postId));\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/controller/RoleController.java",
    "content": "package com.minimalist.basic.controller;\n\nimport cn.dev33.satoken.annotation.SaCheckPermission;\nimport com.minimalist.basic.entity.vo.role.RoleQueryVO;\nimport com.minimalist.basic.entity.vo.role.RoleVO;\nimport com.minimalist.basic.service.RoleService;\nimport com.minimalist.basic.config.mybatis.bo.PageResp;\nimport com.minimalist.basic.utils.Add;\nimport com.minimalist.basic.utils.Update;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.validation.constraints.NotNull;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\n@Validated\n@RestController\n@RequestMapping(\"/basic/role\")\n@Tag(name = \"角色管理\")\npublic class RoleController {\n\n    @Autowired\n    private RoleService roleService;\n\n    @PostMapping(\"/addRole\")\n    @SaCheckPermission(\"basic:role:add\")\n    @Operation(summary = \"添加角色\")\n    public ResponseEntity<Void> addRole(@RequestBody @Validated(Add.class) RoleVO roleVO) {\n        roleService.addRole(roleVO);\n        return ResponseEntity.ok().build();\n    }\n\n    @DeleteMapping(\"/deleteRoleByRoleId\")\n    @SaCheckPermission(\"basic:role:delete\")\n    @Operation(summary = \"删除角色 -> 根据角色ID删除角色\")\n    public ResponseEntity<Void> deleteRoleByRoleId(@RequestParam(\"roleId\")\n                                                   @NotNull(message = \"角色ID不能为空\")\n                                                   @Parameter(name = \"roleId\", required = true, description = \"角色ID\") Long roleId) {\n        roleService.deleteRoleByRoleId(roleId);\n        return ResponseEntity.ok().build();\n    }\n\n    @PutMapping(\"/updateRoleByRoleId\")\n    @SaCheckPermission(\"basic:role:update\")\n    @Operation(summary = \"修改角色 -> 根据角色ID修改\")\n    public ResponseEntity<Void> updateRoleByRoleId(@RequestBody @Validated(Update.class) RoleVO roleVO) {\n        roleService.updateRoleByRoleId(roleVO);\n        return ResponseEntity.ok().build();\n    }\n\n    @GetMapping(\"/getPageRoleList\")\n    @SaCheckPermission(\"basic:role:get\")\n    @Operation(summary = \"查询角色(分页) -> 角色管理使用\")\n    public ResponseEntity<PageResp<RoleVO>> getPageRoleList(RoleQueryVO queryVO) {\n        return ResponseEntity.ok(roleService.getPageRoleList(queryVO));\n    }\n\n    @GetMapping(\"/getRoleByRoleId/{roleId}\")\n    @SaCheckPermission(\"basic:role:get\")\n    @Operation(summary = \"根据角色ID查询角色\")\n    public ResponseEntity<RoleVO> getRoleByRoleId(@PathVariable(value = \"roleId\")\n                                                  @NotNull(message = \"角色ID不能为空\")\n                                                  @Parameter(name = \"roleId\", description = \"角色ID\", required = true) Long roleId) {\n        return ResponseEntity.ok(roleService.getRoleByRoleId(roleId));\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/controller/StorageController.java",
    "content": "package com.minimalist.basic.controller;\n\nimport cn.dev33.satoken.annotation.SaCheckPermission;\nimport com.minimalist.basic.config.mybatis.bo.PageResp;\nimport com.minimalist.basic.entity.vo.storage.StorageQueryVO;\nimport com.minimalist.basic.entity.vo.storage.StorageVO;\nimport com.minimalist.basic.service.StorageService;\nimport com.minimalist.basic.utils.Add;\nimport com.minimalist.basic.utils.Update;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.validation.constraints.NotNull;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\n@Validated\n@RestController\n@RequestMapping(\"/basic/storage\")\n@Tag(name = \"存储管理\")\npublic class StorageController {\n\n    @Autowired\n    private StorageService storageService;\n\n    @PostMapping(\"/addStorage\")\n    @SaCheckPermission(\"basic:storage:add\")\n    @Operation(summary = \"添加存储\")\n    public ResponseEntity<Void> addStorage(@RequestBody @Validated(Add.class) StorageVO storageVO) {\n        storageService.addStorage(storageVO);\n        return ResponseEntity.ok().build();\n    }\n\n    @DeleteMapping(\"/deleteStorageByStorageId\")\n    @SaCheckPermission(\"basic:storage:delete\")\n    @Operation(summary = \"删除存储 -> 根据存储ID删除\")\n    public ResponseEntity<Void> deleteStorageByStorageId(@RequestParam(\"storageId\")\n                                                   @NotNull(message = \"存储ID不能为空\")\n                                                   @Parameter(name = \"storageId\", required = true, description = \"存储ID\") Long storageId) {\n        storageService.deleteStorageByStorageId(storageId);\n        return ResponseEntity.ok().build();\n    }\n\n    @PutMapping(\"/updateStorageByStorageId\")\n    @SaCheckPermission(\"basic:storage:update\")\n    @Operation(summary = \"修改存储 -> 根据存储ID修改\")\n    public ResponseEntity<Void> updateStorageByStorageId(@RequestBody @Validated(Update.class) StorageVO storageVO) {\n        storageService.updateStorageByStorageId(storageVO);\n        return ResponseEntity.ok().build();\n    }\n\n    @GetMapping(\"/getPageStorageList\")\n    @SaCheckPermission(\"basic:storage:get\")\n    @Operation(summary = \"查询存储列表(分页)\")\n    public ResponseEntity<PageResp<StorageVO>> getPageStorageList(StorageQueryVO queryVO) {\n        return ResponseEntity.ok(storageService.getPageStorageList(queryVO));\n    }\n\n    @GetMapping(\"/getStorageByStorageId/{storageId}\")\n    @SaCheckPermission(\"basic:storage:get\")\n    @Operation(summary = \"根据存储ID查询存储信息\")\n    public ResponseEntity<StorageVO> getStorageByStorageId(@PathVariable(value = \"storageId\")\n                                                       @NotNull(message = \"存储ID不能为空\")\n                                                       @Parameter(name = \"storageId\", description = \"存储ID\", required = true) Long storageId) {\n        return ResponseEntity.ok(storageService.getStorageByStorageId(storageId));\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/controller/TenantController.java",
    "content": "package com.minimalist.basic.controller;\n\nimport cn.dev33.satoken.annotation.SaCheckPermission;\nimport com.minimalist.basic.entity.vo.tenant.TenantQueryVO;\nimport com.minimalist.basic.entity.vo.tenant.TenantVO;\nimport com.minimalist.basic.service.TenantService;\nimport com.minimalist.basic.config.mybatis.bo.PageResp;\nimport com.minimalist.basic.config.tenant.TenantIgnore;\nimport com.minimalist.basic.utils.Add;\nimport com.minimalist.basic.utils.Update;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.validation.constraints.NotNull;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\n@Validated\n@RestController\n@RequestMapping(\"/basic/tenant\")\n@Tag(name = \"租户管理\")\npublic class TenantController {\n\n    @Autowired\n    private TenantService tenantService;\n\n    @TenantIgnore\n    @PostMapping(\"/addTenant\")\n    @SaCheckPermission(\"basic:tenant:add\")\n    @Operation(summary = \"添加租户\")\n    public ResponseEntity<Void> addTenant(@RequestBody @Validated(Add.class) TenantVO tenantVO) {\n        tenantService.addTenant(tenantVO);\n        return ResponseEntity.ok().build();\n    }\n\n    @TenantIgnore\n    @DeleteMapping(\"/deleteTenantByTenantId\")\n    @SaCheckPermission(\"basic:tenant:delete\")\n    @Operation(summary = \"删除租户 -> 根据租户ID删除租户\")\n    public ResponseEntity<Void> deleteTenantByTenantId(@RequestParam(\"tenantId\")\n                                                                     @NotNull(message = \"租户ID不能为空\")\n                                                                     @Parameter(name = \"tenantId\", required = true, description = \"租户ID\") Long tenantId) {\n        tenantService.deleteTenantByTenantId(tenantId);\n        return ResponseEntity.ok().build();\n    }\n\n    @TenantIgnore\n    @PutMapping(\"/updateTenantByTenantId\")\n    @SaCheckPermission(\"basic:tenant:update\")\n    @Operation(summary = \"修改租户 -> 根据租户ID修改\")\n    public ResponseEntity<Void> updateTenantByTenantId(@RequestBody @Validated(Update.class) TenantVO tenantVO) {\n        tenantService.updateTenantByTenantId(tenantVO);\n        return ResponseEntity.ok().build();\n    }\n\n    @TenantIgnore\n    @GetMapping(\"/getPageTenantList\")\n    @SaCheckPermission(\"basic:tenant:get\")\n    @Operation(summary = \"查询租户(分页)\")\n    public ResponseEntity<PageResp<TenantVO>> getPageTenantList(TenantQueryVO queryVO) {\n        return ResponseEntity.ok(tenantService.getPageTenantList(queryVO));\n    }\n\n    @TenantIgnore\n    @GetMapping(\"/getTenantByTenantId/{tenantId}\")\n    @SaCheckPermission(\"basic:tenant:get\")\n    @Operation(summary = \"根据租户ID查询租户\")\n    public ResponseEntity<TenantVO> getTenantByTenantId(@PathVariable(value = \"tenantId\")\n                                                                             @NotNull(message = \"租户ID不能为空\")\n                                                                             @Parameter(name = \"tenantId\", description = \"租户ID\", required = true) Long tenantId) {\n        return ResponseEntity.ok(tenantService.getTenantByTenantId(tenantId));\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/controller/TenantPackageController.java",
    "content": "package com.minimalist.basic.controller;\n\nimport cn.dev33.satoken.annotation.SaCheckPermission;\nimport com.minimalist.basic.entity.vo.tenant.TenantPackageQueryVO;\nimport com.minimalist.basic.entity.vo.tenant.TenantPackageVO;\nimport com.minimalist.basic.service.TenantPackageService;\nimport com.minimalist.basic.config.mybatis.bo.PageResp;\nimport com.minimalist.basic.config.tenant.TenantIgnore;\nimport com.minimalist.basic.utils.Add;\nimport com.minimalist.basic.utils.Update;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.validation.constraints.NotNull;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\n@Validated\n@RestController\n@RequestMapping(\"/basic/tenantPackage\")\n@Tag(name = \"租户管理\")\npublic class TenantPackageController {\n\n    @Autowired\n    private TenantPackageService tenantPackageService;\n\n    @TenantIgnore\n    @PostMapping(\"/addTenantPackage\")\n    @SaCheckPermission(\"basic:tenantPackage:add\")\n    @Operation(summary = \"添加租户套餐\")\n    public ResponseEntity<Void> addTenantPackage(@RequestBody @Validated(Add.class) TenantPackageVO tenantPackageVO) {\n        tenantPackageService.addTenantPackage(tenantPackageVO);\n        return ResponseEntity.ok().build();\n    }\n\n    @TenantIgnore\n    @DeleteMapping(\"/deleteTenantPackageByTenantPackageId\")\n    @SaCheckPermission(\"basic:tenantPackage:delete\")\n    @Operation(summary = \"删除租户套餐 -> 根据租户套餐ID删除租户套餐\")\n    public ResponseEntity<Void> deleteTenantPackageByTenantPackageId(@RequestParam(\"tenantPackageId\")\n                                                   @NotNull(message = \"租户套餐ID不能为空\")\n                                                   @Parameter(name = \"tenantPackageId\", required = true, description = \"租户套餐ID\") Long tenantPackageId) {\n        tenantPackageService.deleteTenantPackageByTenantPackageId(tenantPackageId);\n        return ResponseEntity.ok().build();\n    }\n\n    @TenantIgnore\n    @PutMapping(\"/updateTenantPackageByTenantPackageId\")\n    @SaCheckPermission(\"basic:tenantPackage:update\")\n    @Operation(summary = \"修改租户套餐 -> 根据租户套餐ID修改\")\n    public ResponseEntity<Void> updateTenantPackageByTenantPackageId(@RequestBody @Validated(Update.class) TenantPackageVO tenantPackageVO) {\n        tenantPackageService.updateTenantPackageByTenantPackageId(tenantPackageVO);\n        return ResponseEntity.ok().build();\n    }\n\n    @TenantIgnore\n    @GetMapping(\"/getPageTenantPackageList\")\n    @SaCheckPermission(\"basic:tenantPackage:get\")\n    @Operation(summary = \"查询租户套餐(分页)\")\n    public ResponseEntity<PageResp<TenantPackageVO>> getPageTenantPackageList(TenantPackageQueryVO queryVO) {\n        return ResponseEntity.ok(tenantPackageService.getPageTenantPackageList(queryVO));\n    }\n\n    @TenantIgnore\n    @GetMapping(\"/getTenantPackageByTenantPackageId/{tenantPackageId}\")\n    @SaCheckPermission(\"basic:tenantPackage:get\")\n    @Operation(summary = \"根据租户套餐ID查询租户套餐\")\n    public ResponseEntity<TenantPackageVO> getTenantPackageByTenantPackageId(@PathVariable(value = \"tenantPackageId\")\n                                                  @NotNull(message = \"租户套餐ID不能为空\")\n                                                  @Parameter(name = \"tenantPackageId\", description = \"租户套餐ID\", required = true) Long tenantPackageId) {\n        return ResponseEntity.ok(tenantPackageService.getTenantPackageByTenantPackageId(tenantPackageId));\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/controller/UserController.java",
    "content": "package com.minimalist.basic.controller;\n\nimport cn.dev33.satoken.annotation.SaCheckLogin;\nimport cn.dev33.satoken.annotation.SaCheckPermission;\nimport cn.dev33.satoken.annotation.SaIgnore;\nimport cn.dev33.satoken.stp.SaTokenInfo;\nimport cn.dev33.satoken.stp.StpUtil;\nimport com.minimalist.basic.entity.vo.user.*;\nimport com.minimalist.basic.service.UserService;\nimport com.minimalist.basic.config.mybatis.bo.PageResp;\nimport com.minimalist.basic.config.tenant.TenantIgnore;\nimport com.minimalist.basic.utils.Add;\nimport com.minimalist.basic.utils.Update;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.validation.Valid;\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotNull;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\n@Validated\n@RestController\n@RequestMapping(\"/basic/user\")\n@Tag(name = \"用户管理\")\npublic class UserController {\n\n    @Autowired\n    private UserService userService;\n\n    @PostMapping(\"/addUser\")\n    @SaCheckPermission(\"basic:user:add\")\n    @Operation(summary = \"添加用户\")\n    public ResponseEntity<Void> addUser(@RequestBody @Validated(Add.class) UserVO userVO) {\n        userService.addUser(userVO);\n        return ResponseEntity.ok().build();\n    }\n\n    @DeleteMapping(\"/deleteUserByUserId\")\n    @SaCheckPermission(\"basic:user:delete\")\n    @Operation(summary = \"删除用户 -> 根据用户ID删除\")\n    public ResponseEntity<Void> deleteUserByUserId(@RequestParam(\"userId\")\n                                            @NotNull(message = \"用户ID不能为空\")\n                                            @Parameter(name = \"userId\", required = true, description = \"用户ID\") Long userId) {\n        userService.deleteUserByUserId(userId);\n        return ResponseEntity.ok().build();\n    }\n\n    @PutMapping(\"/updateUserByUserId\")\n    @SaCheckPermission(\"basic:user:update\")\n    @Operation(summary = \"修改用户\")\n    public ResponseEntity<Void> updateUserByUserId(@RequestBody @Validated(Update.class) UserVO userVO) {\n        userService.updateUserByUserId(userVO);\n        return ResponseEntity.ok().build();\n    }\n\n    @GetMapping(\"/getPageUserList\")\n    @SaCheckPermission(\"basic:user:get\")\n    @Operation(summary = \"查询用户(分页)\")\n    public ResponseEntity<PageResp<UserVO>> getPageUserList(UserQueryVO queryVO) {\n        return ResponseEntity.ok(userService.getPageUserList(queryVO));\n    }\n\n    @GetMapping(\"/getUserByUserId/{userId}\")\n    @SaCheckPermission(\"basic:user:get\")\n    @Operation(summary = \"根据用户ID查询用户 -> 用户管理页使用\")\n    public ResponseEntity<UserVO> getUserByUserId(@PathVariable(value = \"userId\")\n                                                  @NotNull(message = \"用户ID不能为空\")\n                                                  @Parameter(name = \"userId\", description = \"用户ID\", required = true) Long userId) {\n        return ResponseEntity.ok(userService.getUserByUserId(userId));\n    }\n\n    //@TenantIgnore\n    @GetMapping(\"/getUserInfo\")\n    @Operation(summary = \"获取用户信息(登录后获取，含角色、权限、菜单、部门等)\")\n    public ResponseEntity<UserInfoVO> getUserInfo() {\n        return ResponseEntity.ok(userService.getUserInfo());\n    }\n\n    @SaIgnore\n    @GetMapping(\"/getImageCaptcha\")\n    @Operation(summary = \"获取图形验证码\")\n    public ResponseEntity<ImageCaptchaVO> getImageCaptcha() {\n        return ResponseEntity.ok(userService.getImageCaptcha());\n    }\n\n    @SaIgnore\n    @TenantIgnore\n    @PostMapping(\"/login\")\n    @Operation(summary = \"用户登录，返回token\")\n    public ResponseEntity<SaTokenInfo> login(@RequestBody @Valid UserLoginReqVO reqVO) {\n        return ResponseEntity.ok(userService.userLogin(reqVO));\n    }\n\n    @PostMapping(\"/logout\")\n    @Operation(summary = \"退出登录\")\n    public ResponseEntity<Void> logout() {\n        StpUtil.logout();\n        return ResponseEntity.ok().build();\n    }\n\n    @SaCheckLogin\n    @PostMapping(\"/resetPassword\")\n    @Operation(summary = \"重置密码\")\n    public ResponseEntity<Void> resetPassword(@RequestBody @Valid RePasswordVO passwordVO) {\n        userService.resetPassword(passwordVO);\n        return ResponseEntity.ok().build();\n    }\n\n    @SaCheckLogin\n    @PostMapping(\"/updateUserAvatar\")\n    @Operation(summary = \"修改用户头像\")\n    public ResponseEntity<Void> updateUserAvatar(@RequestParam(\"userAvatar\")\n                                                 @NotBlank(message = \"用户头像不能为空\")\n                                                 @Parameter(name = \"userAvatar\", description = \"用户头像base64编码\", required = true) String userAvatar) {\n        userService.updateUserAvatar(userAvatar);\n        return ResponseEntity.ok().build();\n    }\n\n    @SaCheckLogin\n    @PostMapping(\"/updateUserInfo\")\n    @Operation(summary = \"修改用户基本信息\")\n    public ResponseEntity<Void> updateUserInfo(@RequestBody @Valid UserSettingVO settingVO) {\n        userService.updateUserInfo(settingVO);\n        return ResponseEntity.ok().build();\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/enums/ConfigEnum.java",
    "content": "package com.minimalist.basic.entity.enums;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\npublic class ConfigEnum {\n\n    /** 字典处理异常信息 */\n    @Getter\n    @AllArgsConstructor\n    public enum ErrorMsg {\n        NONENTITY_CONFIG(\"参数配置不存在\"),\n        CONTAIN_CONFIG_KEY(\"参数键名已存在\"),\n        CANNOT_DEL_SYSTEM_CONFIG(\"不能删除系统参数\"),\n        ;\n        private final String desc;\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/enums/DeptEnum.java",
    "content": "package com.minimalist.basic.entity.enums;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\npublic class DeptEnum {\n\n    /** 部门处理异常信息 */\n    @Getter\n    @AllArgsConstructor\n    public enum ErrorMsg {\n        NONENTITY_DEPT(\"部门不存在\"),\n        CONTAIN_CHILDREN(\"该权限下包含子项，请先删除子项\"),\n        USER_DEPT_CHILDREN(\"有用户在该部门，请先调整该用户部门\"),\n        ;\n        private final String desc;\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/enums/DictEnum.java",
    "content": "package com.minimalist.basic.entity.enums;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\npublic class DictEnum {\n\n    /** 字典处理异常信息 */\n    @Getter\n    @AllArgsConstructor\n    public enum ErrorMsg {\n        NONENTITY_DICT(\"字典不存在\"),\n        ;\n        private final String desc;\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/enums/FileEnum.java",
    "content": "package com.minimalist.basic.entity.enums;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\npublic class FileEnum {\n\n    /** 文件处理异常信息 */\n    @Getter\n    @AllArgsConstructor\n    public enum ErrorMsg {\n        NONENTITY_FILE(\"文件不存在\"),\n        FILE_USED(\"文件已被使用\"),\n        FILE_UPLOAD_FAIL(\"文件上传失败，请重试\"),\n        FILE_THUMBNAILS_UPLOAD_FAIL(\"文件缩略图上传失败，请重试\"),\n        FILE_DOWNLOAD_FAIL(\"文件下载失败，请重试\"),\n        FILE_MOVE_FAIL(\"文件移动失败，请重试\"),\n        FILE_DELETE_FAIL(\"文件删除失败，请重试\"),\n        ;\n        private final String desc;\n    }\n\n    /** 文件来源 */\n    @Getter\n    @AllArgsConstructor\n    public enum FileSource {\n        NOTICE_COVER_IMG(1, \"系统公告封面图片\"),\n        NOTICE_CONTENT_IMG(2, \"系统公告内容图片\"),\n        ;\n        private final Integer code;\n        private final String desc;\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/enums/NoticeEnum.java",
    "content": "package com.minimalist.basic.entity.enums;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\npublic class NoticeEnum {\n\n    /** 公告处理异常信息 */\n    @Getter\n    @AllArgsConstructor\n    public enum ErrorMsg {\n        NONENTITY_NOTICE(\"公告不存在\"),\n        ;\n        private final String desc;\n    }\n\n    /** 公告类型 */\n    @Getter\n    @AllArgsConstructor\n    public enum NoticeType {\n        NOTICE(1, \"公告\"),\n        NEWS(2, \"新闻\"),\n        ;\n        private final Integer code;\n        private final String desc;\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/enums/PermEnum.java",
    "content": "package com.minimalist.basic.entity.enums;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\npublic class PermEnum {\n\n    /** 权限处理异常信息 */\n    @Getter\n    @AllArgsConstructor\n    public enum ErrorMsg {\n        NONENTITY_PERM(\"权限不存在\"),\n        CONTAIN_CHILDREN(\"该权限下包含子项，请先删除子项\"),\n        ;\n        private final String desc;\n    }\n\n    /** 权限类型 */\n    @Getter\n    @AllArgsConstructor\n    public enum PermType {\n        MENU(\"M\", \"菜单\"),\n        BUTTON(\"B\", \"按钮\"),\n        ;\n        private final String code;\n        private final String desc;\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/enums/PostEnum.java",
    "content": "package com.minimalist.basic.entity.enums;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\npublic class PostEnum {\n\n    /** 岗位处理异常信息 */\n    @Getter\n    @AllArgsConstructor\n    public enum ErrorMsg {\n        NONENTITY_POST(\"岗位不存在\"),\n        EXISTS_POST(\"岗位编码已存在\"),\n        ;\n        private final String desc;\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/enums/RespEnum.java",
    "content": "package com.minimalist.basic.entity.enums;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n@Getter\n@AllArgsConstructor\npublic enum RespEnum {\n\n    /** 操作成功 */\n    SUCCESS(200, \"操作成功\"),\n\n    /** 系统异常,请稍后再试 */\n    FAILED(500, \"系统异常,请稍后再试\"),\n\n    /** 参数错误 */\n    PARAM_ERROR(400, \"参数错误\"),\n\n    /** 用户认证失败，请重新登录 */\n    REQUEST_UNAUTH(401, \"用户认证失败，请重新登录\"),\n\n    /** 无操作权限 */\n    NO_OPERATION_PERMISSION(403, \"暂无操作权限\"),\n\n    /** 无操作权限 */\n    TAMPER_WITH_DATA(400, \"请勿篡改数据\"),\n\n    /** 重复提交 */\n    RESUBMIT_ERROR(503, \"请求已提交，请稍后重试\"),\n\n    /** 参数存在XSS敏感字符 */\n    PARAM_XSS_ERROR(400, \"存在敏感数据，不能提交\"),\n\n    ;\n\n    private final Integer code;\n    private final String desc;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/enums/RoleEnum.java",
    "content": "package com.minimalist.basic.entity.enums;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\npublic class RoleEnum {\n\n    /** 角色 */\n    @Getter\n    @AllArgsConstructor\n    public enum Role {\n        /** 系统管理员，权限最高 */\n        SYSTEM_ADMIN(\"system_admin\", \"系统管理员\"),\n        /** 租户管理员，租户权限范围内权限最高 */\n        ADMIN(\"admin\", \"管理员\"),\n        ;\n        private final String code;\n        private final String name;\n    }\n\n    /** 角色处理异常信息 */\n    @Getter\n    @AllArgsConstructor\n    public enum ErrorMsg {\n        EXISTS_ROLE(\"角色编码已存在\"),\n        NONENTITY_ROLE(\"角色不存在\"),\n        ;\n        private final String desc;\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/enums/StatusEnum.java",
    "content": "package com.minimalist.basic.entity.enums;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * 公共的状态枚举\n * 对应每张表中的status字段\n */\n@Getter\n@AllArgsConstructor\npublic enum StatusEnum {\n    STATUS_0(0, \"禁用\"),\n    STATUS_1(1, \"正常\"),\n    ;\n    private final Integer code;\n    private final String desc;\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/enums/StorageEnum.java",
    "content": "package com.minimalist.basic.entity.enums;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\npublic class StorageEnum {\n\n    /** 存储处理异常信息 */\n    @Getter\n    @AllArgsConstructor\n    public enum ErrorMsg {\n        NONENTITY_STORAGE(\"存储信息不存在\"),\n        ;\n        private final String desc;\n    }\n\n    /** 存储类型 */\n    @Getter\n    @AllArgsConstructor\n    public enum StorageType {\n        LOCAL(\"local\", \"本地\"),\n        MINIO(\"minio\", \"MinIO\"),\n        QINIU(\"qiniu\", \"七牛云\"),\n        ;\n        private final String code;\n        private final String desc;\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/enums/TenantEnum.java",
    "content": "package com.minimalist.basic.entity.enums;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\npublic class TenantEnum {\n\n    /** 租户处理异常信息 */\n    @Getter\n    @AllArgsConstructor\n    public enum ErrorMsg {\n        NONENTITY_TENANT(\"租户不存在\"),\n        EXISTS_TENANT(\"租户已存在，请检查租户名\"),\n        NONENTITY_TENANT_PACKAGE(\"租户套餐不存在\"),\n        USE_TENANT_PACKAGE(\"有租户正在使用该套餐，不能删除\"),\n        STATUS_TENANT_PACKAGE(\"选择的租户套餐已被禁用\"),\n        TENANT_USER_COUNT_LIMIT(\"租户下可创建的用户数已达上限\"),\n        EX_TENANT(\"租户已过期，请联系管理员\"),\n        DISABLED_TENANT(\"租户已被禁用，请联系管理员\"),\n        QUERY_NULL_TENANT(\"未查询到租户，请联系管理员\"),\n        SYSTEM_TENANT(\"系统租户，不可删除\"),\n        SYSTEM_TENANT_PACKAGE(\"系统租户套餐，不可删除\"),\n        ADD_TENANT_USER_NULL(\"添加租户时用户信息不能为空\"),\n        ADD_TENANT_USERNAME_NULL(\"添加租户时用户账号不能为空\"),\n        ADD_TENANT_PASSWORD_NULL(\"添加租户时用户密码不能为空\"),\n        ADD_TENANT_NICKNAME_NULL(\"添加租户时用户昵称不能为空\"),\n        ADD_TENANT_REALNAME_NULL(\"添加租户时用户真实姓名不能为空\"),\n        ADD_TENANT_PHONE_NULL(\"添加租户时用户手机号不能为空\"),\n        ADD_TENANT_USERSEX_NULL(\"添加租户时用户性别不能为空\"),\n        ;\n        private final String desc;\n    }\n\n    /** 租户数据隔离方式 */\n    @Getter\n    @AllArgsConstructor\n    public enum DataIsolation {\n        COLUMN(\"column\", \"字段隔离\"),\n        DB(\"db\", \"数据库隔离\");\n        private final String code;\n        private final String desc;\n    }\n\n    /** 主数据源名称 */\n    public static final String MASTER = \"master\";\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/enums/UserEnum.java",
    "content": "package com.minimalist.basic.entity.enums;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\npublic class UserEnum {\n\n    /** 用户处理异常信息 */\n    @Getter\n    @AllArgsConstructor\n    public enum ErrorMsg {\n        CAPTCHA_ID_EMPTY(\"验证码ID为空\"),\n        CAPTCHA_CONTENT_EMPTY(\"验证码为空\"),\n        CAPTCHA_INCORRECT(\"验证码输入错误\"),\n        U_OR_P_INCORRECT(\"用户名或密码错误\"),\n        USER_FROZEN(\"账户已被冻结\"),\n        AUTH_EXPIRED(\"登录信息过期，请重新登录\"),\n        ACCOUNT_EXPIRED(\"账户已过期\"),\n        BAD_INVALID(\"凭证无效，请重新登录\"),\n        EXISTS_ACCOUNT(\"用户名已存在\"),\n        PHONE_ACCOUNT(\"用户手机已存在\"),\n        EMAIL_ACCOUNT(\"用户邮箱已存在\"),\n        NONENTITY_ACCOUNT(\"用户不存在\"),\n        NONENTITY_OPT_ACCOUNT(\"操作的用户不存在\"),\n        OLD_PASSWORD_INCORRECT(\"旧密码输入错误\"),\n        USER_AVATAR_SIZE(\"头像大小需小于100kb\"),\n        LOGIN_USER_INCONSISTENT(\"获取的用户信息与当前登陆用户不一致\"),\n        USER_UNBOUND_TENANT(\"该账户未绑定租户\"),\n\n        ;\n        private final String desc;\n    }\n\n    /** 用户性别 */\n    @Getter\n    @AllArgsConstructor\n    public enum UserSex {\n        USER_SEX_0(0, \"未知\"),\n        USER_SEX_1(1, \"男\"),\n        USER_SEX_2(2, \"女\"),\n        ;\n        private final Integer code;\n        private final String desc;\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/po/MConfig.java",
    "content": "package com.minimalist.basic.entity.po;\n\nimport com.minimalist.basic.config.mybatis.InsertFullColumnHandler;\nimport com.minimalist.basic.config.mybatis.UpdateFullColumnHandler;\nimport com.minimalist.basic.config.mybatis.bo.BaseEntity;\nimport com.mybatisflex.annotation.Table;\nimport java.io.Serializable;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport lombok.EqualsAndHashCode;\nimport java.io.Serial;\n\n/**\n * 参数配置表 实体类。\n *\n * @author 小太阳\n * @since 2024-10-18\n */\n@Data\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\n@EqualsAndHashCode(callSuper = true)\n@Table(value = \"m_config\", onInsert = InsertFullColumnHandler.class, onUpdate = UpdateFullColumnHandler.class)\npublic class MConfig extends BaseEntity implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * 参数ID\n     */\n    private Long configId;\n\n    /**\n     * 参数名称\n     */\n    private String configName;\n\n    /**\n     * 参数键名\n     */\n    private String configKey;\n\n    /**\n     * 参数键值\n     */\n    private String configValue;\n\n    /**\n     * 说明\n     */\n    private String description;\n\n    /**\n     * 状态  0禁用 1正常\n     */\n    private Integer status;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/po/MDept.java",
    "content": "package com.minimalist.basic.entity.po;\n\nimport com.minimalist.basic.config.mybatis.InsertFullColumnHandler;\nimport com.minimalist.basic.config.mybatis.UpdateFullColumnHandler;\nimport com.minimalist.basic.config.mybatis.bo.BaseEntity;\nimport com.mybatisflex.annotation.Column;\nimport com.mybatisflex.annotation.Table;\nimport java.io.Serializable;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport lombok.EqualsAndHashCode;\nimport java.io.Serial;\n\n/**\n * 部门表 实体类。\n *\n * @author 小太阳\n * @since 2024-10-18\n */\n@Data\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\n@EqualsAndHashCode(callSuper = true)\n@Table(value = \"m_dept\", onInsert = InsertFullColumnHandler.class, onUpdate = UpdateFullColumnHandler.class)\npublic class MDept extends BaseEntity implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * 部门id\n     */\n    private Long deptId;\n\n    /**\n     * 父部门id\n     */\n    private Long parentDeptId;\n\n    /**\n     * 祖级列表\n     */\n    private String ancestors;\n\n    /**\n     * 部门名称\n     */\n    private String deptName;\n\n    /**\n     * 部门负责人\n     */\n    private Long deptLeader;\n\n    /**\n     * 显示顺序\n     */\n    private Integer deptSort;\n\n    /**\n     * 联系电话\n     */\n    private String phone;\n\n    /**\n     * 邮箱\n     */\n    private String email;\n\n    /**\n     * 状态  0禁用 1正常\n     */\n    private Integer status;\n\n    /**\n     * 租户编号\n     */\n    @Column(tenantId = true)\n    private Long tenantId;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/po/MDict.java",
    "content": "package com.minimalist.basic.entity.po;\n\nimport com.minimalist.basic.config.mybatis.InsertFullColumnHandler;\nimport com.minimalist.basic.config.mybatis.UpdateFullColumnHandler;\nimport com.minimalist.basic.config.mybatis.bo.BaseEntity;\nimport com.mybatisflex.annotation.Table;\nimport java.io.Serializable;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport lombok.EqualsAndHashCode;\nimport java.io.Serial;\n\n/**\n * 字典表 实体类。\n *\n * @author 小太阳\n * @since 2024-10-18\n */\n@Data\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\n@EqualsAndHashCode(callSuper = true)\n@Table(value = \"m_dict\", onInsert = InsertFullColumnHandler.class, onUpdate = UpdateFullColumnHandler.class)\npublic class MDict extends BaseEntity implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * 字典ID\n     */\n    private Long dictId;\n\n    /**\n     * 字典类型\n     */\n    private String dictType;\n\n    /**\n     * 字典key\n     */\n    private String dictKey;\n\n    /**\n     * 字典value\n     */\n    private String dictValue;\n\n    /**\n     * 字典名称\n     */\n    private String dictName;\n\n    /**\n     * 字典描述\n     */\n    private String dictDesc;\n\n    /**\n     * 字典排序值\n     */\n    private Integer dictOrder;\n\n    /**\n     * 字典样式，对应前端Tag组件的type\n     */\n    private String dictClass;\n\n    /**\n     * 状态  0禁用 1正常\n     */\n    private Integer status;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/po/MFile.java",
    "content": "package com.minimalist.basic.entity.po;\n\nimport com.minimalist.basic.config.mybatis.InsertFullColumnHandler;\nimport com.minimalist.basic.config.mybatis.UpdateFullColumnHandler;\nimport com.minimalist.basic.config.mybatis.bo.BaseEntity;\nimport com.mybatisflex.annotation.Column;\nimport com.mybatisflex.annotation.Table;\nimport java.io.Serializable;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport lombok.EqualsAndHashCode;\nimport java.io.Serial;\n\n/**\n * 文件表 实体类。\n *\n * @author 小太阳\n * @since 2024-10-18\n */\n@Data\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\n@EqualsAndHashCode(callSuper = true)\n@Table(value = \"m_file\", onInsert = InsertFullColumnHandler.class, onUpdate = UpdateFullColumnHandler.class)\npublic class MFile extends BaseEntity implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * 文件ID\n     */\n    private Long fileId;\n\n    /**\n     * 原文件名\n     */\n    private String fileName;\n\n    /**\n     * 现文件名\n     */\n    private String newFileName;\n\n    /**\n     * 文件大小\n     */\n    private Long fileSize;\n\n    /**\n     * 文件类型\n     */\n    private String fileType;\n\n    /**\n     * 文件基础路径\n     */\n    private String fileBasePath;\n\n    /**\n     * 文件相对路径\n     */\n    private String filePath;\n\n    /**\n     * 文件url\n     */\n    private String fileUrl;\n\n    /**\n     * 文件来源\n     */\n    private Integer fileSource;\n\n    /**\n     * 存储ID\n     */\n    private Long storageId;\n\n    /**\n     * 文件缩略图url\n     */\n    private String fileThUrl;\n\n    /**\n     * 文件缩略图文件名\n     */\n    private String fileThFilename;\n\n    /**\n     * 缩略图文件大小\n     */\n    private Long fileThSize;\n\n    /**\n     * 备注\n     */\n    private String remark;\n\n    /**\n     * 状态  0未使用 1已使用，默认未使用，代码中控制修改为已使用，可以定期清理未使用的文件\n     */\n    private Integer status;\n\n    /**\n     * 租户ID\n     */\n    @Column(tenantId = true)\n    private Long tenantId;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/po/MNotice.java",
    "content": "package com.minimalist.basic.entity.po;\n\nimport com.minimalist.basic.config.mybatis.InsertFullColumnHandler;\nimport com.minimalist.basic.config.mybatis.UpdateFullColumnHandler;\nimport com.minimalist.basic.config.mybatis.bo.BaseEntity;\nimport com.mybatisflex.annotation.Table;\nimport java.io.Serializable;\nimport java.time.LocalDateTime;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport lombok.EqualsAndHashCode;\nimport java.io.Serial;\n\n/**\n * 通知公告表 实体类。\n *\n * @author 小太阳\n * @since 2024-10-18\n */\n@Data\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\n@EqualsAndHashCode(callSuper = true)\n@Table(value = \"m_notice\", onInsert = InsertFullColumnHandler.class, onUpdate = UpdateFullColumnHandler.class)\npublic class MNotice extends BaseEntity implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * 公告ID\n     */\n    private Long noticeId;\n\n    /**\n     * 公告标题\n     */\n    private String noticeTitle;\n\n    /**\n     * 公告类型（1公告）\n     */\n    private Integer noticeType;\n\n    /**\n     * 公告内容\n     */\n    private String noticeContent;\n\n    /**\n     * 封面图文件ID，多个使用 , 分割\n     */\n    private String noticePicFileId;\n\n    /**\n     * 是否置顶 0否 1是\n     */\n    private Boolean noticeTop;\n\n    /**\n     * 延时发布的时间\n     */\n    private LocalDateTime noticeTimeInterval;\n\n    /**\n     * 排序\n     */\n    private Integer noticeSort;\n\n    /**\n     * 是否外链 0否 1是\n     */\n    private Boolean noticeOutChain;\n\n    /**\n     * 外部跳转链接\n     */\n    private String noticeLink;\n\n    /**\n     * 发布部门\n     */\n    private Long publishDeptId;\n\n    /**\n     * 发布人\n     */\n    private Long publishAuthorId;\n\n    /**\n     * 发布时间\n     */\n    private LocalDateTime publishTime;\n\n    /**\n     * 状态  0禁用 1正常\n     */\n    private Integer status;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/po/MPerms.java",
    "content": "package com.minimalist.basic.entity.po;\n\nimport com.minimalist.basic.config.mybatis.InsertFullColumnHandler;\nimport com.minimalist.basic.config.mybatis.UpdateFullColumnHandler;\nimport com.minimalist.basic.config.mybatis.bo.BaseEntity;\nimport com.mybatisflex.annotation.Table;\nimport java.io.Serializable;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport lombok.EqualsAndHashCode;\nimport java.io.Serial;\n\n/**\n * 权限表 实体类。\n *\n * @author 小太阳\n * @since 2024-10-18\n */\n@Data\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\n@EqualsAndHashCode(callSuper = true)\n@Table(value = \"m_perms\", onInsert = InsertFullColumnHandler.class, onUpdate = UpdateFullColumnHandler.class)\npublic class MPerms extends BaseEntity implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * 权限ID\n     */\n    private Long permId;\n\n    /**\n     * 权限标识\n     */\n    private String permCode;\n\n    /**\n     * 权限名称\n     */\n    private String permName;\n\n    /**\n     * 父权限ID\n     */\n    private Long parentPermId;\n\n    /**\n     * 显示顺序\n     */\n    private Integer permSort;\n\n    /**\n     * 路由地址\n     */\n    private String permPath;\n\n    /**\n     * 权限图标 菜单或目录时可传图标\n     */\n    private String permIcon;\n\n    /**\n     * 权限类型  M菜单 B按钮\n     */\n    private String permType;\n\n    /**\n     * 组件路径\n     */\n    private String component;\n\n    /**\n     * 是否为外部链接  0否 1是\n     */\n    private Boolean externalLink;\n\n    /**\n     * 是否可见 0隐藏 1显示）\n     */\n    private Boolean visible;\n\n    /**\n     * 备注\n     */\n    private String remark;\n\n    /**\n     * 状态  0禁用 1正常\n     */\n    private Integer status;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/po/MPost.java",
    "content": "package com.minimalist.basic.entity.po;\n\nimport com.minimalist.basic.config.mybatis.InsertFullColumnHandler;\nimport com.minimalist.basic.config.mybatis.UpdateFullColumnHandler;\nimport com.minimalist.basic.config.mybatis.bo.BaseEntity;\nimport com.mybatisflex.annotation.Column;\nimport com.mybatisflex.annotation.Table;\nimport java.io.Serializable;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport lombok.EqualsAndHashCode;\nimport java.io.Serial;\n\n/**\n * 岗位表 实体类。\n *\n * @author 小太阳\n * @since 2024-10-18\n */\n@Data\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\n@EqualsAndHashCode(callSuper = true)\n@Table(value = \"m_post\", onInsert = InsertFullColumnHandler.class, onUpdate = UpdateFullColumnHandler.class)\npublic class MPost extends BaseEntity implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * 岗位ID\n     */\n    private Long postId;\n\n    /**\n     * 岗位编码\n     */\n    private String postCode;\n\n    /**\n     * 岗位名称\n     */\n    private String postName;\n\n    /**\n     * 显示顺序\n     */\n    private Integer postSort;\n\n    /**\n     * 备注\n     */\n    private String remark;\n\n    /**\n     * 状态  0禁用 1正常\n     */\n    private Integer status;\n\n    /**\n     * 租户编号\n     */\n    @Column(tenantId = true)\n    private Long tenantId;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/po/MRole.java",
    "content": "package com.minimalist.basic.entity.po;\n\nimport com.minimalist.basic.config.mybatis.InsertFullColumnHandler;\nimport com.minimalist.basic.config.mybatis.UpdateFullColumnHandler;\nimport com.minimalist.basic.config.mybatis.bo.BaseEntity;\nimport com.mybatisflex.annotation.Column;\nimport com.mybatisflex.annotation.Table;\nimport java.io.Serializable;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport lombok.EqualsAndHashCode;\nimport java.io.Serial;\n\n/**\n * 角色表 实体类。\n *\n * @author 小太阳\n * @since 2024-10-18\n */\n@Data\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\n@EqualsAndHashCode(callSuper = true)\n@Table(value = \"m_role\", onInsert = InsertFullColumnHandler.class, onUpdate = UpdateFullColumnHandler.class)\npublic class MRole extends BaseEntity implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * 角色ID\n     */\n    private Long roleId;\n\n    /**\n     * 角色名称\n     */\n    private String roleName;\n\n    /**\n     * 角色编码\n     */\n    private String roleCode;\n\n    /**\n     * 显示顺序\n     */\n    private Integer roleSort;\n\n    /**\n     * 状态  0禁用 1正常\n     */\n    private Integer status;\n\n    /**\n     * 备注\n     */\n    private String remark;\n\n    /**\n     * 租户编号\n     */\n    @Column(tenantId = true)\n    private Long tenantId;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/po/MRoleDept.java",
    "content": "package com.minimalist.basic.entity.po;\n\nimport com.minimalist.basic.config.mybatis.InsertFullColumnHandler;\nimport com.minimalist.basic.config.mybatis.UpdateFullColumnHandler;\nimport com.minimalist.basic.config.mybatis.bo.BaseEntity;\nimport com.mybatisflex.annotation.Table;\nimport java.io.Serializable;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport lombok.EqualsAndHashCode;\nimport java.io.Serial;\n\n/**\n * 角色与部门关联表 1角色-N部门 实体类。\n *\n * @author 小太阳\n * @since 2024-10-18\n */\n@Data\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\n@EqualsAndHashCode\n@Table(value = \"m_role_dept\")\npublic class MRoleDept implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * 角色ID\n     */\n    private Long roleId;\n\n    /**\n     * 部门ID\n     */\n    private Long deptId;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/po/MRolePerm.java",
    "content": "package com.minimalist.basic.entity.po;\n\nimport com.minimalist.basic.config.mybatis.InsertFullColumnHandler;\nimport com.minimalist.basic.config.mybatis.UpdateFullColumnHandler;\nimport com.minimalist.basic.config.mybatis.bo.BaseEntity;\nimport com.mybatisflex.annotation.Table;\nimport java.io.Serializable;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport lombok.EqualsAndHashCode;\nimport java.io.Serial;\n\n/**\n * 角色与权限关联表 1角色-N权限 实体类。\n *\n * @author 小太阳\n * @since 2024-10-18\n */\n@Data\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\n@EqualsAndHashCode\n@Table(value = \"m_role_perm\")\npublic class MRolePerm implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * 角色ID\n     */\n    private Long roleId;\n\n    /**\n     * 权限ID\n     */\n    private Long permId;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/po/MStorage.java",
    "content": "package com.minimalist.basic.entity.po;\n\nimport com.minimalist.basic.config.mybatis.InsertFullColumnHandler;\nimport com.minimalist.basic.config.mybatis.UpdateFullColumnHandler;\nimport com.minimalist.basic.config.mybatis.bo.BaseEntity;\nimport com.mybatisflex.annotation.Table;\nimport java.io.Serializable;\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport lombok.EqualsAndHashCode;\nimport java.io.Serial;\n\n/**\n * 存储管理表 实体类。\n *\n * @author 小太阳\n * @since 2024-10-18\n */\n@Data\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\n@EqualsAndHashCode(callSuper = true)\n@Table(value = \"m_storage\", onInsert = InsertFullColumnHandler.class, onUpdate = UpdateFullColumnHandler.class)\npublic class MStorage extends BaseEntity implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    private Long storageId;\n\n    /**\n     * 名称\n     */\n    private String storageName;\n\n    /**\n     * 存储类型，用于标识存储平台，如本地、阿里云oss、七牛云oss等\n     */\n    private String storageType;\n\n    /**\n     * 说明\n     */\n    private String description;\n\n    /**\n     * 存储配置，JSON数据\n     */\n    private String storageConfig;\n\n    /**\n     * 状态\n     */\n    private Integer status;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/po/MTenant.java",
    "content": "package com.minimalist.basic.entity.po;\n\nimport com.minimalist.basic.config.mybatis.InsertFullColumnHandler;\nimport com.minimalist.basic.config.mybatis.UpdateFullColumnHandler;\nimport com.minimalist.basic.config.mybatis.bo.BaseEntity;\nimport com.mybatisflex.annotation.Table;\nimport java.io.Serializable;\nimport java.time.LocalDateTime;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport lombok.EqualsAndHashCode;\nimport java.io.Serial;\n\n/**\n * 租户表 实体类。\n *\n * @author 小太阳\n * @since 2024-10-18\n */\n@Data\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\n@EqualsAndHashCode(callSuper = true)\n@Table(value = \"m_tenant\", onInsert = InsertFullColumnHandler.class, onUpdate = UpdateFullColumnHandler.class)\npublic class MTenant extends BaseEntity implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * 租户ID\n     */\n    private Long tenantId;\n\n    /**\n     * 用户ID\n     */\n    private Long userId;\n\n    /**\n     * 租户套餐ID\n     */\n    private Long packageId;\n\n    /**\n     * 租户名\n     */\n    private String tenantName;\n\n    /**\n     * 租户过期时间\n     */\n    private LocalDateTime expireTime;\n\n    /**\n     * 可创建账号数量\n     */\n    private Integer accountCount;\n\n    /**\n     * 数据隔离方式  column字段隔离(默认)  db数据库隔离\n     */\n    private String dataIsolation;\n\n    /**\n     * 数据源名称  master默认使用主库\n     */\n    private String datasource;\n\n    /**\n     * 存储ID 表示该租户使用哪个文件存储\n     */\n    private Long storageId;\n\n    /**\n     * 状态 0禁用 1正常\n     */\n    private Integer status;\n\n    /**\n     * 备注\n     */\n    private String remark;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/po/MTenantDatasource.java",
    "content": "package com.minimalist.basic.entity.po;\n\nimport com.mybatisflex.annotation.Id;\nimport com.mybatisflex.annotation.KeyType;\nimport com.mybatisflex.annotation.Table;\nimport java.io.Serializable;\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport lombok.EqualsAndHashCode;\nimport java.io.Serial;\n\n/**\n * 租户数据源表 实体类。\n *\n * @author asus\n * @since 2025-02-14\n */\n@Data\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\n@EqualsAndHashCode\n@Table(value = \"m_tenant_datasource\")\npublic class MTenantDatasource implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    /** ID自增 */\n    @Id(keyType = KeyType.Auto)\n    private Long id;\n\n    /**\n     * 数据源ID\n     */\n    private Long datasourceId;\n\n    /**\n     * 租户ID\n     */\n    private Long tenantId;\n\n    /**\n     * 数据源名称\n     */\n    private String datasourceName;\n\n    /**\n     * 数据源连接\n     */\n    private String datasourceUrl;\n\n    /**\n     * 数据源用户名\n     */\n    private String username;\n\n    /**\n     * 数据源密码\n     */\n    private String password;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/po/MTenantPackage.java",
    "content": "package com.minimalist.basic.entity.po;\n\nimport com.minimalist.basic.config.mybatis.InsertFullColumnHandler;\nimport com.minimalist.basic.config.mybatis.UpdateFullColumnHandler;\nimport com.minimalist.basic.config.mybatis.bo.BaseEntity;\nimport com.mybatisflex.annotation.Table;\nimport java.io.Serializable;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport lombok.EqualsAndHashCode;\nimport java.io.Serial;\n\n/**\n * 租户套餐表 实体类。\n *\n * @author 小太阳\n * @since 2024-10-18\n */\n@Data\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\n@EqualsAndHashCode(callSuper = true)\n@Table(value = \"m_tenant_package\", onInsert = InsertFullColumnHandler.class, onUpdate = UpdateFullColumnHandler.class)\npublic class MTenantPackage extends BaseEntity implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * 套餐ID\n     */\n    private Long packageId;\n\n    /**\n     * 套餐名称\n     */\n    private String packageName;\n\n    /**\n     * 状态 0禁用 1正常\n     */\n    private Integer status;\n\n    /**\n     * 备注\n     */\n    private String remark;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/po/MTenantPackagePerm.java",
    "content": "package com.minimalist.basic.entity.po;\n\nimport com.minimalist.basic.config.mybatis.InsertFullColumnHandler;\nimport com.minimalist.basic.config.mybatis.UpdateFullColumnHandler;\nimport com.minimalist.basic.config.mybatis.bo.BaseEntity;\nimport com.mybatisflex.annotation.Table;\nimport java.io.Serializable;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport lombok.EqualsAndHashCode;\nimport java.io.Serial;\n\n/**\n * 租户套餐与权限关联表 实体类。\n *\n * @author 小太阳\n * @since 2024-10-18\n */\n@Data\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\n@EqualsAndHashCode\n@Table(value = \"m_tenant_package_perm\")\npublic class MTenantPackagePerm implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * 租户套餐ID\n     */\n    private Long packageId;\n\n    /**\n     * 权限ID\n     */\n    private Long permId;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/po/MUser.java",
    "content": "package com.minimalist.basic.entity.po;\n\nimport com.minimalist.basic.config.mybatis.InsertFullColumnHandler;\nimport com.minimalist.basic.config.mybatis.UpdateFullColumnHandler;\nimport com.minimalist.basic.config.mybatis.bo.BaseEntity;\nimport com.mybatisflex.annotation.Column;\nimport com.mybatisflex.annotation.Table;\nimport java.io.Serializable;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport lombok.EqualsAndHashCode;\nimport java.io.Serial;\n\n/**\n * 用户表 实体类。\n *\n * @author 小太阳\n * @since 2024-10-18\n */\n@Data\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\n@EqualsAndHashCode(callSuper = true)\n@Table(value = \"m_user\", onInsert = InsertFullColumnHandler.class, onUpdate = UpdateFullColumnHandler.class)\npublic class MUser extends BaseEntity implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * 用户ID\n     */\n    private Long userId;\n\n    /**\n     * 用户账号\n     */\n    private String username;\n\n    /**\n     * 密码\n     */\n    private String password;\n\n    /**\n     * 盐值\n     */\n    private String salt;\n\n    /**\n     * 用户昵称\n     */\n    private String nickname;\n\n    /**\n     * 用户真实姓名\n     */\n    private String userRealName;\n\n    /**\n     * 用户邮箱\n     */\n    private String email;\n\n    /**\n     * 手机号码\n     */\n    private String phone;\n\n    /**\n     * 用户性别  0未知 1男 2女\n     */\n    private Integer userSex;\n\n    /**\n     * 头像base64编码\n     */\n    private String userAvatar;\n\n    /**\n     * 备注\n     */\n    private String remark;\n\n    /**\n     * 状态  0禁用 1正常\n     */\n    private Integer status;\n\n    /**\n     * 租户编号\n     */\n    @Column(tenantId = true)\n    private Long tenantId;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/po/MUserDept.java",
    "content": "package com.minimalist.basic.entity.po;\n\nimport com.minimalist.basic.config.mybatis.InsertFullColumnHandler;\nimport com.minimalist.basic.config.mybatis.UpdateFullColumnHandler;\nimport com.minimalist.basic.config.mybatis.bo.BaseEntity;\nimport com.mybatisflex.annotation.Table;\nimport java.io.Serializable;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport lombok.EqualsAndHashCode;\nimport java.io.Serial;\n\n/**\n * 用户与岗位关联表 实体类。\n *\n * @author 小太阳\n * @since 2024-10-18\n */\n@Data\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\n@EqualsAndHashCode\n@Table(value = \"m_user_dept\")\npublic class MUserDept implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * 用户ID\n     */\n    private Long userId;\n\n    /**\n     * 部门ID\n     */\n    private Long deptId;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/po/MUserPost.java",
    "content": "package com.minimalist.basic.entity.po;\n\nimport com.minimalist.basic.config.mybatis.InsertFullColumnHandler;\nimport com.minimalist.basic.config.mybatis.UpdateFullColumnHandler;\nimport com.minimalist.basic.config.mybatis.bo.BaseEntity;\nimport com.mybatisflex.annotation.Table;\nimport java.io.Serializable;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport lombok.EqualsAndHashCode;\nimport java.io.Serial;\n\n/**\n * 用户与岗位关联表 1用户-N岗位 实体类。\n *\n * @author 小太阳\n * @since 2024-10-18\n */\n@Data\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\n@EqualsAndHashCode\n@Table(value = \"m_user_post\")\npublic class MUserPost implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * 用户ID\n     */\n    private Long userId;\n\n    /**\n     * 岗位ID\n     */\n    private Long postId;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/po/MUserRole.java",
    "content": "package com.minimalist.basic.entity.po;\n\nimport com.minimalist.basic.config.mybatis.InsertFullColumnHandler;\nimport com.minimalist.basic.config.mybatis.UpdateFullColumnHandler;\nimport com.minimalist.basic.config.mybatis.bo.BaseEntity;\nimport com.mybatisflex.annotation.Table;\nimport java.io.Serializable;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport lombok.EqualsAndHashCode;\nimport java.io.Serial;\n\n/**\n * 用户与角色关联表 1用户-N角色 实体类。\n *\n * @author 小太阳\n * @since 2024-10-18\n */\n@Data\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\n@EqualsAndHashCode\n@Table(value = \"m_user_role\")\npublic class MUserRole implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * 用户ID\n     */\n    private Long userId;\n\n    /**\n     * 角色ID\n     */\n    private Long roleId;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/config/ConfigQueryVO.java",
    "content": "package com.minimalist.basic.entity.vo.config;\n\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.config.mybatis.bo.PageReq;\nimport com.minimalist.basic.config.swagger.SchemaEnum;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.io.Serial;\nimport java.io.Serializable;\n\n@Data\n@Schema(name = \"参数配置查询实体\")\npublic class ConfigQueryVO extends PageReq implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @Schema(name = \"configName\", description = \"参数名称\", type = \"string\")\n    private String configName;\n\n    @Schema(name = \"configKey\", description = \"参数键名\", type = \"string\")\n    private String configKey;\n\n    @SchemaEnum(implementation = StatusEnum.class)\n    @Schema(name = \"status\", description = \"参数配置状态\", type = \"integer\")\n    private Integer status;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/config/ConfigVO.java",
    "content": "package com.minimalist.basic.entity.vo.config;\n\nimport com.fasterxml.jackson.databind.annotation.JsonSerialize;\nimport com.fasterxml.jackson.databind.ser.std.ToStringSerializer;\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.config.swagger.SchemaEnum;\nimport com.minimalist.basic.utils.Add;\nimport com.minimalist.basic.utils.Update;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotNull;\nimport lombok.Data;\nimport java.io.Serial;\nimport java.io.Serializable;\n\n@Data\n@Schema(name = \"参数配置实体\")\npublic class ConfigVO implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @NotNull(message = \"参数ID不能为空\", groups = {Update.class})\n    @JsonSerialize(using = ToStringSerializer.class)\n    @Schema(name = \"configId\", description = \"参数ID\", type = \"string\")\n    private Long configId;\n\n    @NotBlank(message = \"参数名称不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"configName\", description = \"参数名称\", type = \"string\")\n    private String configName;\n\n    @NotBlank(message = \"参数键名不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"configKey\", description = \"参数键名\", type = \"string\")\n    private String configKey;\n\n    @NotBlank(message = \"参数键值不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"configValue\", description = \"参数键值\", type = \"string\")\n    private String configValue;\n\n    @Schema(name = \"description\", description = \"说明\", type = \"string\")\n    private String description;\n\n    @SchemaEnum(implementation = StatusEnum.class)\n    @NotNull(message = \"参数配置状态不能为空\", groups = {Update.class})\n    @Schema(name = \"status\", description = \"参数配置状态\", type = \"integer\")\n    private Integer status;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/dept/DeptQueryVO.java",
    "content": "package com.minimalist.basic.entity.vo.dept;\n\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.config.swagger.SchemaEnum;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport java.io.Serial;\nimport java.io.Serializable;\n\n@Data\n@Schema(name = \"部门查询实体\")\npublic class DeptQueryVO implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @Schema(name = \"deptName\", description = \"部门名称\", type = \"string\")\n    private String deptName;\n\n    @SchemaEnum(implementation = StatusEnum.class)\n    @Schema(name = \"status\", description = \"部门状态\", type = \"integer\")\n    private Integer status;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/dept/DeptVO.java",
    "content": "package com.minimalist.basic.entity.vo.dept;\n\nimport com.fasterxml.jackson.databind.annotation.JsonSerialize;\nimport com.fasterxml.jackson.databind.ser.std.ToStringSerializer;\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.config.swagger.SchemaEnum;\nimport com.minimalist.basic.utils.Add;\nimport com.minimalist.basic.utils.Update;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotNull;\nimport lombok.Data;\nimport java.io.Serial;\nimport java.io.Serializable;\nimport java.util.List;\n\n@Data\n@Schema(name = \"部门实体\")\npublic class DeptVO implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @NotNull(message = \"部门ID不能为空\", groups = {Update.class})\n    @JsonSerialize(using = ToStringSerializer.class)\n    @Schema(name = \"deptId\", description = \"部门id\", type = \"string\")\n    private Long deptId;\n\n    @NotNull(message = \"上级部门ID不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"parentDeptId\", description = \"父部门ID\", type = \"string\")\n    @JsonSerialize(using = ToStringSerializer.class)\n    private Long parentDeptId;\n\n    @Schema(name = \"ancestors\", description = \"祖级列表\", type = \"string\")\n    private String ancestors;\n\n    @NotBlank(message = \"部门名称不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"deptName\", description = \"部门名称\", type = \"string\")\n    private String deptName;\n\n    @NotNull(message = \"排序值不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"deptSort\", description = \"排序值\", type = \"integer\")\n    private Integer deptSort;\n\n    @NotNull(message = \"部门负责人不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"deptLeader\", description = \"部门负责人\", type = \"string\")\n    @JsonSerialize(using = ToStringSerializer.class)\n    private Long deptLeader;\n\n    @NotBlank(message = \"部门电话不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"phone\", description = \"部门电话\", type = \"string\")\n    private String phone;\n\n    @NotBlank(message = \"部门邮箱不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"email\", description = \"部门邮箱\", type = \"string\")\n    private String email;\n\n    @SchemaEnum(implementation = StatusEnum.class)\n    @NotNull(message = \"部门状态不能为空\", groups = {Update.class})\n    @Schema(name = \"status\", description = \"部门状态\", type = \"integer\")\n    private Integer status;\n\n    @JsonSerialize(using = ToStringSerializer.class)\n    @Schema(name = \"tenantId\", description = \"租户ID\", type = \"string\")\n    private Long tenantId;\n\n    @Schema(name = \"children\", description = \"部门子集\", type = \"array\")\n    private List<DeptVO> children;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/dict/DictCacheVO.java",
    "content": "package com.minimalist.basic.entity.vo.dict;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport java.io.Serial;\nimport java.io.Serializable;\nimport java.util.List;\n\n@Data\n@Schema(name = \"字典数据实体 - 一般用于下拉框的数据展示或编码转换使用\")\npublic class DictCacheVO implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @Data\n    @Schema(name = \"字典key和value实体\")\n    public static class DictKV {\n\n        @Schema(name = \"dictKey\", description = \"字典key，字典编码\", type = \"string\")\n        private String dictKey;\n\n        @Schema(name = \"dictValue\", description = \"字典value，字典编码对应的中文\", type = \"string\")\n        private String dictValue;\n\n        @Schema(name = \"dictType\", description = \"字典类型\", type = \"string\")\n        private String dictType;\n\n        @Schema(name = \"dictClass\", description = \"字典样式，对应前端Tag组件的type\", type = \"string\")\n        private String dictClass;\n\n    }\n\n    @Schema(name = \"dictType\", description = \"字典类型\", type = \"string\")\n    private String dictType;\n\n    @Schema(name = \"dictList\", description = \"字典列表\", type = \"array\")\n    private List<DictKV> dictList;\n\n}\n\n\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/dict/DictDataVO.java",
    "content": "package com.minimalist.basic.entity.vo.dict;\n\nimport com.fasterxml.jackson.databind.annotation.JsonSerialize;\nimport com.fasterxml.jackson.databind.ser.std.ToStringSerializer;\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.config.swagger.SchemaEnum;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotNull;\nimport lombok.Data;\nimport java.io.Serial;\nimport java.io.Serializable;\n\n@Data\n@Schema(name = \"字典数据实体\")\npublic class DictDataVO implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @JsonSerialize(using = ToStringSerializer.class)\n    @Schema(name = \"dictId\", description = \"字典ID\", type = \"string\")\n    private Long dictId;\n\n    @NotBlank(message = \"字典key不能为空\")\n    @Schema(name = \"dictKey\", description = \"字典key\", type = \"string\")\n    private String dictKey;\n\n    @NotBlank(message = \"字典value不能为空\")\n    @Schema(name = \"dictValue\", description = \"字典value\", type = \"string\")\n    private String dictValue;\n\n    @NotNull(message = \"字典排序值不能为空\")\n    @Schema(name = \"dictOrder\", description = \"字典排序值\", type = \"integer\")\n    private Integer dictOrder;\n\n    @Schema(name = \"dictClass\", description = \"字典样式，对应前端Tag组件的type\", type = \"string\")\n    private String dictClass;\n\n    @SchemaEnum(implementation = StatusEnum.class)\n    @Schema(name = \"status\", description = \"字典状态\", type = \"integer\")\n    private Integer status;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/dict/DictInfoVO.java",
    "content": "package com.minimalist.basic.entity.vo.dict;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotEmpty;\nimport lombok.Data;\n\nimport java.io.Serial;\nimport java.io.Serializable;\nimport java.util.List;\n\n@Data\n@Schema(name = \"字典基础信息实体\")\npublic class DictInfoVO implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @NotBlank(message = \"字典类型不能为空\")\n    @Schema(name = \"dictType\", description = \"字典类型\", type = \"string\")\n    private String dictType;\n\n    @NotBlank(message = \"字典名称不能为空\")\n    @Schema(name = \"dictName\", description = \"字典名称\", type = \"string\")\n    private String dictName;\n\n    @NotBlank(message = \"字典描述不能为空\")\n    @Schema(name = \"dictDesc\", description = \"字典描述\", type = \"string\")\n    private String dictDesc;\n\n    @NotEmpty(message = \"字典数据不能为空\")\n    @Schema(name = \"dictDataList\", description = \"字典数据列表\", type = \"array\")\n    private List<DictDataVO> dictDataList;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/dict/DictListVO.java",
    "content": "package com.minimalist.basic.entity.vo.dict;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.io.Serial;\nimport java.io.Serializable;\nimport java.util.List;\n\n@Data\n@Schema(name = \"字典列表实体\")\npublic class DictListVO implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @Schema(name = \"dictType\", description = \"字典类型\", type = \"string\")\n    private String dictType;\n\n    @Schema(name = \"dictList\", description = \"字典列表\", type = \"array\")\n    private List<DictVO> dictList;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/dict/DictQueryVO.java",
    "content": "package com.minimalist.basic.entity.vo.dict;\n\nimport com.minimalist.basic.config.mybatis.bo.PageReq;\nimport com.minimalist.basic.config.swagger.SchemaEnum;\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport java.io.Serial;\nimport java.io.Serializable;\n\n@Data\n@Schema(name = \"字典查询实体\")\npublic class DictQueryVO extends PageReq implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @Schema(name = \"dictName\", description = \"字典名称\", type = \"string\")\n    private String dictName;\n\n    @Schema(name = \"dictType\", description = \"字典类型\", type = \"string\")\n    private String dictType;\n\n    @SchemaEnum(implementation = StatusEnum.class)\n    @Schema(name = \"status\", description = \"字典状态\", type = \"int\")\n    private Byte status;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/dict/DictVO.java",
    "content": "package com.minimalist.basic.entity.vo.dict;\n\nimport com.fasterxml.jackson.databind.annotation.JsonSerialize;\nimport com.fasterxml.jackson.databind.ser.std.ToStringSerializer;\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.config.swagger.SchemaEnum;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport java.io.Serial;\nimport java.io.Serializable;\n\n@Data\n@Schema(name = \"字典管理实体\")\npublic class DictVO implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @JsonSerialize(using = ToStringSerializer.class)\n    @Schema(name = \"dictId\", description = \"字典ID\", type = \"string\")\n    private Long dictId;\n\n    @Schema(name = \"dictType\", description = \"字典类型\", type = \"string\")\n    private String dictType;\n\n    @Schema(name = \"dictKey\", description = \"字典key\", type = \"string\")\n    private String dictKey;\n\n    @Schema(name = \"dictValue\", description = \"字典value\", type = \"string\")\n    private String dictValue;\n\n    @Schema(name = \"dictName\", description = \"字典名称\", type = \"string\")\n    private String dictName;\n\n    @Schema(name = \"dictDesc\", description = \"字典描述\", type = \"string\")\n    private String dictDesc;\n\n    @Schema(name = \"dictOrder\", description = \"字典排序值\", type = \"integer\")\n    private Integer dictOrder;\n\n    @Schema(name = \"dictClass\", description = \"字典样式，对应前端Tag组件的type\", type = \"string\")\n    private String dictClass;\n\n    @SchemaEnum(implementation = StatusEnum.class)\n    @Schema(name = \"status\", description = \"字典状态\", type = \"integer\")\n    private Integer status;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/file/FileQueryVO.java",
    "content": "package com.minimalist.basic.entity.vo.file;\n\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.config.mybatis.bo.PageReq;\nimport com.minimalist.basic.config.swagger.SchemaEnum;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport java.io.Serial;\nimport java.io.Serializable;\n\n@Data\n@Schema(name = \"文件查询实体\")\npublic class FileQueryVO extends PageReq implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @Schema(name = \"fileName\", description = \"文件名\", type = \"string\")\n    private String fileName;\n\n    @Schema(name = \"fileSource\", description = \"文件来源\", type = \"string\")\n    private Integer fileSource;\n\n    @Schema(name = \"fileType\", description = \"文件类型，由字典配置，与file表file_type字段对应\", type = \"string\")\n    private String fileType;\n\n    @SchemaEnum(implementation = StatusEnum.class)\n    @Schema(name = \"status\", description = \"文件状态\", type = \"int\")\n    private Integer status;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/file/FileUploadBatchVO.java",
    "content": "package com.minimalist.basic.entity.vo.file;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\nimport lombok.Data;\nimport org.springframework.web.multipart.MultipartFile;\nimport java.io.Serial;\nimport java.io.Serializable;\nimport java.util.List;\n\n@Data\n@Schema(name = \"文件上传实体\")\npublic class FileUploadBatchVO implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @NotEmpty(message = \"上传的文件不能为空\")\n    @Schema(name = \"files\", description = \"文件列表(支持多个文件)\", type = \"array\")\n    private List<MultipartFile> files;\n\n    @NotNull(message = \"文件来源不能为空\")\n    @Schema(name = \"fileSource\", description = \"文件来源，字典：file-source-path\", type = \"int\")\n    private Integer fileSource;\n\n    @Schema(name = \"storageId\", description = \"存储ID，可为空。为空则取默认使用的存储\", type = \"int\")\n    private Long storageId;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/file/FileUploadRespVO.java",
    "content": "package com.minimalist.basic.entity.vo.file;\n\nimport com.fasterxml.jackson.databind.annotation.JsonSerialize;\nimport com.fasterxml.jackson.databind.ser.std.ToStringSerializer;\nimport com.minimalist.basic.config.convert.FileSizeSerializer;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.io.Serial;\nimport java.io.Serializable;\n\n@Data\n@Schema(name = \"文件上传响应实体\")\npublic class FileUploadRespVO implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @JsonSerialize(using = ToStringSerializer.class)\n    @Schema(name = \"fileId\", description = \"文件ID\", type = \"string\")\n    private Long fileId;\n\n    @Schema(name = \"fileName\", description = \"文件名称\", type = \"string\")\n    private String fileName;\n\n    @Schema(name = \"filePath\", description = \"文件路径\", type = \"string\")\n    private String filePath;\n\n    @Schema(name = \"fileUrl\", description = \"文件URL\", type = \"string\")\n    private String fileUrl;\n\n    @Schema(name = \"fileType\", description = \"文件类型\", type = \"string\")\n    private String fileType;\n\n    @Schema(name = \"fileSuffix\", description = \"文件后缀\", type = \"string\")\n    private String fileSuffix;\n\n    @JsonSerialize(using = FileSizeSerializer.class)\n    @Schema(name = \"fileSize\", description = \"文件大小\", type = \"string\")\n    private Long fileSize;\n\n    @Schema(name = \"fileSource\", description = \"文件来源\", type = \"string\")\n    private Integer fileSource;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/file/FileUploadVO.java",
    "content": "package com.minimalist.basic.entity.vo.file;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotNull;\nimport lombok.Data;\nimport org.springframework.web.multipart.MultipartFile;\nimport java.io.Serial;\nimport java.io.Serializable;\n\n@Data\n@Schema(name = \"文件上传实体\")\npublic class FileUploadVO implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @NotNull(message = \"上传的文件不能为空\")\n    @Schema(name = \"file\", description = \"文件(单个文件)\", type = \"file\")\n    private MultipartFile file;\n\n    @Schema(name = \"fileSource\", description = \"文件来源，字典：file-source-path\", type = \"int\")\n    private Integer fileSource = -1;\n\n    @Schema(name = \"storageId\", description = \"存储ID，可为空。为空则取默认使用的存储\", type = \"int\")\n    private Long storageId;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/file/FileVO.java",
    "content": "package com.minimalist.basic.entity.vo.file;\n\nimport com.fasterxml.jackson.databind.annotation.JsonDeserialize;\nimport com.fasterxml.jackson.databind.annotation.JsonSerialize;\nimport com.fasterxml.jackson.databind.ser.std.ToStringSerializer;\nimport com.minimalist.basic.config.convert.FileSizeDeserializer;\nimport com.minimalist.basic.config.convert.FileSizeSerializer;\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.config.swagger.SchemaEnum;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport java.io.Serial;\nimport java.io.Serializable;\n\n@Data\n@Schema(name = \"文件实体\")\npublic class FileVO implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @JsonSerialize(using = ToStringSerializer.class)\n    @Schema(name = \"fileId\", description = \"文件ID\", type = \"string\")\n    private Long fileId;\n\n    @Schema(name = \"fileName\", description = \"文件名\", type = \"string\")\n    private String fileName;\n\n    @JsonSerialize(using = FileSizeSerializer.class)\n    @JsonDeserialize(using = FileSizeDeserializer.class)\n    @Schema(name = \"fileSize\", description = \"文件大小\", type = \"string\")\n    private Long fileSize;\n\n    @Schema(name = \"fileType\", description = \"文件类型\", type = \"string\")\n    private String fileType;\n\n    @Schema(name = \"fileType\", description = \"文件类型后缀\", type = \"string\")\n    private String fileTypeSuffix;\n\n    @Schema(name = \"fileUrl\", description = \"文件URL\", type = \"string\")\n    private String fileUrl;\n\n    @SchemaEnum(implementation = StatusEnum.class)\n    @Schema(name = \"fileSource\", description = \"文件来源\", type = \"int\")\n    private Integer fileSource;\n\n    @JsonSerialize(using = ToStringSerializer.class)\n    @Schema(name = \"storageId\", description = \"存储ID\", type = \"string\")\n    private Long storageId;\n\n    @Schema(name = \"fileThUrl\", description = \"文件缩略图URL\", type = \"string\")\n    private String fileThUrl;\n\n    @SchemaEnum(implementation = StatusEnum.class)\n    @Schema(name = \"status\", description = \"文件状态\", type = \"int\")\n    private Integer status;\n\n    @Schema(name = \"remark\", description = \"备注\", type = \"string\")\n    private String remark;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/notice/NoticeQueryVO.java",
    "content": "package com.minimalist.basic.entity.vo.notice;\n\nimport com.minimalist.basic.entity.enums.NoticeEnum;\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.config.mybatis.bo.PageReq;\nimport com.minimalist.basic.config.swagger.SchemaEnum;\nimport com.minimalist.basic.utils.Add;\nimport com.minimalist.basic.utils.Update;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotNull;\nimport lombok.Data;\nimport java.io.Serial;\nimport java.io.Serializable;\n\n@Data\n@Schema(name = \"公告查询实体\")\npublic class NoticeQueryVO extends PageReq implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @Schema(name = \"noticeTitle\", description = \"公告标题\", type = \"string\")\n    private String noticeTitle;\n\n    @SchemaEnum(implementation = NoticeEnum.NoticeType.class)\n    @Schema(name = \"noticeType\", description = \"公告类型\", type = \"string\")\n    private Integer noticeType;\n\n    @NotNull(message = \"公告状态不能为空\", groups = {Add.class, Update.class})\n    @SchemaEnum(implementation = StatusEnum.class)\n    @Schema(name = \"status\", description = \"公告状态\", type = \"string\")\n    private Byte status;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/notice/NoticeVO.java",
    "content": "package com.minimalist.basic.entity.vo.notice;\n\nimport cn.hutool.core.date.DatePattern;\nimport com.fasterxml.jackson.annotation.JsonFormat;\nimport com.fasterxml.jackson.databind.annotation.JsonSerialize;\nimport com.fasterxml.jackson.databind.ser.std.ToStringSerializer;\nimport com.minimalist.basic.entity.enums.NoticeEnum;\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.config.swagger.SchemaEnum;\nimport com.minimalist.basic.entity.vo.file.FileVO;\nimport com.minimalist.basic.utils.Add;\nimport com.minimalist.basic.utils.Update;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotNull;\nimport lombok.Data;\nimport org.springframework.format.annotation.DateTimeFormat;\nimport java.io.Serial;\nimport java.io.Serializable;\nimport java.time.LocalDateTime;\nimport java.util.ArrayList;\nimport java.util.List;\n\n@Data\n@Schema(name = \"公告管理实体\")\npublic class NoticeVO implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @NotNull(message = \"公告ID不能为空\", groups = {Update.class})\n    @JsonSerialize(using = ToStringSerializer.class)\n    @Schema(name = \"noticeId\", description = \"公告ID\", type = \"string\")\n    private Long noticeId;\n\n    @NotBlank(message = \"公告标题不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"noticeTitle\", description = \"公告标题\", type = \"string\")\n    private String noticeTitle;\n\n    @NotNull(message = \"公告类型不能为空\", groups = {Add.class, Update.class})\n    @SchemaEnum(implementation = NoticeEnum.NoticeType.class)\n    @Schema(name = \"noticeType\", description = \"公告类型\", type = \"string\")\n    private Integer noticeType;\n\n    @NotBlank(message = \"公告内容不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"noticeContent\", description = \"公告内容\", type = \"string\")\n    private String noticeContent;\n\n    @Schema(name = \"noticePicFileId\", description = \"公告封面图文件ID，多张 , 分割\", type = \"string\")\n    private String noticePicFileId;\n\n    @Schema(name = \"noticePicFile\", description = \"公告封面图文件信息，新增和修改时直接传入文件信息\", type = \"array\")\n    private List<FileVO> noticePicFile = new ArrayList<>();\n\n    @Schema(name = \"noticeTop\", description = \"是否置顶\", type = \"string\")\n    private Boolean noticeTop;\n\n    @DateTimeFormat(pattern = DatePattern.NORM_DATETIME_PATTERN)\n    @JsonFormat(pattern = DatePattern.NORM_DATETIME_PATTERN)\n    @Schema(name = \"noticeTimeInterval\", description = \"延期发布时间\", type = \"string\")\n    private LocalDateTime noticeTimeInterval;\n\n    @Schema(name = \"noticeSort\", description = \"排序值\", type = \"string\")\n    private Integer noticeSort;\n\n    @Schema(name = \"noticeOutChain\", description = \"是否外链\", type = \"string\")\n    private Boolean noticeOutChain;\n\n    @Schema(name = \"noticeLink\", description = \"外部链接\", type = \"string\")\n    private String noticeLink;\n\n    @JsonSerialize(using = ToStringSerializer.class)\n    @Schema(name = \"publishDeptId\", description = \"发布部门ID\", type = \"string\")\n    private Long publishDeptId;\n\n    @JsonSerialize(using = ToStringSerializer.class)\n    @NotNull(message = \"发布人不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"publishAuthorId\", description = \"发布人ID\", type = \"string\")\n    private Long publishAuthorId;\n\n    @JsonFormat(pattern = DatePattern.NORM_DATETIME_PATTERN)\n    @Schema(name = \"publishTime\", description = \"发布时间\", type = \"string\")\n    private LocalDateTime publishTime;\n\n    @NotNull(message = \"公告状态不能为空\", groups = {Add.class, Update.class})\n    @SchemaEnum(implementation = StatusEnum.class)\n    @Schema(name = \"status\", description = \"公告状态\", type = \"string\")\n    private Byte status;\n\n    @JsonSerialize(using = ToStringSerializer.class)\n    @Schema(name = \"createId\", description = \"创建人ID\", type = \"string\")\n    private Long createId;\n\n    @JsonFormat(pattern = DatePattern.NORM_DATETIME_PATTERN)\n    @Schema(name = \"createTime\", description = \"创建时间\", type = \"string\")\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/perm/PermQueryVO.java",
    "content": "package com.minimalist.basic.entity.vo.perm;\n\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.config.swagger.SchemaEnum;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport java.io.Serial;\nimport java.io.Serializable;\n\n@Data\n@Schema(name = \"权限查询实体\")\npublic class PermQueryVO implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @Schema(name = \"permName\", description = \"权限名称\", type = \"string\")\n    private String permName;\n\n    @Schema(name = \"permType\", description = \"权限类型\", type = \"string\")\n    private String permType;\n\n    @SchemaEnum(implementation = StatusEnum.class)\n    @Schema(name = \"status\", description = \"状态\", type = \"integer\")\n    private Integer status;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/perm/PermVO.java",
    "content": "package com.minimalist.basic.entity.vo.perm;\n\nimport com.fasterxml.jackson.databind.annotation.JsonSerialize;\nimport com.fasterxml.jackson.databind.ser.std.ToStringSerializer;\nimport com.minimalist.basic.entity.enums.PermEnum;\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.config.swagger.SchemaEnum;\nimport com.minimalist.basic.utils.Add;\nimport com.minimalist.basic.utils.Update;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotNull;\nimport lombok.Data;\nimport java.io.Serial;\nimport java.util.List;\nimport java.io.Serializable;\n\n@Data\n@Schema(name = \"权限实体\")\npublic class PermVO implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @NotNull(message = \"权限ID不能为空\", groups = {Update.class})\n    @Schema(name = \"permId\", description = \"权限ID\", type = \"string\")\n    @JsonSerialize(using = ToStringSerializer.class)\n    private Long permId;\n\n    @Schema(name = \"permCode\", description = \"权限编码\", type = \"string\")\n    private String permCode;\n\n    @NotBlank(message = \"权限名称不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"permName\", description = \"权限名称\", type = \"string\")\n    private String permName;\n\n    @NotNull(message = \"上级权限ID不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"parentPermId\", description = \"父权限ID\", type = \"string\")\n    @JsonSerialize(using = ToStringSerializer.class)\n    private Long parentPermId;\n\n    @NotNull(message = \"排序值不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"permSort\", description = \"排序值\", type = \"integer\")\n    private Integer permSort;\n\n    @Schema(name = \"permPath\", description = \"路由地址\", type = \"string\")\n    private String permPath;\n\n    @Schema(name = \"permIcon\", description = \"权限图标，菜单可传图标\", type = \"string\")\n    private String permIcon;\n\n    @SchemaEnum(implementation = PermEnum.PermType.class)\n    @Schema(name = \"permType\", description = \"权限类型\", type = \"string\")\n    private String permType;\n\n    @Schema(name = \"component\", description = \"组件路径\", type = \"string\")\n    private String component;\n\n    @NotNull(message = \"是否可见不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"visible\", description = \"是否可见\", type = \"boolean\")\n    private Boolean visible;\n\n    @SchemaEnum(implementation = StatusEnum.class)\n    @NotNull(message = \"权限状态不能为空\", groups = {Update.class})\n    @Schema(name = \"status\", description = \"权限状态\", type = \"integer\")\n    private Integer status;\n\n    @Schema(name = \"remark\", description = \"备注\", type = \"string\")\n    private String remark;\n\n    @Schema(name = \"children\", description = \"权限子集\", type = \"array\")\n    private List<PermVO> children;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/post/PostQueryVO.java",
    "content": "package com.minimalist.basic.entity.vo.post;\n\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.config.mybatis.bo.PageReq;\nimport com.minimalist.basic.config.swagger.SchemaEnum;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport java.io.Serial;\nimport java.io.Serializable;\n\n@Data\n@Schema(name = \"岗位查询实体\")\npublic class PostQueryVO extends PageReq implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @Schema(name = \"postName\", description = \"岗位名称\", type = \"string\")\n    private String postName;\n\n    @Schema(name = \"postCode\", description = \"岗位编码\", type = \"string\")\n    private String postCode;\n\n    @SchemaEnum(implementation = StatusEnum.class)\n    @Schema(name = \"status\", description = \"岗位状态\", type = \"integer\")\n    private Integer status;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/post/PostVO.java",
    "content": "package com.minimalist.basic.entity.vo.post;\n\nimport com.fasterxml.jackson.databind.annotation.JsonSerialize;\nimport com.fasterxml.jackson.databind.ser.std.ToStringSerializer;\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.config.swagger.SchemaEnum;\nimport com.minimalist.basic.utils.Add;\nimport com.minimalist.basic.utils.Update;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotNull;\nimport lombok.Data;\nimport java.io.Serial;\nimport java.io.Serializable;\n\n@Data\n@Schema(name = \"岗位实体\")\npublic class PostVO implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @NotNull(message = \"岗位ID不能为空\", groups = {Update.class})\n    @JsonSerialize(using = ToStringSerializer.class)\n    @Schema(name = \"postId\", description = \"岗位ID\", type = \"string\")\n    private Long postId;\n\n    @NotBlank(message = \"岗位编码不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"postCode\", description = \"岗位编码\", type = \"string\")\n    private String postCode;\n\n    @NotBlank(message = \"岗位名称不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"postName\", description = \"岗位名称\", type = \"string\")\n    private String postName;\n\n    @NotNull(message = \"排序值不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"postSort\", description = \"排序\", type = \"integer\")\n    private Integer postSort;\n\n    @Schema(name = \"remark\", description = \"备注\", type = \"string\")\n    private String remark;\n\n    @NotNull(message = \"岗位状态不能为空\", groups = {Update.class})\n    @SchemaEnum(implementation = StatusEnum.class)\n    @Schema(name = \"status\", description = \"岗位状态\", type = \"integer\")\n    private Integer status;\n\n    @JsonSerialize(using = ToStringSerializer.class)\n    @Schema(name = \"tenantId\", description = \"租户ID\", type = \"string\")\n    private Long tenantId;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/role/RoleQueryVO.java",
    "content": "package com.minimalist.basic.entity.vo.role;\n\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.config.mybatis.bo.PageReq;\nimport com.minimalist.basic.config.swagger.SchemaEnum;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport java.io.Serial;\nimport java.io.Serializable;\n\n@Data\n@Schema(name = \"角色查询实体\")\npublic class RoleQueryVO extends PageReq implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @Schema(name = \"roleName\", description = \"角色名称\", type = \"string\")\n    private String roleName;\n\n    @Schema(name = \"roleCode\", description = \"角色编码\", type = \"string\")\n    private String roleCode;\n\n    @SchemaEnum(implementation = StatusEnum.class)\n    @Schema(name = \"status\", description = \"角色状态\", type = \"integer\")\n    private Integer status;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/role/RoleVO.java",
    "content": "package com.minimalist.basic.entity.vo.role;\n\nimport com.fasterxml.jackson.databind.annotation.JsonSerialize;\nimport com.fasterxml.jackson.databind.ser.std.ToStringSerializer;\nimport com.minimalist.basic.config.convert.LongArrJsonSerializer;\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.config.swagger.SchemaEnum;\nimport com.minimalist.basic.utils.Add;\nimport com.minimalist.basic.utils.Update;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\nimport lombok.Data;\nimport java.io.Serial;\nimport java.io.Serializable;\nimport java.util.List;\n\n@Data\n@Schema(name = \"角色实体\")\npublic class RoleVO implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @JsonSerialize(using = ToStringSerializer.class)\n    @NotNull(message = \"角色ID不能为空\", groups = {Update.class})\n    @Schema(name = \"roleId\", description = \"角色ID\", type = \"string\")\n    private Long roleId;\n\n    @NotBlank(message = \"角色名称不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"roleName\", description = \"角色名称\", type = \"string\")\n    private String roleName;\n\n    @NotBlank(message = \"角色编码不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"roleCode\", description = \"角色编码\", type = \"string\")\n    private String roleCode;\n\n    @NotNull(message = \"排序值不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"roleSort\", description = \"排序\", type = \"integer\")\n    private Integer roleSort;\n\n    @NotNull(message = \"角色状态不能为空\", groups = {Update.class})\n    @SchemaEnum(implementation = StatusEnum.class)\n    @Schema(name = \"status\", description = \"角色状态\", type = \"integer\")\n    private Integer status;\n\n    @Schema(name = \"remark\", description = \"备注\", type = \"string\")\n    private String remark;\n\n    @JsonSerialize(using = ToStringSerializer.class)\n    @Schema(name = \"tenantId\", description = \"租户ID\", type = \"string\")\n    private Long tenantId;\n\n    @Schema(name = \"checkedPermIds\", description = \"角色权限编码集合，用于回显\", type = \"array\")\n    private List<String> checkedPermIds;\n\n    @JsonSerialize(using = LongArrJsonSerializer.class)\n    @NotEmpty(message = \"角色权限不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"permissionsIds\", description = \"角色权限编码集合，全勾选+半勾选的节点\", type = \"array\")\n    private List<Long> permissionsIds;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/storage/StorageQueryVO.java",
    "content": "package com.minimalist.basic.entity.vo.storage;\n\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.config.mybatis.bo.PageReq;\nimport com.minimalist.basic.config.swagger.SchemaEnum;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.io.Serial;\nimport java.io.Serializable;\n\n@Data\n@Schema(name = \"存储信息查询实体\")\npublic class StorageQueryVO extends PageReq implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @Schema(name = \"storageName\", description = \"存储名称\", type = \"string\")\n    private String storageName;\n\n    @Schema(name = \"storageType\", description = \"存储类型\", type = \"string\")\n    private String storageType;\n\n    @SchemaEnum(implementation = StatusEnum.class)\n    @Schema(name = \"status\", description = \"存储状态\", type = \"integer\")\n    private Integer status;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/storage/StorageVO.java",
    "content": "package com.minimalist.basic.entity.vo.storage;\n\nimport com.fasterxml.jackson.databind.annotation.JsonSerialize;\nimport com.fasterxml.jackson.databind.ser.std.ToStringSerializer;\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.config.swagger.SchemaEnum;\nimport com.minimalist.basic.utils.Add;\nimport com.minimalist.basic.utils.Update;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotNull;\nimport lombok.Data;\n\nimport java.io.Serial;\nimport java.io.Serializable;\n\n@Data\n@Schema(name = \"存储信息实体\")\npublic class StorageVO implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @JsonSerialize(using = ToStringSerializer.class)\n    @NotNull(message = \"存储ID不能为空\", groups = {Update.class})\n    @Schema(name = \"storageId\", description = \"存储ID\", type = \"string\")\n    private Long storageId;\n\n    @NotBlank(message = \"存储名称不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"storageName\", description = \"存储名称\", type = \"string\")\n    private String storageName;\n\n    @NotBlank(message = \"存储类型不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"storageType\", description = \"存储类型\", type = \"string\")\n    private String storageType;\n\n    @Schema(name = \"description\", description = \"说明\", type = \"integer\")\n    private String description;\n\n    @NotBlank(message = \"存储配置不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"storageConfig\", description = \"存储配置\", type = \"string\")\n    private String storageConfig;\n\n    @NotNull(message = \"存储状态不能为空\", groups = {Update.class})\n    @SchemaEnum(implementation = StatusEnum.class)\n    @Schema(name = \"status\", description = \"存储状态\", type = \"integer\")\n    private Integer status;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/tenant/TenantDatasourceVO.java",
    "content": "package com.minimalist.basic.entity.vo.tenant;\n\nimport com.minimalist.basic.utils.Add;\nimport com.minimalist.basic.utils.Update;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotBlank;\nimport lombok.*;\nimport java.io.Serial;\nimport java.io.Serializable;\n\n@Data\n@Schema(name = \"租户数据源实体\")\npublic class TenantDatasourceVO implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @NotBlank(message = \"数据源名称不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"datasourceName\", description = \"数据源连接\", type = \"string\")\n    private String datasourceName;\n\n    @NotBlank(message = \"数据源连接不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"datasourceUrl\", description = \"数据源连接\", type = \"string\")\n    private String datasourceUrl;\n\n    @NotBlank(message = \"数据源用户名不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"username\", description = \"数据源用户名\", type = \"string\")\n    private String username;\n\n    @NotBlank(message = \"数据源密码不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"password\", description = \"数据源密码\", type = \"string\")\n    private String password;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/tenant/TenantPackageQueryVO.java",
    "content": "package com.minimalist.basic.entity.vo.tenant;\n\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.config.mybatis.bo.PageReq;\nimport com.minimalist.basic.config.swagger.SchemaEnum;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport java.io.Serial;\nimport java.io.Serializable;\n\n@Data\n@Schema(name = \"租户套餐查询实体\")\npublic class TenantPackageQueryVO extends PageReq implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @Schema(name = \"packageName\", description = \"套餐名称\", type = \"string\")\n    private String packageName;\n\n    @SchemaEnum(implementation = StatusEnum.class)\n    @Schema(name = \"status\", description = \"租户套餐状态\", type = \"integer\")\n    private Byte status;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/tenant/TenantPackageVO.java",
    "content": "package com.minimalist.basic.entity.vo.tenant;\n\nimport com.fasterxml.jackson.databind.annotation.JsonSerialize;\nimport com.fasterxml.jackson.databind.ser.std.ToStringSerializer;\nimport com.minimalist.basic.config.convert.LongArrJsonSerializer;\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.config.swagger.SchemaEnum;\nimport com.minimalist.basic.utils.Add;\nimport com.minimalist.basic.utils.Update;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\nimport lombok.Data;\nimport java.io.Serial;\nimport java.io.Serializable;\nimport java.util.List;\n\n@Data\n@Schema(name = \"租户套餐实体\")\npublic class TenantPackageVO implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @JsonSerialize(using = ToStringSerializer.class)\n    @NotNull(message = \"租户套餐ID不能为空\", groups = {Update.class})\n    @Schema(name = \"packageId\", description = \"租户套餐ID\", type = \"string\")\n    private Long packageId;\n\n    @NotNull(message = \"套餐名称能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"packageName\", description = \"套餐名称\", type = \"string\")\n    private String packageName;\n\n    @NotNull(message = \"租户套餐状态不能为空\", groups = {Update.class})\n    @SchemaEnum(implementation = StatusEnum.class)\n    @Schema(name = \"status\", description = \"租户套餐状态\", type = \"integer\")\n    private Byte status;\n\n    @Schema(name = \"remark\", description = \"备注\", type = \"string\")\n    private String remark;\n\n    @Schema(name = \"checkedPermIds\", description = \"租户套餐权限编码集合，用于回显\", type = \"array\")\n    private List<String> checkedPermIds;\n\n    @JsonSerialize(using = LongArrJsonSerializer.class)\n    @NotEmpty(message = \"租户套餐权限不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"permissionsIds\", description = \"租户套餐权限编码集合\", type = \"array\")\n    private List<Long> permissionsIds;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/tenant/TenantQueryVO.java",
    "content": "package com.minimalist.basic.entity.vo.tenant;\n\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.config.mybatis.bo.PageReq;\nimport com.minimalist.basic.config.swagger.SchemaEnum;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport java.io.Serial;\nimport java.io.Serializable;\n\n@Data\n@Schema(name = \"租户查询实体\")\npublic class TenantQueryVO extends PageReq implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @Schema(name = \"tenantName\", description = \"租户名\", type = \"string\")\n    private String tenantName;\n\n    @SchemaEnum(implementation = StatusEnum.class)\n    @Schema(name = \"status\", description = \"租户状态\", type = \"integer\")\n    private Byte status;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/tenant/TenantVO.java",
    "content": "package com.minimalist.basic.entity.vo.tenant;\n\nimport cn.hutool.core.date.DatePattern;\nimport com.fasterxml.jackson.annotation.JsonFormat;\nimport com.fasterxml.jackson.databind.annotation.JsonSerialize;\nimport com.fasterxml.jackson.databind.ser.std.ToStringSerializer;\nimport com.minimalist.basic.entity.enums.TenantEnum;\nimport com.minimalist.basic.entity.vo.storage.StorageVO;\nimport com.minimalist.basic.entity.vo.user.UserVO;\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.config.swagger.SchemaEnum;\nimport com.minimalist.basic.utils.Add;\nimport com.minimalist.basic.utils.Update;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotNull;\nimport lombok.Data;\nimport org.springframework.format.annotation.DateTimeFormat;\nimport java.io.Serial;\nimport java.io.Serializable;\nimport java.time.LocalDateTime;\n\n@Data\n@Schema(name = \"租户实体\")\npublic class TenantVO implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @JsonSerialize(using = ToStringSerializer.class)\n    @NotNull(message = \"租户ID不能为空\", groups = {Update.class})\n    @Schema(name = \"tenantId\", description = \"租户ID\", type = \"string\")\n    private Long tenantId;\n\n    @JsonSerialize(using = ToStringSerializer.class)\n    @NotNull(message = \"租户联系人不能为空\", groups = {Update.class})\n    @Schema(name = \"userId\", description = \"用户ID，与租户绑定\", type = \"string\")\n    private Long userId;\n\n    @Schema(name = \"contactName\", description = \"联系人昵称，回显联系人使用\", type = \"string\")\n    private String contactName;\n\n    @Schema(name = \"email\", description = \"联系人邮箱，回显联系人使用\", type = \"string\")\n    private String email;\n\n    @Schema(name = \"phone\", description = \"联系人号码，回显联系人使用\", type = \"string\")\n    private String phone;\n\n    @JsonSerialize(using = ToStringSerializer.class)\n    @NotNull(message = \"租户套餐ID不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"packageId\", description = \"租户套餐ID\", type = \"string\")\n    private Long packageId;\n\n    @NotBlank(message = \"租户名不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"tenantName\", description = \"租户名\", type = \"string\")\n    private String tenantName;\n\n    @JsonFormat(pattern = DatePattern.NORM_DATETIME_PATTERN)\n    @DateTimeFormat(pattern = DatePattern.NORM_DATETIME_PATTERN)\n    @NotNull(message = \"租户过期时间不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"expireTime\", description = \"租户过期时间\", type = \"string\")\n    private LocalDateTime expireTime;\n\n    @NotNull(message = \"可创建账号数量不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"accountCount\", description = \"可创建账号数量，表示这个租户下可以创建多少账号\", type = \"integer\")\n    private Integer accountCount;\n\n    @NotBlank(message = \"数据隔离方式不能为空\", groups = {Add.class, Update.class})\n    @SchemaEnum(implementation = TenantEnum.DataIsolation.class)\n    @Schema(name = \"dataIsolation\", description = \"数据隔离方式\", type = \"string\")\n    private String dataIsolation;\n\n    @Schema(name = \"datasource\", description = \"所使用的数据源，默认使用master主库\", type = \"string\")\n    private String datasource;\n\n    @JsonSerialize(using = ToStringSerializer.class)\n    @NotNull(message = \"存储ID不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"storageId\", description = \"存储ID 表示该租户使用哪个文件存储\", type = \"string\")\n    private Long storageId;\n\n    @NotNull(message = \"租户状态不能为空\", groups = {Update.class})\n    @SchemaEnum(implementation = StatusEnum.class)\n    @Schema(name = \"status\", description = \"租户状态\", type = \"integer\")\n    private Byte status;\n\n    @Schema(name = \"remark\", description = \"备注\", type = \"string\")\n    private String remark;\n\n    @NotNull(message = \"租户的用户信息不能为空\", groups = {Add.class})\n    @Schema(name = \"user\", description = \"租户的用户信息，新增时填充\", type = \"object\")\n    private UserVO user;\n\n    @NotNull(message = \"租户数据源不能为空\", groups = {Add.class})\n    @Schema(name = \"tenantDatasource\", description = \"租户数据源\", type = \"object\")\n    private TenantDatasourceVO tenantDatasource;\n\n    @Schema(name = \"tenantDatasource\", description = \"租户文件存储\", type = \"object\")\n    private StorageVO storage;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/user/ImageCaptchaVO.java",
    "content": "package com.minimalist.basic.entity.vo.user;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.io.Serial;\nimport java.io.Serializable;\n\n@Data\n@Schema(name = \"图形验证码响应实体\")\npublic class ImageCaptchaVO implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @Schema(name = \"enable\", description = \"是否启用验证码\", type = \"boolean\")\n    private boolean enable;\n\n    @Schema(name = \"captchaId\", description = \"验证码ID\", type = \"string\")\n    private String captchaId;\n\n    @Schema(name = \"captchaImg\", description = \"验证码图片Base64编码\", type = \"string\")\n    private String captchaImg;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/user/RePasswordVO.java",
    "content": "package com.minimalist.basic.entity.vo.user;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotBlank;\nimport lombok.Data;\nimport java.io.Serial;\nimport java.io.Serializable;\n\n@Data\n@Schema(name = \"用户重置密码实体\")\npublic class RePasswordVO implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @NotBlank(message = \"旧密码不能为空\")\n    @Schema(name = \"oldPassword\", description = \"旧密码\", type = \"string\")\n    private String oldPassword;\n\n    @NotBlank(message = \"新密码不能为空\")\n    @Schema(name = \"newPassword\", description = \"新密码\", type = \"string\")\n    private String newPassword;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/user/UserInfoVO.java",
    "content": "package com.minimalist.basic.entity.vo.user;\n\nimport com.fasterxml.jackson.annotation.JsonIgnore;\nimport com.fasterxml.jackson.databind.annotation.JsonSerialize;\nimport com.fasterxml.jackson.databind.ser.std.ToStringSerializer;\nimport com.minimalist.basic.entity.vo.dept.DeptVO;\nimport com.minimalist.basic.entity.vo.perm.PermVO;\nimport com.minimalist.basic.entity.vo.post.PostVO;\nimport com.minimalist.basic.entity.enums.UserEnum;\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.config.swagger.SchemaEnum;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport java.io.Serial;\nimport java.io.Serializable;\nimport java.util.List;\nimport java.util.Set;\n\n@Data\n@Schema(name = \"用户实体\")\npublic class UserInfoVO implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @Schema(name = \"userId\", description = \"用户ID\", type = \"string\")\n    @JsonSerialize(using = ToStringSerializer.class)\n    private Long userId;\n\n    @Schema(name = \"username\", description = \"用户账号\", type = \"string\")\n    private String username;\n\n    /** 用户密码 */\n    @JsonIgnore\n    private String password;\n\n    @Schema(name = \"nickname\", description = \"用户昵称\", type = \"string\")\n    private String nickname;\n\n    @Schema(name = \"userRealName\", description = \"用户真实姓名\", type = \"string\")\n    private String userRealName;\n\n    @Schema(name = \"email\", description = \"用户邮箱\", type = \"string\")\n    private String email;\n\n    @Schema(name = \"phone\", description = \"手机号码\", type = \"string\")\n    private String phone;\n\n    @SchemaEnum(implementation = UserEnum.UserSex.class)\n    @Schema(name = \"userSex\", description = \"用户性别\", type = \"integer\")\n    private Integer userSex;\n\n    @Schema(name = \"userAvatar\", description = \"用户头像base64编码\", type = \"string\")\n    private String userAvatar;\n\n    @Schema(name = \"remark\", description = \"备注\", type = \"string\")\n    private String remark;\n\n    @SchemaEnum(implementation = StatusEnum.class)\n    @Schema(name = \"status\", description = \"用户状态\", type = \"integer\")\n    private Integer status;\n\n    @Schema(name = \"tenantId\", description = \"租户编号\", type = \"string\")\n    @JsonSerialize(using = ToStringSerializer.class)\n    private Long tenantId;\n\n    @Schema(name = \"perms\", description = \"用户权限标识符\", type = \"array\")\n    private Set<String> perms;\n\n    @Schema(name = \"roles\", description = \"用户角色标识符\", type = \"array\")\n    private Set<String> roles;\n\n    @Schema(name = \"deptList\", description = \"用户所属部门信息\", type = \"array\")\n    private List<DeptVO> deptList;\n\n    @Schema(name = \"postList\", description = \"用户岗位信息\", type = \"array\")\n    private List<PostVO> postList;\n\n    @Schema(name = \"menus\", description = \"用户菜单\")\n    private List<PermVO> menus;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/user/UserLoginReqVO.java",
    "content": "package com.minimalist.basic.entity.vo.user;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotBlank;\nimport lombok.Data;\n\nimport java.io.Serial;\nimport java.io.Serializable;\n\n@Data\n@Schema(name = \"用户登录请求实体\")\npublic class UserLoginReqVO implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @NotBlank(message = \"用户名不能为空\")\n    @Schema(name = \"username\", description = \"用户名\", requiredMode = Schema.RequiredMode.REQUIRED, type = \"string\")\n    private String username;\n\n    @NotBlank(message = \"密码不能为空\")\n    @Schema(name = \"password\", description = \"密码\", requiredMode = Schema.RequiredMode.REQUIRED, type = \"string\")\n    private String password;\n\n    @Schema(name = \"captchaId\", description = \"验证码ID，与验证码一一对应\", type = \"string\")\n    private String captchaId;\n\n    @Schema(name = \"captcha\", description = \"验证码\", type = \"string\")\n    private String captcha;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/user/UserQueryVO.java",
    "content": "package com.minimalist.basic.entity.vo.user;\n\nimport com.minimalist.basic.config.mybatis.bo.PageReq;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport java.io.Serial;\nimport java.io.Serializable;\n\n@Data\n@Schema(name = \"用户查询实体\")\npublic class UserQueryVO extends PageReq implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @Schema(name = \"userRealName\", description = \"用户真实姓名\", type = \"string\")\n    private String userRealName;\n\n    @Schema(name = \"phone\", description = \"手机号码\", type = \"string\")\n    private String phone;\n\n    @Schema(name = \"dept\", description = \"选择的部门\", type = \"string\")\n    private Long deptId;\n\n    @Schema(name = \"status\", description = \"用户状态\", type = \"integer\")\n    private Integer status;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/user/UserSettingVO.java",
    "content": "package com.minimalist.basic.entity.vo.user;\n\nimport com.minimalist.basic.entity.enums.UserEnum;\nimport com.minimalist.basic.config.swagger.SchemaEnum;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotNull;\nimport lombok.Data;\nimport java.io.Serial;\nimport java.io.Serializable;\n\n@Data\n@Schema(name = \"用户设置实体\")\npublic class UserSettingVO implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @NotBlank(message = \"用户昵称不能为空\")\n    @Schema(name = \"nickname\", description = \"用户昵称\", type = \"string\")\n    private String nickname;\n\n    @NotBlank(message = \"用户真实姓名不能为空\")\n    @Schema(name = \"userRealName\", description = \"用户真实姓名\", type = \"string\")\n    private String userRealName;\n\n    @NotBlank(message = \"手机号不能为空\")\n    @Schema(name = \"phone\", description = \"手机号码\", type = \"string\")\n    private String phone;\n\n    @Schema(name = \"email\", description = \"用户邮箱，可为空\", type = \"string\")\n    private String email;\n\n    @NotNull(message = \"用户性别不能为空\")\n    @SchemaEnum(implementation = UserEnum.UserSex.class)\n    @Schema(name = \"userSex\", description = \"用户性别\", type = \"integer\")\n    private Integer userSex;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/entity/vo/user/UserVO.java",
    "content": "package com.minimalist.basic.entity.vo.user;\n\nimport com.fasterxml.jackson.databind.annotation.JsonSerialize;\nimport com.fasterxml.jackson.databind.ser.std.NullSerializer;\nimport com.fasterxml.jackson.databind.ser.std.ToStringSerializer;\nimport com.minimalist.basic.config.convert.LongArrJsonSerializer;\nimport com.minimalist.basic.entity.enums.UserEnum;\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.config.swagger.SchemaEnum;\nimport com.minimalist.basic.utils.Add;\nimport com.minimalist.basic.utils.Update;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\nimport lombok.Data;\nimport java.io.Serial;\nimport java.io.Serializable;\nimport java.util.List;\nimport java.util.Set;\n\n@Data\n@Schema(name = \"用户管理实体\")\npublic class UserVO implements Serializable {\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @JsonSerialize(using = ToStringSerializer.class)\n    @NotNull(message = \"用户ID不能为空\", groups = {Update.class})\n    @Schema(name = \"userId\", description = \"用户ID\", type = \"string\")\n    private Long userId;\n\n    @NotBlank(message = \"用户账号不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"username\", description = \"用户账号\", type = \"string\")\n    private String username;\n\n    @JsonSerialize(using = NullSerializer.class)\n    @Schema(name = \"password\", description = \"密码\", type = \"string\")\n    private String password;\n\n    @NotBlank(message = \"用户昵称不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"nickname\", description = \"用户昵称\", type = \"string\")\n    private String nickname;\n\n    @NotBlank(message = \"用户真实姓名不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"userRealName\", description = \"用户真实姓名\", type = \"string\")\n    private String userRealName;\n\n    @NotBlank(message = \"手机号不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"phone\", description = \"手机号码\", type = \"string\")\n    private String phone;\n\n    @Schema(name = \"email\", description = \"用户邮箱，可为空\", type = \"string\")\n    private String email;\n\n    @NotNull(message = \"用户性别不能为空\", groups = {Add.class, Update.class})\n    @SchemaEnum(implementation = UserEnum.UserSex.class)\n    @Schema(name = \"userSex\", description = \"用户性别\", type = \"integer\")\n    private Integer userSex;\n\n    @Schema(name = \"userAvatar\", description = \"用户头像base64编码\", type = \"string\")\n    private String userAvatar;\n\n    @Schema(name = \"remark\", description = \"备注\", type = \"string\")\n    private String remark;\n\n    @NotNull(message = \"用户状态不能为空\", groups = {Update.class})\n    @SchemaEnum(implementation = StatusEnum.class)\n    @Schema(name = \"status\", description = \"用户状态\", type = \"integer\")\n    private Integer status;\n\n    @JsonSerialize(using = ToStringSerializer.class)\n    @Schema(name = \"tenantId\", description = \"租户编号\", type = \"string\")\n    private Long tenantId;\n\n    @JsonSerialize(using = LongArrJsonSerializer.class)\n    @NotEmpty(message = \"角色不能为空\", groups = {Add.class, Update.class})\n    @Schema(name = \"roles\", description = \"角色ID集合\", type = \"array\")\n    private Set<Long> roleIds;\n\n    @JsonSerialize(using = LongArrJsonSerializer.class)\n    @Schema(name = \"postIds\", description = \"岗位ID集合\", type = \"array\")\n    private Set<Long> postIds;\n\n    @JsonSerialize(using = LongArrJsonSerializer.class)\n    @Schema(name = \"deptIds\", description = \"部门ID集合\", type = \"array\")\n    private Set<Long> deptIds;\n\n    @Schema(name = \"checkedPermIds\", description = \"用户部门编码集合，全勾选的节点\", type = \"array\")\n    private List<String> checkedDeptIds;\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/manager/TenantManager.java",
    "content": "package com.minimalist.basic.manager;\n\nimport cn.hutool.core.collection.CollectionUtil;\nimport cn.hutool.core.date.LocalDateTimeUtil;\nimport cn.hutool.core.lang.Assert;\nimport com.baomidou.dynamic.datasource.DynamicRoutingDataSource;\nimport com.baomidou.dynamic.datasource.creator.DataSourceProperty;\nimport com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;\nimport com.minimalist.basic.entity.enums.*;\nimport com.minimalist.basic.entity.po.*;\nimport com.minimalist.basic.entity.vo.tenant.TenantDatasourceVO;\nimport com.minimalist.basic.mapper.MRolePermMapper;\nimport com.minimalist.basic.mapper.MTenantMapper;\nimport com.minimalist.basic.mapper.MTenantPackagePermMapper;\nimport com.minimalist.basic.mapper.MUserMapper;\nimport com.minimalist.basic.config.exception.BusinessException;\nimport com.mybatisflex.core.logicdelete.LogicDeleteManager;\nimport com.mybatisflex.core.query.QueryWrapper;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Component;\nimport javax.sql.DataSource;\nimport java.time.Duration;\nimport java.time.LocalDateTime;\nimport java.util.List;\n\n/**\n * 租户相关的辅助处理\n */\n@Component\npublic class TenantManager {\n\n    @Autowired\n    private MTenantMapper tenantMapper;\n\n    @Autowired\n    private MUserMapper userMapper;\n\n    @Autowired\n    private MRolePermMapper rolePermMapper;\n\n    @Autowired\n    private MTenantPackagePermMapper tenantPackagePermMapper;\n\n    @Autowired\n    private DataSource dataSource;\n\n    @Autowired\n    private DefaultDataSourceCreator dataSourceCreator;\n\n    /**\n     * 检查租户套餐\n     * @param tenantId 租户ID\n     */\n    public void checkTenantPackage(long tenantId) {\n        //检查租户下用户数是否满足套餐\n        MTenant mTenant = tenantMapper.selectTenantByTenantId(tenantId);\n        Assert.notNull(mTenant, () -> new BusinessException(TenantEnum.ErrorMsg.NONENTITY_TENANT.getDesc()));\n        long userCount = userMapper.selectUserCountByTenantId(tenantId);\n        // +1是去除租户本身的用户\n        Assert.isFalse((userCount + 1) >= mTenant.getAccountCount(),\n                () -> new BusinessException(TenantEnum.ErrorMsg.TENANT_USER_COUNT_LIMIT.getDesc()));\n        //检查租户状态\n        Assert.isTrue(StatusEnum.STATUS_1.getCode().equals(mTenant.getStatus()),\n                () -> new BusinessException(TenantEnum.ErrorMsg.DISABLED_TENANT.getDesc()));\n        //检查租户是否过期\n        checkTenantExpireTime(mTenant.getExpireTime());\n    }\n\n    /**\n     * 检查租户是否过期\n     * @param expireTime 租户到期时间\n     */\n    public void checkTenantExpireTime(LocalDateTime expireTime) {\n        //获取当天最晚时间，23:59:59\n        LocalDateTime localDateTime = LocalDateTimeUtil.endOfDay(LocalDateTime.now());\n        //检查租户是否过期\n        Duration duration = LocalDateTimeUtil.between(localDateTime, expireTime);\n        //如果租户到期时间 < 当天，返回负，说明已到期\n        long exHours = duration.toHours();\n        Assert.isFalse(exHours <= 0, () -> new BusinessException(TenantEnum.ErrorMsg.EX_TENANT.getDesc()));\n    }\n\n    public void updateTenantPermission(List<MRole> roleList, Long packageId) {\n        //修改后的套餐权限\n        List<MTenantPackagePerm> newTpp = tenantPackagePermMapper.selectTenantPackagePermByTenantPackageId(packageId);\n        List<Long> permIds = newTpp.stream().map(MTenantPackagePerm::getPermId).toList();\n        for (MRole role : roleList) {\n            //如果是租户管理员，将套餐所有权限重新分配给租户管理员\n            if (RoleEnum.Role.ADMIN.getCode().equals(role.getRoleCode())) {\n                //删除旧关联数据\n                LogicDeleteManager.execWithoutLogicDelete(()->\n                        rolePermMapper.deleteByQuery(QueryWrapper.create().eq(MRolePerm::getRoleId, role.getRoleId()))\n                );\n                //插入新关联数据\n                List<MRolePerm> rolePerms = newTpp.stream().map(tpp -> {\n                    MRolePerm rolePerm = new MRolePerm();\n                    rolePerm.setRoleId(role.getRoleId());\n                    rolePerm.setPermId(tpp.getPermId());\n                    return rolePerm;\n                }).toList();\n                rolePermMapper.insertBatch(rolePerms);\n            } else {\n                //如果是其他角色，删除超出的权限\n                if (CollectionUtil.isNotEmpty(permIds)) {\n                    rolePermMapper.deleteByQuery(QueryWrapper.create()\n                            .eq(MRolePerm::getRoleId, role.getRoleId())\n                            .notIn(MRolePerm::getPermId, permIds)\n                    );\n                }\n            }\n        }\n    }\n\n    /**\n     * 动态添加数据源\n     * @param tenantId 租户ID\n     * @param tenantDatasourceVO 数据源信息\n     */\n    public void dynamicAddDatasource(String tenantId, TenantDatasourceVO tenantDatasourceVO) {\n        DataSourceProperty dataSourceProperty = new DataSourceProperty();\n        dataSourceProperty.setUrl(tenantDatasourceVO.getDatasourceUrl());\n        dataSourceProperty.setUsername(tenantDatasourceVO.getUsername());\n        dataSourceProperty.setPassword(tenantDatasourceVO.getPassword());\n        DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;\n        DataSource dataSource = dataSourceCreator.createDataSource(dataSourceProperty);\n        ds.addDataSource(tenantId, dataSource);\n    }\n\n    /**\n     * 动态删除数据源\n     * @param tenantId 租户ID\n     */\n    public void dynamicDeleteDatasource(String tenantId) {\n        //动态删除数据源\n        DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;\n        ds.removeDataSource(tenantId);\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/manager/UserManager.java",
    "content": "package com.minimalist.basic.manager;\n\nimport cn.hutool.core.collection.CollectionUtil;\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.util.ObjectUtil;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.crypto.SecureUtil;\nimport com.minimalist.basic.entity.po.MUser;\nimport com.minimalist.basic.entity.po.MUserDept;\nimport com.minimalist.basic.entity.po.MUserPost;\nimport com.minimalist.basic.entity.po.MUserRole;\nimport com.minimalist.basic.mapper.MUserDeptMapper;\nimport com.minimalist.basic.mapper.MUserMapper;\nimport com.minimalist.basic.config.exception.BusinessException;\nimport com.minimalist.basic.entity.enums.UserEnum;\nimport com.minimalist.basic.mapper.MUserPostMapper;\nimport com.minimalist.basic.mapper.MUserRoleMapper;\nimport com.mybatisflex.core.logicdelete.LogicDeleteManager;\nimport com.mybatisflex.core.query.QueryWrapper;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Component;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n * 用户相关的辅助处理\n */\n@Component\npublic class UserManager {\n\n    @Autowired\n    private MUserMapper userMapper;\n\n    @Autowired\n    private MUserRoleMapper userRoleMapper;\n\n    @Autowired\n    private MUserPostMapper userPostMapper;\n\n    @Autowired\n    private MUserDeptMapper userDeptMapper;\n\n    /**\n     * 用户密码加密\n     * @param password 密码\n     * @param salt 盐值\n     * @return 加密后的密码\n     */\n    public String passwordEncrypt(String password, String salt) {\n        String p1 = SecureUtil.md5(password + salt);\n        return SecureUtil.md5(p1);\n    }\n\n    /**\n     * 用户名唯一性校验\n     * 整个系统中不允许重复的用户名\n     * @param username 用户名\n     * @param userId 用户ID，可以被忽略的用户ID\n     */\n    public void checkUsernameUniqueness(String username, Long userId) {\n        QueryWrapper queryWrapper = QueryWrapper.create().eq(MUser::getUsername, username);\n        if (ObjectUtil.isNotNull(userId)) {\n            queryWrapper.ne(MUser::getUserId, userId);\n        }\n        MUser user = userMapper.selectOneByQuery(queryWrapper);\n        Assert.isNull(user, () -> new BusinessException(UserEnum.ErrorMsg.EXISTS_ACCOUNT.getDesc()));\n    }\n\n    /**\n     * 用户邮箱唯一性校验\n     * 邮箱不允许重复\n     * @param email 用户邮箱\n     * @param userId 用户ID，可以被忽略的用户ID\n     */\n    public void checkUserEmailUniqueness(String email, Long userId) {\n        if (StrUtil.isBlank(email)) { return; }\n        QueryWrapper queryWrapper = QueryWrapper.create().eq(MUser::getEmail, email);\n        if (ObjectUtil.isNotNull(userId)) {\n            queryWrapper.ne(MUser::getUserId, userId);\n        }\n        MUser user = userMapper.selectOneByQuery(queryWrapper);\n        Assert.isNull(user, () -> new BusinessException(UserEnum.ErrorMsg.EMAIL_ACCOUNT.getDesc()));\n    }\n\n    /**\n     * 新增用户角色、岗位、部门关联信息\n     * @param roleIds 角色ID列表\n     * @param postIds 岗位ID列表\n     * @param deptIds 部门ID列表\n     */\n    public void insertUserRelation(Set<Long> roleIds, Set<Long> postIds, Set<Long> deptIds, Long userId) {\n        //用户与角色关联关系\n        if (CollectionUtil.isNotEmpty(roleIds)) {\n            List<MUserRole> userRoleList = roleIds.stream().map(r -> {\n                MUserRole userRole = new MUserRole();\n                userRole.setUserId(userId);\n                userRole.setRoleId(r);\n                return userRole;\n            }).toList();\n            userRoleMapper.insertBatch(userRoleList);\n        }\n        //用户与岗位关联关系\n        if (CollectionUtil.isNotEmpty(postIds)) {\n            List<MUserPost> userPostList = postIds.stream().map(p -> {\n                MUserPost userPost = new MUserPost();\n                userPost.setUserId(userId);\n                userPost.setPostId(p);\n                return userPost;\n            }).toList();\n            userPostMapper.insertBatch(userPostList);\n        }\n        //用户与部门关联关系\n        if (CollectionUtil.isNotEmpty(deptIds)) {\n            List<MUserDept> userDeptList = deptIds.stream().map(d -> {\n                MUserDept userDept = new MUserDept();\n                userDept.setUserId(userId);\n                userDept.setDeptId(d);\n                return userDept;\n            }).toList();\n            userDeptMapper.insertBatch(userDeptList);\n        }\n    }\n\n    /**\n     * 删除用户角色、岗位关联信息\n     * @param userId 用户ID\n     */\n    public void deleteUserRelation(Long userId) {\n        //删除用户与角色关联关系 -> 真实删除\n        LogicDeleteManager.execWithoutLogicDelete(()->\n                userRoleMapper.deleteByQuery(QueryWrapper.create().eq(MUserRole::getUserId, userId))\n        );\n        //删除用户与岗位关联关系 -> 真实删除\n        LogicDeleteManager.execWithoutLogicDelete(()->\n                userPostMapper.deleteByQuery(QueryWrapper.create().eq(MUserPost::getUserId, userId))\n        );\n        //删除用户与部门关联关系 -> 真实删除\n        LogicDeleteManager.execWithoutLogicDelete(()->\n                userDeptMapper.deleteByQuery(QueryWrapper.create().eq(MUserDept::getUserId, userId))\n        );\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/mapper/MConfigMapper.java",
    "content": "package com.minimalist.basic.mapper;\n\nimport com.minimalist.basic.entity.vo.config.ConfigQueryVO;\nimport com.mybatisflex.core.BaseMapper;\nimport com.minimalist.basic.entity.po.MConfig;\nimport com.mybatisflex.core.paginate.Page;\nimport com.mybatisflex.core.query.QueryWrapper;\n\n/**\n * 参数配置表 映射层。\n *\n * @author 小太阳\n * @since 2024-10-18\n */\npublic interface MConfigMapper extends BaseMapper<MConfig> {\n\n    /**\n     * 根据参数ID查询参数\n     * @param configId 参数ID\n     * @return 参数信息\n     */\n    default MConfig selectConfigByConfigId(Long configId) {\n        return selectOneByQuery(QueryWrapper.create().eq(MConfig::getConfigId, configId));\n    }\n\n    /**\n     * 根据参数键名和状态查询参数\n     * @param configKey 参数键名\n     * @param status 参数状态\n     * @return 参数信息\n     */\n    default MConfig selectConfigByConfigKey(String configKey, Integer status) {\n        return selectOneByQuery(QueryWrapper.create()\n                .eq(MConfig::getConfigKey, configKey)\n                .eq(MConfig::getStatus, status));\n    }\n\n    /**\n     * 根据参数ID删除参数\n     * @param configId 参数ID\n     * @return 受影响行数\n     */\n    default int deleteConfigByConfigId(Long configId) {\n        return deleteByQuery(QueryWrapper.create().eq(MConfig::getConfigId, configId));\n    }\n\n    /**\n     * 根据参数ID修改参数\n     * @param updateConfig 参数信息\n     */\n    default void updateConfigByConfigId(MConfig updateConfig) {\n        updateByQuery(updateConfig, QueryWrapper.create().eq(MConfig::getConfigId, updateConfig.getConfigId()));\n    }\n\n    /**\n     * 查询参数配置列表(分页)\n     * @param queryVO 查询条件\n     * @return 参数配置列表\n     */\n    default Page<MConfig> selectPageConfigList(ConfigQueryVO queryVO) {\n        return paginate(queryVO.getPageNum(), queryVO.getPageSize(),\n                QueryWrapper.create()\n                        .eq(MConfig::getStatus, queryVO.getStatus())\n                        .like(MConfig::getConfigName, queryVO.getConfigName())\n                        .like(MConfig::getConfigKey, queryVO.getConfigKey()));\n\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/mapper/MDeptMapper.java",
    "content": "package com.minimalist.basic.mapper;\n\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.entity.vo.dept.DeptQueryVO;\nimport com.mybatisflex.core.BaseMapper;\nimport com.minimalist.basic.entity.po.MDept;\nimport com.mybatisflex.core.query.QueryWrapper;\nimport java.util.List;\n\n/**\n * 部门表 映射层。\n *\n * @author 小太阳\n * @since 2024-10-18\n */\npublic interface MDeptMapper extends BaseMapper<MDept> {\n\n    /**\n     * 根据部门ID查询直属下级数量\n     * @param deptId 部门ID\n     * @return 下级数量\n     */\n    default long selectChildrenCountByDeptId(Long deptId) {\n        return selectCountByQuery(QueryWrapper.create().where(\"FIND_IN_SET(\"  + deptId +\", ancestors)\"));\n    }\n\n    /**\n     * 根据部门ID删除部门\n     * @param deptId 部门ID\n     */\n    default void deleteDeptByDeptId(Long deptId) {\n        deleteByQuery(QueryWrapper.create()\n                .eq(MDept::getDeptId, deptId));\n    }\n\n    /**\n     * 根据部门ID获取部门(单条)\n     * @param deptId 部门ID\n     * @return 部门实体\n     */\n    default MDept selectDeptByDeptId(Long deptId) {\n        return selectOneByQuery(QueryWrapper.create().eq(MDept::getDeptId, deptId));\n    }\n\n    /**\n     * 根据部门ID获取部门(批量)\n     * @param deptIds 部门ID列表\n     * @return 部门实体列表\n     */\n    default List<MDept> selectDeptByDeptIds(List<Long> deptIds) {\n        return selectListByQuery(QueryWrapper.create().in(MDept::getDeptId, deptIds));\n    }\n\n    /**\n     * 根据部门ID修改部门\n     * @param mDept 部门实体\n     */\n    default void updateDeptByDeptId(MDept mDept) {\n        updateByQuery(mDept,\n                QueryWrapper.create().eq(MDept::getDeptId, mDept.getDeptId()));\n    }\n\n    /**\n     * 根据条件查询部门列表\n     * @param queryVO 查询参数\n     * @return 部门列表\n     */\n    default List<MDept> selectDeptList(DeptQueryVO queryVO) {\n        return selectListByQuery(QueryWrapper.create()\n                .eq(MDept::getStatus, queryVO.getStatus())\n                .like(MDept::getDeptName, queryVO.getDeptName()));\n    }\n\n    /**\n     * 根据租户ID查询部门列表 -> 字典查询\n     * @return 部门列表\n     */\n    default List<MDept> selectDeptDict() {\n        return selectListByQuery(QueryWrapper.create()\n                .eq(MDept::getStatus, StatusEnum.STATUS_1.getCode()));\n    }\n\n    /**\n     * 根据部门ID查询所有子集部门\n     * @param deptId 部门ID\n     * @return 子集部门列表\n     */\n    default List<MDept> selectChildrenDeptByDeptId(Long deptId) {\n        return selectListByQuery(QueryWrapper.create().where(\"FIND_IN_SET(\"  + deptId +\", ancestors)\"));\n    }\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/mapper/MDictMapper.java",
    "content": "package com.minimalist.basic.mapper;\n\nimport com.minimalist.basic.entity.vo.dict.DictQueryVO;\nimport com.minimalist.basic.entity.vo.dict.DictVO;\nimport com.mybatisflex.core.BaseMapper;\nimport com.minimalist.basic.entity.po.MDict;\nimport com.mybatisflex.core.paginate.Page;\nimport com.mybatisflex.core.query.QueryWrapper;\nimport java.util.List;\n\n/**\n * 字典表 映射层。\n *\n * @author 小太阳\n * @since 2024-10-18\n */\npublic interface MDictMapper extends BaseMapper<MDict> {\n    /**\n     * 根据字典ID查询字典\n     * @param dictId 字典ID\n     * @return 字典数据\n     */\n    default MDict selectDictByDictId(Long dictId) {\n        return selectOneByQuery(QueryWrapper.create().eq(MDict::getDictId, dictId));\n    }\n\n    /**\n     * 根据字典ID删除字典\n     * @param dictId 字典ID\n     */\n    default void deleteDictByDictId(Long dictId) {\n        deleteByQuery(QueryWrapper.create().eq(MDict::getDictId, dictId));\n    }\n\n    /**\n     * 根据字典类型删除字典\n     * @param dictType 字典类型\n     */\n    default void deleteDictByDictType(String dictType) {\n        deleteByQuery(QueryWrapper.create().eq(MDict::getDictType, dictType));\n    }\n\n    /**\n     * 分页查询字典\n     * @param queryVO 查询实体\n     * @return 分页数据\n     */\n    default Page<DictVO> selectPageDictList(DictQueryVO queryVO) {\n        return paginateAs(queryVO.getPageNum(), queryVO.getPageSize(),\n                QueryWrapper.create().eq(MDict::getStatus, queryVO.getStatus())\n                        .like(MDict::getDictName, queryVO.getDictName())\n                        .like(MDict::getDictType, queryVO.getDictType())\n                        .orderBy(MDict::getDictOrder, true)\n                        .groupBy(MDict::getDictType), DictVO.class);\n    }\n\n    /**\n     * 根据类型查询字典数据\n     * @param dictTypeList 字典类型列表\n     * @return 字典列表\n     */\n    default List<MDict> selectDictListByDictType(List<String> dictTypeList) {\n        return selectListByQuery(QueryWrapper.create().in(MDict::getDictType, dictTypeList).orderBy(MDict::getDictOrder, true));\n    }\n\n    /**\n     * 根据字典ID修改字典\n     * @param dict 字典实体\n     */\n    default void updateDictByDictId(MDict dict) {\n        updateByQuery(dict, QueryWrapper.create().eq(MDict::getDictId, dict.getDictId()));\n    }\n\n    /**\n     * 根据字典类型和字典key查询字典数据\n     * @param dictType 字典类型\n     * @param dictKey 字典key\n     * @return 字典数据\n     */\n    default MDict selectDictByDictTypeAndKey(String dictType, String dictKey) {\n        return selectOneByQuery(QueryWrapper.create()\n                .eq(MDict::getDictType, dictType)\n                .eq(MDict::getDictKey, dictKey));\n    }\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/mapper/MFileMapper.java",
    "content": "package com.minimalist.basic.mapper;\n\nimport cn.hutool.core.util.ObjectUtil;\nimport com.minimalist.basic.entity.vo.file.FileQueryVO;\nimport com.mybatisflex.core.BaseMapper;\nimport com.minimalist.basic.entity.po.MFile;\nimport com.mybatisflex.core.paginate.Page;\nimport com.mybatisflex.core.query.QueryWrapper;\nimport java.time.LocalDateTime;\nimport java.util.List;\n\n/**\n * 文件表 映射层。\n *\n * @author 小太阳\n * @since 2024-10-18\n */\npublic interface MFileMapper extends BaseMapper<MFile> {\n    /**\n     * 根据fileUrl修改文件状态\n     * @param userId 用户ID\n     * @param status 状态\n     * @param urlList 文件url列表\n     */\n    default void updateStatusByFileUrl(Long userId, List<String> urlList, Integer status) {\n        MFile file = new MFile();\n        file.setStatus(status);\n        file.setUpdateId(userId);\n        file.setUpdateTime(LocalDateTime.now());\n        updateByQuery(file, QueryWrapper.create().in(MFile::getFileUrl, urlList));\n    }\n\n    /**\n     * 查询文件列表(分页)\n     * @param queryVO 查询条件\n     * @return 文件分页数据\n     */\n    default Page<MFile> selectPageFileList(FileQueryVO queryVO) {\n        return paginate(queryVO.getPageNum(), queryVO.getPageSize(),\n                QueryWrapper.create()\n                        .eq(MFile::getStatus, queryVO.getStatus())\n                        .eq(MFile::getFileSource, queryVO.getFileSource())\n                        .like(MFile::getFileName, queryVO.getFileName())\n                        .like(MFile::getFileType, queryVO.getFileType())\n                        .orderBy(MFile::getCreateTime, false));\n    }\n\n    /**\n     * 根据文件ID查询文件\n     * @param fileId 文件ID\n     * @return 文件实体\n     */\n    default MFile selectFileByFileId(Long fileId) {\n        return selectOneByQuery(QueryWrapper.create().eq(MFile::getFileId, fileId));\n    }\n\n    /**\n     * 根据文件UID查询文件\n     * @param fileIdList 文件ID列表\n     * @return 文件列表\n     */\n    default List<MFile> selectFileByFileIds(List<Long> fileIdList) {\n        return selectListByQuery(QueryWrapper.create().in(MFile::getFileId, fileIdList));\n    }\n\n    /**\n     * 根据文件ID修改文件状态\n     * @param userId 修改人ID\n     * @param fileIdList 文件ID列表\n     * @param fileStatus 文件状态\n     * @param fileSource 文件来源\n     */\n    default void updateFileStatusByFileIds(Long userId, List<Long> fileIdList, Integer fileStatus, Integer fileSource) {\n        MFile file = new MFile();\n        file.setStatus(fileStatus);\n        if (ObjectUtil.isNotNull(userId)) {\n            file.setUpdateId(userId);\n        }\n        if (ObjectUtil.isNotNull(fileSource)) {\n            file.setFileSource(fileSource);\n        }\n        file.setUpdateTime(LocalDateTime.now());\n        updateByQuery(file, QueryWrapper.create().in(MFile::getFileId, fileIdList));\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/mapper/MNoticeMapper.java",
    "content": "package com.minimalist.basic.mapper;\n\nimport com.minimalist.basic.config.mybatis.bo.PageReq;\nimport com.minimalist.basic.entity.enums.NoticeEnum;\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.entity.vo.notice.NoticeQueryVO;\nimport com.mybatisflex.core.BaseMapper;\nimport com.minimalist.basic.entity.po.MNotice;\nimport com.mybatisflex.core.paginate.Page;\nimport com.mybatisflex.core.query.QueryWrapper;\n\nimport java.time.LocalDateTime;\n\n/**\n * 通知公告表 映射层。\n *\n * @author 小太阳\n * @since 2024-10-18\n */\npublic interface MNoticeMapper extends BaseMapper<MNotice> {\n    /**\n     * 根据公告ID删除公告\n     * @param noticeId 公告ID\n     */\n    default void deleteNoticeByNoticeId(Long noticeId) {\n        deleteByQuery(QueryWrapper.create().eq(MNotice::getNoticeId, noticeId));\n    }\n\n    /**\n     * 根据公告ID修改公告\n     * @param notice 公告数据\n     */\n    default void updateNoticeByNoticeId(MNotice notice) {\n        updateByQuery(notice, QueryWrapper.create().eq(MNotice::getNoticeId, notice.getNoticeId()));\n    }\n\n    /**\n     * 查询公告列表(分页) -> 公告管理使用\n     * @param queryVO 查询条件\n     * @return 公告分页数据\n     */\n    default Page<MNotice> selectPageNoticeList(NoticeQueryVO queryVO) {\n        return paginate(queryVO.getPageNum(), queryVO.getPageSize(),\n                QueryWrapper.create()\n                        .eq(MNotice::getStatus, queryVO.getStatus())\n                        .eq(MNotice::getNoticeType, queryVO.getNoticeType())\n                        .like(MNotice::getNoticeTitle, queryVO.getNoticeTitle())\n                        .orderBy(MNotice::getNoticeTop, false)\n                        .orderBy(MNotice::getNoticeSort, true));\n    }\n\n    /**\n     * 根据公告ID查询公告\n     * @param noticeId 公告ID\n     * @return 公告实体\n     */\n    default MNotice selectNoticeByNoticeId(Long noticeId) {\n        return selectOneByQuery(QueryWrapper.create().eq(MNotice::getNoticeId, noticeId));\n    }\n\n    /**\n     * 查询公告列表(分页) -> 首页使用\n     * @return 公告列表\n     */\n    default Page<MNotice> selectHomePageNoticeList(PageReq pageReq) {\n        return paginate(pageReq.getPageNum(), pageReq.getPageSize(),\n                QueryWrapper.create()\n                        .eq(MNotice::getStatus, StatusEnum.STATUS_1.getCode())\n                        .eq(MNotice::getNoticeType, NoticeEnum.NoticeType.NOTICE.getCode())\n                        .le(MNotice::getPublishTime, LocalDateTime.now())\n                        .orderBy(MNotice::getNoticeTop, false)\n                        .orderBy(MNotice::getNoticeSort, true)\n        );\n    }\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/mapper/MPermsMapper.java",
    "content": "package com.minimalist.basic.mapper;\n\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.entity.vo.perm.PermQueryVO;\nimport com.mybatisflex.core.BaseMapper;\nimport com.minimalist.basic.entity.po.MPerms;\nimport com.mybatisflex.core.query.QueryWrapper;\nimport java.util.List;\n\n/**\n * 权限表 映射层。\n *\n * @author 小太阳\n * @since 2024-10-18\n */\npublic interface MPermsMapper extends BaseMapper<MPerms> {\n\n    /**\n     * 根据权限ID获取权限（多条）\n     * @param permsIds 权限ID集合\n     * @return 权限集合\n     */\n    default List<MPerms> selectPermsByPermsIds(List<Long> permsIds) {\n        return selectListByQuery(QueryWrapper.create().in(MPerms::getPermId, permsIds));\n    }\n\n    /**\n     * 根据权限ID获取权限（单条）\n     * @param permId 权限ID\n     * @return 权限实体\n     */\n    default MPerms selectPermsByPermId(Long permId) {\n        return selectOneByQuery(QueryWrapper.create().eq(MPerms::getPermId, permId));\n    }\n\n    /**\n     * 根据权限ID修改权限\n     * @param perms 权限实体\n     */\n    default void updatePermsByPermId(MPerms perms) {\n        updateByQuery(perms, QueryWrapper.create().eq(MPerms::getPermId, perms.getPermId()));\n    }\n\n    /**\n     * 根据权限ID查询直属下级数量\n     * @param permId 权限ID\n     * @return 下级数量\n     */\n    default long selectChildrenCountByPermId(Long permId) {\n        return selectCountByQuery(QueryWrapper.create().eq(MPerms::getParentPermId, permId));\n    }\n\n    /**\n     * 根据权限ID删除权限\n     * @param permId 权限ID\n     */\n    default void deletePermsByPermId(Long permId) {\n        deleteByQuery(QueryWrapper.create().eq(MPerms::getPermId, permId));\n    }\n\n    /**\n     * 根据条件查询权限列表\n     * @param queryVO 查询参数\n     * @return 权限列表\n     */\n    default List<MPerms> selectPermList(PermQueryVO queryVO) {\n        return selectListByQuery(QueryWrapper.create()\n                .eq(MPerms::getStatus, queryVO.getStatus())\n                .like(MPerms::getPermName, queryVO.getPermName())\n                .like(MPerms::getPermType, queryVO.getPermType())\n        );\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/mapper/MPostMapper.java",
    "content": "package com.minimalist.basic.mapper;\n\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.entity.vo.post.PostQueryVO;\nimport com.minimalist.basic.entity.vo.post.PostVO;\nimport com.mybatisflex.core.BaseMapper;\nimport com.minimalist.basic.entity.po.MPost;\nimport com.mybatisflex.core.paginate.Page;\nimport com.mybatisflex.core.query.QueryWrapper;\nimport java.util.List;\n\n/**\n * 岗位表 映射层。\n *\n * @author 小太阳\n * @since 2024-10-18\n */\npublic interface MPostMapper extends BaseMapper<MPost> {\n\n    /**\n     * 根据岗位编码查询岗位\n     * @param postCode 岗位编码\n     * @return 岗位实体\n     */\n    default MPost selectPostByPostCode(String postCode) {\n        return selectOneByQuery(QueryWrapper.create().eq(MPost::getPostCode, postCode));\n    }\n\n    /**\n     * 根据岗位ID删除岗位\n     * @param postId 岗位ID\n     */\n    default void deletePostByPostId(Long postId) {\n        deleteByQuery(QueryWrapper.create().eq(MPost::getPostId, postId));\n    }\n\n    /**\n     * 根据岗位ID查询岗位(单条)\n     * @param postId 岗位ID\n     * @return 岗位实体\n     */\n    default MPost selectPostByPostId(Long postId) {\n        return selectOneByQuery(QueryWrapper.create().eq(MPost::getPostId, postId));\n    }\n\n    /**\n     * 根据岗位ID查询岗位(批量)\n     * @param postIds 岗位ID列表\n     * @return 岗位实体列表\n     */\n    default List<PostVO> selectPostByPostIds(List<Long> postIds) {\n        return selectListByQueryAs(QueryWrapper.create().in(MPost::getPostId, postIds), PostVO.class);\n    }\n\n    /**\n     * 根据岗位ID修改岗位\n     * @param mPost 岗位数据\n     */\n    default void updatePostByPostId(MPost mPost) {\n        updateByQuery(mPost, QueryWrapper.create().eq(MPost::getPostId, mPost.getPostId()));\n    }\n\n    /**\n     * 查询岗位列表(分页)\n     * @param queryVO 查询条件\n     * @return 岗位分页数据\n     */\n    default Page<PostVO> selectPagePostList(PostQueryVO queryVO) {\n        return paginateAs(queryVO.getPageNum(), queryVO.getPageSize(),\n                QueryWrapper.create()\n                        .eq(MPost::getStatus, queryVO.getStatus())\n                        .like(MPost::getPostName, queryVO.getPostName())\n                        .like(MPost::getPostCode, queryVO.getPostCode())\n                        .orderBy(MPost::getPostSort, true),\n                PostVO.class\n        );\n    }\n\n    /**\n     * 根据租户ID查询岗位列表 -> 字典查询\n     * @return 部门列表\n     */\n    default List<MPost> selectPostDict() {\n        return selectListByQuery(QueryWrapper.create().eq(MPost::getStatus, StatusEnum.STATUS_1.getCode()));\n    }\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/mapper/MRoleDeptMapper.java",
    "content": "package com.minimalist.basic.mapper;\n\nimport com.mybatisflex.core.BaseMapper;\nimport com.minimalist.basic.entity.po.MRoleDept;\n\n/**\n * 角色与部门关联表 1角色-N部门 映射层。\n *\n * @author 小太阳\n * @since 2024-10-18\n */\npublic interface MRoleDeptMapper extends BaseMapper<MRoleDept> {\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/mapper/MRoleMapper.java",
    "content": "package com.minimalist.basic.mapper;\n\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.entity.vo.role.RoleQueryVO;\nimport com.minimalist.basic.entity.vo.role.RoleVO;\nimport com.mybatisflex.core.BaseMapper;\nimport com.minimalist.basic.entity.po.MRole;\nimport com.mybatisflex.core.paginate.Page;\nimport com.mybatisflex.core.query.QueryWrapper;\nimport java.util.List;\n\n/**\n * 角色表 映射层。\n *\n * @author 小太阳\n * @since 2024-10-18\n */\npublic interface MRoleMapper extends BaseMapper<MRole> {\n\n    /**\n     * 根据角色编码查询角色\n     * @param roleCode 角色编码\n     * @return 角色实体\n     */\n    default MRole selectRoleByRoleCode(String roleCode) {\n        return selectOneByQuery(QueryWrapper.create().eq(MRole::getRoleCode, roleCode));\n    }\n\n    /**\n     * 根据角色ID查询角色\n     * @param roleId 角色ID\n     * @return 角色实体\n     */\n    default MRole selectRoleByRoleId(Long roleId) {\n        return selectOneByQuery(QueryWrapper.create().eq(MRole::getRoleId, roleId));\n    }\n\n    /**\n     * 根据角色ID查询角色\n     * @param roleId 角色ID\n     * @return 角色实体\n     */\n    default List<RoleVO> selectRoleByRoleIds(List<Long> roleId) {\n        return selectListByQueryAs(QueryWrapper.create().in(MRole::getRoleId, roleId), RoleVO.class);\n    }\n\n    /**\n     * 根据角色ID删除角色\n     * @param roleId 角色ID\n     */\n    default void deleteRoleByRoleId(Long roleId) {\n        deleteByQuery(QueryWrapper.create().eq(MRole::getRoleId, roleId));\n    }\n\n    /**\n     * 根据角色ID修改角色\n     * @param role 角色实体\n     */\n    default void updateRoleByRoleId(MRole role) {\n        updateByQuery(role, QueryWrapper.create().eq(MRole::getRoleId, role.getRoleId()));\n    }\n\n    /**\n     * 查询角色列表(分页)\n     * @param queryVO 查询条件\n     * @return 角色分页数据\n     */\n    default Page<RoleVO> selectPageRoleList(RoleQueryVO queryVO) {\n        return paginateAs(queryVO.getPageNum(), queryVO.getPageSize(),\n                QueryWrapper.create()\n                        .eq(MRole::getStatus, queryVO.getStatus())\n                        .like(MRole::getRoleCode, queryVO.getRoleCode())\n                        .like(MRole::getRoleName, queryVO.getRoleName()),\n                RoleVO.class\n        );\n    }\n\n    /**\n     * 根据租户ID查询角色列表 -> 字典查询\n     * @return 部门列表\n     */\n    default List<MRole> selectRoleDict() {\n        return selectListByQuery(QueryWrapper.create().eq(MRole::getStatus, StatusEnum.STATUS_1.getCode()));\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/mapper/MRolePermMapper.java",
    "content": "package com.minimalist.basic.mapper;\n\nimport com.mybatisflex.core.BaseMapper;\nimport com.minimalist.basic.entity.po.MRolePerm;\nimport com.mybatisflex.core.query.QueryWrapper;\nimport java.util.List;\n\n/**\n * 角色与权限关联表 1角色-N权限 映射层。\n *\n * @author 小太阳\n * @since 2024-10-18\n */\npublic interface MRolePermMapper extends BaseMapper<MRolePerm> {\n\n    /**\n     * 根据角色ID获取角色与权限关联关系\n     * @param roleIds 角色ID集合(多条)\n     * @return 角色与权限关联数据\n     */\n    default List<MRolePerm> selectRolePermByRoleIds(List<Long> roleIds) {\n        return selectListByQuery(QueryWrapper.create().in(MRolePerm::getRoleId, roleIds));\n    }\n\n    /**\n     * 根据角色ID获取角色与权限关联关系\n     * @param roleId 角色ID(单条)\n     * @return 角色与权限关联数据\n     */\n    default List<MRolePerm> selectRolePermByRoleId(Long roleId) {\n        return selectListByQuery(QueryWrapper.create().eq(MRolePerm::getRoleId, roleId));\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/mapper/MStorageMapper.java",
    "content": "package com.minimalist.basic.mapper;\n\nimport com.minimalist.basic.entity.vo.storage.StorageQueryVO;\nimport com.minimalist.basic.entity.vo.storage.StorageVO;\nimport com.mybatisflex.core.BaseMapper;\nimport com.minimalist.basic.entity.po.MStorage;\nimport com.mybatisflex.core.paginate.Page;\nimport com.mybatisflex.core.query.QueryWrapper;\n\n/**\n * 存储管理表 映射层。\n *\n * @author 小太阳\n * @since 2024-10-18\n */\npublic interface MStorageMapper extends BaseMapper<MStorage> {\n    /**\n     * 根据存储ID查询存储信息\n     * @param storageId 存储ID\n     * @return 存储信息\n     */\n    default MStorage selectStorageByStorageId(Long storageId) {\n        return selectOneByQuery(QueryWrapper.create().eq(MStorage::getStorageId, storageId));\n    }\n\n    /**\n     * 根据存储ID删除存储\n     * @param storageId 存储ID\n     */\n    default void deleteStorageByStorageId(Long storageId) {\n        deleteByQuery(QueryWrapper.create().eq(MStorage::getStorageId, storageId));\n    }\n\n    /**\n     * 根据存储ID修改存储信息\n     * @param storage 存储信息\n     */\n    default void updateStorageByStorageId(MStorage storage) {\n        updateByQuery(storage, QueryWrapper.create().eq(MStorage::getStorageId, storage.getStorageId()));\n    }\n\n    /**\n     * 根据条件查询存储信息列表（分页）\n     * @param queryVO 查询条件\n     * @return 存储信息列表\n     */\n    default Page<StorageVO> selectPageStorageList(StorageQueryVO queryVO) {\n        return paginateAs(queryVO.getPageNum(), queryVO.getPageSize(),\n                QueryWrapper.create()\n                        .eq(MStorage::getStorageType, queryVO.getStorageType())\n                        .eq(MStorage::getStatus, queryVO.getStatus())\n                        .like(MStorage::getStorageName, queryVO.getStorageName()),\n                StorageVO.class\n        );\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/mapper/MTenantDatasourceMapper.java",
    "content": "package com.minimalist.basic.mapper;\n\nimport com.mybatisflex.core.BaseMapper;\nimport com.minimalist.basic.entity.po.MTenantDatasource;\nimport com.mybatisflex.core.query.QueryWrapper;\n\n/**\n * 租户数据源表 映射层。\n *\n * @author asus\n * @since 2025-02-14\n */\npublic interface MTenantDatasourceMapper extends BaseMapper<MTenantDatasource> {\n\n    /**\n     * 根据租户ID删除数据源信息\n     * @param tenantId 租户ID\n     */\n    default void deleteTenantDatasourceByTenantId(Long tenantId) {\n        deleteByQuery(QueryWrapper.create().eq(MTenantDatasource::getTenantId, tenantId));\n    }\n\n    /**\n     * 根据租户ID查询数据源信息\n     * @param tenantId 租户ID\n     * @return 数据源信息\n     */\n    default MTenantDatasource selectTenantDatasourceByTenantId(Long tenantId) {\n        return selectOneByQuery(QueryWrapper.create().eq(MTenantDatasource::getTenantId, tenantId));\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/mapper/MTenantMapper.java",
    "content": "package com.minimalist.basic.mapper;\n\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.entity.vo.tenant.TenantQueryVO;\nimport com.minimalist.basic.entity.vo.tenant.TenantVO;\nimport com.minimalist.basic.utils.CommonConstant;\nimport com.mybatisflex.core.BaseMapper;\nimport com.minimalist.basic.entity.po.MTenant;\nimport com.mybatisflex.core.paginate.Page;\nimport com.mybatisflex.core.query.QueryWrapper;\nimport java.util.List;\n\n/**\n * 租户表 映射层。\n *\n * @author 小太阳\n * @since 2024-10-18\n */\npublic interface MTenantMapper extends BaseMapper<MTenant> {\n    /**\n     * 根据租户套餐ID查询租户数量\n     * @param tenantPackageId 租户套餐ID\n     * @return 租户数量\n     */\n    default long selectTenantCountByTenantPackageId(Long tenantPackageId) {\n        return selectCountByQuery(QueryWrapper.create().eq(MTenant::getPackageId, tenantPackageId));\n    }\n\n    /**\n     * 根据租户套餐ID查询租户\n     * @param tenantPackageId 租户套餐ID\n     * @return 租户数量\n     */\n    default List<MTenant> selectTenantByTenantPackageId(Long tenantPackageId) {\n        return selectListByQuery(QueryWrapper.create().eq(MTenant::getPackageId, tenantPackageId));\n    }\n\n    /**\n     * 根据租户名查询租户\n     * @param tenantName 租户名\n     * @return 租户实体\n     */\n    default MTenant selectTenantByTenantName(String tenantName) {\n        return selectOneByQuery(QueryWrapper.create().eq(MTenant::getTenantName, tenantName));\n    }\n\n    /**\n     * 根据租户ID查询租户\n     * @param tenantId 租户ID\n     * @return 租户实体\n     */\n    default MTenant selectTenantByTenantId(Long tenantId) {\n        return selectOneByQuery(QueryWrapper.create().eq(MTenant::getTenantId, tenantId));\n    }\n\n    /**\n     * 根据租户ID删除租户\n     * @param tenantId 租户ID\n     */\n    default void deleteTenantByTenantId(Long tenantId) {\n        deleteByQuery(QueryWrapper.create().eq(MTenant::getTenantId, tenantId));\n    }\n\n    /**\n     * 根据租户ID更新租户\n     * @param tenant 租户信息\n     */\n    default void updateTenantByTenantId(MTenant tenant) {\n        updateByQuery(tenant, QueryWrapper.create().eq(MTenant::getTenantId, tenant.getTenantId()));\n    }\n\n    /**\n     * 查询租户(分页)\n     * @param queryVO 查询条件\n     * @return 租户分页数据\n     */\n    default Page<TenantVO> selectPageTenantList(TenantQueryVO queryVO) {\n        return paginateAs(queryVO.getPageNum(), queryVO.getPageSize(),\n                QueryWrapper.create()\n                        .eq(MTenant::getStatus, queryVO.getStatus())\n                        //排除系统租户\n                        .ne(MTenant::getTenantId, CommonConstant.ZERO)\n                        .like(MTenant::getTenantName, queryVO.getTenantName()),\n                TenantVO.class\n        );\n    }\n\n    /**\n     * 租户列表 -> 字典查询\n     * @return 部门列表\n     */\n    default List<MTenant> selectTenantDict() {\n        return selectListByQuery(QueryWrapper.create().eq(MTenant::getStatus, StatusEnum.STATUS_1.getCode()));\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/mapper/MTenantPackageMapper.java",
    "content": "package com.minimalist.basic.mapper;\n\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.entity.vo.tenant.TenantPackageQueryVO;\nimport com.minimalist.basic.entity.vo.tenant.TenantPackageVO;\nimport com.mybatisflex.core.BaseMapper;\nimport com.minimalist.basic.entity.po.MTenantPackage;\nimport com.mybatisflex.core.paginate.Page;\nimport com.mybatisflex.core.query.QueryWrapper;\nimport java.util.List;\n\n/**\n * 租户套餐表 映射层。\n *\n * @author 小太阳\n * @since 2024-10-18\n */\npublic interface MTenantPackageMapper extends BaseMapper<MTenantPackage> {\n\n    /**\n     * 根据租户套餐ID查询租户套餐\n     * @param tenantPackageId 租户套餐ID\n     * @return 租户套餐实体\n     */\n    default MTenantPackage selectTenantPackageByTenantPackageId(Long tenantPackageId) {\n        return selectOneByQuery(QueryWrapper.create().eq(MTenantPackage::getPackageId, tenantPackageId));\n    }\n\n    /**\n     * 根据租户套餐ID删除租户套餐\n     * @param tenantPackageId 租户套餐ID\n     */\n    default void deleteTenantPackageByTenantPackageId(Long tenantPackageId) {\n        deleteByQuery(QueryWrapper.create().eq(MTenantPackage::getPackageId, tenantPackageId));\n    }\n\n    /**\n     * 根据租户套餐ID修改租户套餐\n     * @param tenantPackage 租户套餐实体\n     */\n    default void updateTenantPackageByTenantPackageId(MTenantPackage tenantPackage) {\n        updateByQuery(tenantPackage, QueryWrapper.create().eq(MTenantPackage::getPackageId, tenantPackage.getPackageId()));\n    }\n\n    /**\n     * 查询租户套餐(分页)\n     * @param queryVO 查询条件\n     * @return 租户套餐分页数据\n     */\n    default Page<TenantPackageVO> selectPageTenantPackageList(TenantPackageQueryVO queryVO) {\n        return paginateAs(queryVO.getPageNum(), queryVO.getPageSize(),\n                QueryWrapper.create()\n                        .eq(MTenantPackage::getStatus, queryVO.getStatus())\n                        .like(MTenantPackage::getPackageName, queryVO.getPackageName()),\n                TenantPackageVO.class\n        );\n    }\n\n    /**\n     * 查询全部租户套餐 -> 字典查询\n     * @return 用户列表\n     */\n    default List<MTenantPackage> selectTenantPackageDict() {\n        return selectListByQuery(QueryWrapper.create().eq(MTenantPackage::getStatus, StatusEnum.STATUS_1.getCode()));\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/mapper/MTenantPackagePermMapper.java",
    "content": "package com.minimalist.basic.mapper;\n\nimport com.mybatisflex.core.BaseMapper;\nimport com.minimalist.basic.entity.po.MTenantPackagePerm;\nimport com.mybatisflex.core.query.QueryWrapper;\nimport java.util.List;\n\n/**\n * 租户套餐与权限关联表 映射层。\n *\n * @author 小太阳\n * @since 2024-10-18\n */\npublic interface MTenantPackagePermMapper extends BaseMapper<MTenantPackagePerm> {\n\n    /**\n     * 根据租户套餐ID查询租户套餐与权限关联数据\n     * @param tenantPackageId 租户套餐ID\n     * @return 租户套餐与权限关联数据\n     */\n    default List<MTenantPackagePerm> selectTenantPackagePermByTenantPackageId(Long tenantPackageId) {\n        return selectListByQuery(QueryWrapper.create().eq(MTenantPackagePerm::getPackageId, tenantPackageId));\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/mapper/MUserDeptMapper.java",
    "content": "package com.minimalist.basic.mapper;\n\nimport com.mybatisflex.core.BaseMapper;\nimport com.minimalist.basic.entity.po.MUserDept;\nimport com.mybatisflex.core.query.QueryWrapper;\nimport java.util.List;\n\n/**\n * 用户与岗位关联表 映射层。\n *\n * @author 小太阳\n * @since 2024-10-18\n */\npublic interface MUserDeptMapper extends BaseMapper<MUserDept> {\n\n    /**\n     * 根据用户ID查询用户与部门关联数据\n     * @param userId 用户ID\n     * @return 用户与部门关联数据\n     */\n    default List<MUserDept> selectUserDeptRelation(Long userId) {\n        return selectListByQuery(QueryWrapper.create().eq(MUserDept::getUserId, userId));\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/mapper/MUserMapper.java",
    "content": "package com.minimalist.basic.mapper;\n\nimport cn.hutool.core.util.ObjectUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.entity.po.table.MDeptTableDef;\nimport com.minimalist.basic.entity.po.table.MUserDeptTableDef;\nimport com.minimalist.basic.entity.po.table.MUserTableDef;\nimport com.minimalist.basic.entity.vo.user.UserQueryVO;\nimport com.minimalist.basic.entity.vo.user.UserVO;\nimport com.minimalist.basic.utils.CommonConstant;\nimport com.mybatisflex.core.BaseMapper;\nimport com.minimalist.basic.entity.po.MUser;\nimport com.mybatisflex.core.paginate.Page;\nimport com.mybatisflex.core.query.QueryColumn;\nimport com.mybatisflex.core.query.QueryMethods;\nimport com.mybatisflex.core.query.QueryWrapper;\nimport org.apache.ibatis.annotations.Param;\nimport java.util.List;\n\n/**\n * 用户表 映射层。\n *\n * @author 小太阳\n * @since 2024-10-18\n */\npublic interface MUserMapper extends BaseMapper<MUser> {\n\n    /**\n     * 根据用户名查询用户\n     * @param username 用户名\n     * @return 用户PO\n     */\n    default MUser selectUserByUsername(String username) {\n        return selectOneByQuery(QueryWrapper.create().eq(MUser::getUsername, username));\n    }\n\n    /**\n     * 根据用户ID查询用户\n     * @param userId 用户ID\n     * @return 用户PO\n     */\n    default MUser selectUserByUserId(Long userId) {\n        return selectOneByQuery(QueryWrapper.create().eq(MUser::getUserId, userId));\n    }\n\n    /**\n     * 根据用户ID查询用户\n     * @param userIdList 用户ID列表\n     * @return 用户PO\n     */\n    default List<MUser> selectUserByUserIds(List<Long> userIdList) {\n        return selectListByQuery(QueryWrapper.create().in(MUser::getUserId, userIdList));\n    }\n\n    /**\n     * 根据租户ID查询用户 -> 字典查询（区分租户和管理员）\n     * @return 用户列表\n     */\n    default List<MUser> selectUserDict() {\n        return selectListByQuery(QueryWrapper.create().eq(MUser::getStatus, StatusEnum.STATUS_1.getCode()));\n    }\n\n    /**\n     * 根据租户ID查询该租户下的用户数量\n     * @param tenantId 租户ID\n     * @return 该租户下的用户数量\n     */\n    default long selectUserCountByTenantId(Long tenantId) {\n        return selectCountByQuery(QueryWrapper.create().eq(MUser::getTenantId, tenantId));\n    }\n\n    /**\n     * 根据手机号查询用户\n     * @param phone 手机号\n     * @return 用户实体\n     */\n    default MUser selectUserByPhone(String phone) {\n        return selectOneByQuery(QueryWrapper.create().eq(MUser::getPhone, phone));\n    }\n\n    /**\n     * 根据用户ID删除用户\n     * @param userId 用户ID\n     */\n    default void deleteUserByUserId(Long userId) {\n        deleteByQuery(QueryWrapper.create().eq(MUser::getUserId, userId));\n    }\n\n    /**\n     * 根据用户ID修改用户\n     * @param user 用户数据\n     */\n    default void updateUserByUserId(MUser user) {\n        updateByQuery(user, QueryWrapper.create().eq(MUser::getUserId, user.getUserId()));\n    }\n\n    /**\n     * 根据部门ID列表，查询用户数\n     * @param deptIds 部门ID\n     * @return 用户数\n     */\n    default long selectUserCountByDeptIds(List<Long> deptIds) {\n        long result = 0;\n        for (Long deptId : deptIds) {\n            result += selectCountByQuery(QueryWrapper.create().where(\"FIND_IN_SET(\"  + deptId +\", ancestors)\"));\n        }\n        return result;\n    }\n\n    /**\n     * 查询用户列表(分页)\n     * @param query 查询条件\n     * @return 用户分页数据\n     */\n    default Page<UserVO> selectPageUserList(UserQueryVO query) {\n        /* select u.* FROM m_user u\n         * inner join m_user_dept ud on u.user_id = ud.user_id\n         * inner join m_dept d on d.dept_id = ud.dept_id\n         * WHERE (\n         *  d.dept_id = 1677964029214371840 or d.dept_id in (select t.dept_id from m_dept t where find_in_set(1677964029214371840, ancestors))\n         * )\n         * group by u.user_id;\n         */\n        QueryWrapper queryWrapper = QueryWrapper.create()\n                .select(MUserTableDef.MUSER.ALL_COLUMNS)\n                .from(MUserTableDef.MUSER)\n                .leftJoin(MUserDeptTableDef.MUSER_DEPT).on(MUserDeptTableDef.MUSER_DEPT.USER_ID.eq(MUserTableDef.MUSER.USER_ID))\n                .leftJoin(MDeptTableDef.MDEPT).on(MDeptTableDef.MDEPT.DEPT_ID.eq(MUserDeptTableDef.MUSER_DEPT.DEPT_ID))\n                .where(MUserTableDef.MUSER.STATUS.eq(query.getStatus()))\n                .and(MUserTableDef.MUSER.PHONE.like(query.getPhone()))\n                .and(MUserTableDef.MUSER.USER_REAL_NAME.like(query.getUserRealName()));\n        //deptId=0表示全部，需要忽略\n        if (ObjectUtil.isNotNull(query.getDeptId()) && CommonConstant.ZERO != query.getDeptId()) {\n            queryWrapper.and(\n                    MDeptTableDef.MDEPT.DEPT_ID.eq(query.getDeptId())\n                            .or(MDeptTableDef.MDEPT.DEPT_ID.in(\n                                    QueryWrapper.create()\n                                            .select(MDeptTableDef.MDEPT.DEPT_ID)\n                                            .from(MDeptTableDef.MDEPT)\n                                            .where(\"FIND_IN_SET(\" + query.getDeptId() + \", ancestors)\")\n                            ))\n            );\n        }\n        queryWrapper.groupBy(MUserTableDef.MUSER.USER_ID);\n        return paginateAs(query.getPageNum(), query.getPageSize(), queryWrapper, UserVO.class);\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/mapper/MUserPostMapper.java",
    "content": "package com.minimalist.basic.mapper;\n\nimport com.mybatisflex.core.BaseMapper;\nimport com.minimalist.basic.entity.po.MUserPost;\nimport com.mybatisflex.core.query.QueryWrapper;\nimport java.util.List;\n\n/**\n * 用户与岗位关联表 1用户-N岗位 映射层。\n *\n * @author 小太阳\n * @since 2024-10-18\n */\npublic interface MUserPostMapper extends BaseMapper<MUserPost> {\n\n    /**\n     * 根据用户ID查询用户与岗位关联数据\n     * @param userId 用户ID\n     * @return 用户与岗位关联数据\n     */\n    default List<MUserPost> selectUserPostRelation(Long userId) {\n        return selectListByQuery(QueryWrapper.create().eq(MUserPost::getUserId, userId));\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/mapper/MUserRoleMapper.java",
    "content": "package com.minimalist.basic.mapper;\n\nimport com.mybatisflex.core.BaseMapper;\nimport com.minimalist.basic.entity.po.MUserRole;\nimport com.mybatisflex.core.query.QueryWrapper;\nimport java.util.List;\n\n/**\n * 用户与角色关联表 1用户-N角色 映射层。\n *\n * @author 小太阳\n * @since 2024-10-18\n */\npublic interface MUserRoleMapper extends BaseMapper<MUserRole> {\n\n    /**\n     * 根据userId查询用户与角色关联关系\n     * @param userId 用户ID\n     * @return 用户与角色关联关系\n     */\n    default List<MUserRole> selectUserRoleRelation(Long userId) {\n        return selectListByQuery(QueryWrapper.create().eq(MUserRole::getUserId, userId));\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/mq/SystemConfigSyncConsumer.java",
    "content": "package com.minimalist.basic.mq;\n\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.json.JSONUtil;\nimport com.minimalist.basic.entity.vo.config.ConfigVO;\nimport com.minimalist.basic.utils.CommonConstant;\nimport com.minimalist.basic.utils.RedisKeyConstant;\nimport lombok.extern.slf4j.Slf4j;\nimport org.redisson.api.RPatternTopic;\nimport org.redisson.api.RedissonClient;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.ApplicationArguments;\nimport org.springframework.boot.ApplicationRunner;\nimport org.springframework.stereotype.Component;\n\n/**\n * 系统配置同步消费者\n * 主要是防止部署多个节点，修改后不同步问题\n * 因为数据会缓存在一个Map中，在页面上修改后，需要让所有节点在内存中的Map同时更新\n */\n@Slf4j\n@Component\npublic class SystemConfigSyncConsumer implements ApplicationRunner {\n\n    @Autowired\n    private RedissonClient redissonClient;\n\n    @Override\n    public void run(ApplicationArguments args) throws Exception {\n        RPatternTopic topic = redissonClient.getPatternTopic(RedisKeyConstant.SYSTEM_CONFIG_TOPIC_KEY + \".*\");\n        topic.addListener(String.class, (pattern, channel, msg) -> {\n            log.info(\"系统配置同步处理，参数：{}\", msg);\n            try {\n                //区分新增、修改、删除\n                String opt = StrUtil.subAfter(channel, \".\", true);\n                if (CommonConstant.ADD.equals(opt) || CommonConstant.UPDATE.equals(opt)) {\n                    ConfigVO configVO = JSONUtil.toBean(msg, ConfigVO.class);\n                    CommonConstant.systemConfigMap.put(configVO.getConfigKey(), configVO);\n                }\n                if (CommonConstant.DELETE.equals(opt)) {\n                    CommonConstant.systemConfigMap.remove(msg);\n                }\n                log.info(\"系统配置同步处理，完毕！\");\n            } catch (Exception e) {\n                log.error(\"系统配置同步处理异常：\", e);\n            }\n        });\n        System.out.println(\"-------------------- 系统配置同步消费者监听启动完毕 --------------------\");\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/mq/TenantDatasourceSyncConsumer.java",
    "content": "package com.minimalist.basic.mq;\n\nimport cn.hutool.core.util.ObjectUtil;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.json.JSONUtil;\nimport com.minimalist.basic.entity.enums.TenantEnum;\nimport com.minimalist.basic.entity.vo.tenant.TenantVO;\nimport com.minimalist.basic.manager.TenantManager;\nimport com.minimalist.basic.utils.CommonConstant;\nimport com.minimalist.basic.utils.RedisKeyConstant;\nimport lombok.extern.slf4j.Slf4j;\nimport org.redisson.api.RPatternTopic;\nimport org.redisson.api.RedissonClient;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.ApplicationArguments;\nimport org.springframework.boot.ApplicationRunner;\nimport org.springframework.stereotype.Component;\n\n/**\n * 租户数据源信息同步消费者\n * 主要是防止部署多个节点，修改后不同步问题\n * 因为数据会缓存在一个Map中，在页面上修改后，需要让所有节点在内存中的Map同时更新\n */\n@Slf4j\n@Component\npublic class TenantDatasourceSyncConsumer implements ApplicationRunner {\n\n    @Autowired\n    private RedissonClient redissonClient;\n\n    @Autowired\n    private TenantManager tenantManager;\n\n    @Override\n    public void run(ApplicationArguments args) throws Exception {\n        RPatternTopic topic = redissonClient.getPatternTopic(RedisKeyConstant.TENANT_DATA_TOPIC_KEY + \".*\");\n        topic.addListener(String.class, (pattern, channel, msg) -> {\n            log.info(\"租户数据源信息同步处理，参数：{}\", msg);\n            try {\n                //区分新增、修改、删除\n                String opt = StrUtil.subAfter(channel, \".\", true);\n                if (CommonConstant.ADD.equals(opt) || CommonConstant.UPDATE.equals(opt)) {\n                    TenantVO tenantVO = JSONUtil.toBean(msg, TenantVO.class);\n                    CommonConstant.tenantMap.put(tenantVO.getTenantId(), tenantVO);\n                    //动态加载数据源\n                    if (TenantEnum.DataIsolation.DB.getCode().equals(tenantVO.getDataIsolation())\n                        && ObjectUtil.isNotNull(tenantVO.getTenantDatasource())) {\n                        String tenantId = tenantVO.getTenantId().toString();\n                        //删除旧数据源\n                        tenantManager.dynamicDeleteDatasource(tenantId);\n                        //动态添加数据源\n                        tenantManager.dynamicAddDatasource(tenantId, tenantVO.getTenantDatasource());\n                    } else {\n                        //不需要数据库隔离，删除数据源\n                        tenantManager.dynamicDeleteDatasource(tenantVO.getTenantId().toString());\n                    }\n                }\n                if (CommonConstant.DELETE.equals(opt)) {\n                    CommonConstant.tenantMap.remove(Long.parseLong(msg));\n                    //动态删除数据源\n                    tenantManager.dynamicDeleteDatasource(msg);\n                }\n                log.info(\"租户数据源信息同步处理，完毕！\");\n            } catch (Exception e) {\n                log.error(\"租户数据源信息同步处理异常：\", e);\n            }\n        });\n        System.out.println(\"-------------------- 租户数据源信息同步消费者监听启动完毕 --------------------\");\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/service/ConfigService.java",
    "content": "package com.minimalist.basic.service;\n\nimport com.minimalist.basic.config.mybatis.bo.PageResp;\nimport com.minimalist.basic.entity.vo.config.ConfigQueryVO;\nimport com.minimalist.basic.entity.vo.config.ConfigVO;\n\npublic interface ConfigService {\n\n    /**\n     * 添加参数\n     * @param configVO 参数配置信息\n     */\n    void addConfig(ConfigVO configVO);\n\n    /**\n     * 删除参数 -> 根据参数ID删除\n     * @param configId 参数ID\n     */\n    void deleteConfigByConfigId(Long configId);\n\n    /**\n     * 修改参数\n     * @param configVO 参数配置信息\n     */\n    void updateConfigByConfigId(ConfigVO configVO);\n\n    /**\n     * 查询参数配置列表(分页)\n     * @param queryVO 查询条件\n     * @return 参数配置列表\n     */\n    PageResp<ConfigVO> getPageConfigList(ConfigQueryVO queryVO);\n\n    /**\n     * 根据参数ID查询参数\n     * @param configId 参数ID\n     * @return 参数信息\n     */\n    ConfigVO getConfigByConfigId(Long configId);\n\n    /**\n     * 根据参数Key查询参数 - 状态为正常\n     * @param configKey 参数Key\n     * @return 参数信息\n     */\n    ConfigVO getConfigByConfigKey(String configKey);\n\n    /**\n     * 刷新配置缓存\n     */\n    void refreshConfigCache();\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/service/DeptService.java",
    "content": "package com.minimalist.basic.service;\n\nimport com.minimalist.basic.entity.vo.dept.DeptQueryVO;\nimport com.minimalist.basic.entity.vo.dept.DeptVO;\nimport java.util.List;\n\npublic interface DeptService {\n\n    /**\n     * 添加部门\n     * @param deptVO 部门数据\n     */\n    void addDept(DeptVO deptVO);\n\n    /**\n     * 删除部门 -> 根据部门ID删除\n     * @param deptId 部门ID\n     */\n    void deleteDeptByDeptId(Long deptId);\n\n    /**\n     * 修改部门 -> 根据部门ID修改\n     * @param deptVO 部门数据\n     */\n    void updateDeptByDeptId(DeptVO deptVO);\n\n    /**\n     * 查询部门列表（全部不分页） -> 部门管理使用\n     * @param queryVO 查询参数\n     * @return 部门树\n     */\n    List<DeptVO> getDeptList(DeptQueryVO queryVO);\n\n    /**\n     * 根据部门ID查询部门\n     * @param deptId 部门ID\n     * @return 部门数据\n     */\n    DeptVO getDeptByDeptId(Long deptId);\n\n    /**\n     * 查询部门列表 -> 其他地方使用(只获取正常状态的部门)\n     * @return 部门树\n     */\n    List<DeptVO> getEnableDeptList();\n\n    /**\n     * 根据部门ID查询部门\n     * @param deptIds 部门ID列表\n     * @return 用户所属部门列表\n     */\n    List<DeptVO> getDeptByDeptIds(List<Long> deptIds);\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/service/DictService.java",
    "content": "package com.minimalist.basic.service;\n\nimport com.minimalist.basic.entity.vo.dict.*;\nimport com.minimalist.basic.config.mybatis.bo.PageResp;\n\nimport java.util.List;\n\npublic interface DictService {\n\n    /**\n     * 新增字典\n     * @param dictInfoVO 字典实体\n     */\n    void addDict(DictInfoVO dictInfoVO);\n\n    /**\n     * 删除字典 -> 根据字典ID删除\n     * @param dictId 字典ID\n     */\n    void deleteDictByDictId(Long dictId);\n\n    /**\n     * 删除字典 -> 根据字典类型删除\n     * @param dictType 字典类型\n     */\n    void deleteDictByDictType(String dictType);\n\n    /**\n     * 修改字典\n     * @param dictInfoVO 字典实体\n     */\n    void updateDictByDictId(DictInfoVO dictInfoVO);\n\n    /**\n     * 查询字典列表(分页)\n     * @param queryVO 查询实体\n     * @return 分页字典数据列表\n     */\n    PageResp<DictVO> getPageDictList(DictQueryVO queryVO);\n\n    /**\n     * 根据字典类型查询字典\n     * @param dictType 字典类型\n     * @return 字典数据\n     */\n    DictInfoVO getDictByDictType(String dictType);\n\n    /**\n     * 根据字典类型查询字典\n     * @param dictTypes 字典类型，为空查询所有字典数据\n     * @return 字典数据列表\n     */\n    List<DictCacheVO> getDictList(List<String> dictTypes);\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/service/EDictService.java",
    "content": "package com.minimalist.basic.service;\n\nimport com.minimalist.basic.entity.vo.dict.DictCacheVO;\n\n/**\n * 额外的字典数据，这些数据不会出现在字典管理表中，因为这些数据是来源于其他表\n */\npublic interface EDictService {\n\n    /**\n     * 获取部门字典数据（额外字典数据）\n     * @return 字典数据列表\n     */\n    DictCacheVO getDeptDictData();\n\n    /**\n     * 获取用户字典数据（额外字典数据）\n     * @return 字典数据列表\n     */\n    DictCacheVO getUserDictData();\n\n    /**\n     * 获取租户套餐字典数据（额外字典数据）\n     * @return 字典数据列表\n     */\n    DictCacheVO getTenantPackageDictData();\n\n    /**\n     * 获取角色字典数据（额外字典数据）\n     * @return 字典数据列表\n     */\n    DictCacheVO getRoleDictData();\n\n    /**\n     * 获取岗位字典数据（额外字典数据）\n     * @return 字典数据列表\n     */\n    DictCacheVO getPostDictData();\n\n    /**\n     * 获取租户字典数据（额外字典数据）\n     * @return 字典数据列表\n     */\n    DictCacheVO getTenantDictData();\n\n    /**\n     * 获取存储方式数据（额外字典数据）\n     * @return 字典数据列表\n     */\n    DictCacheVO getStorageDictData();\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/service/FileService.java",
    "content": "package com.minimalist.basic.service;\n\nimport com.minimalist.basic.entity.po.MFile;\nimport com.minimalist.basic.entity.vo.file.*;\nimport com.minimalist.basic.config.mybatis.bo.PageResp;\nimport java.util.List;\n\npublic interface FileService {\n\n    /**\n     * 单文件上传\n     * @param fileUploadVO 文件及携带参数\n     * @return 文件信息\n     */\n    FileUploadRespVO uploadFile(FileUploadVO fileUploadVO);\n\n    /**\n     * 多上传文件\n     * @param uploadBatchVO 文件及携带参数\n     * @return 文件信息\n     */\n    List<FileUploadRespVO> uploadFileBatch(FileUploadBatchVO uploadBatchVO);\n\n    /**\n     * 删除文件\n     * @param fileId 文件ID\n     */\n    void deleteFile(Long fileId);\n\n    /**\n     * 移动文件\n     * 在前端文件选择组件上传文件时不需要指定文件来源，默认会上传到common目录，\n     * 后端处理时可以将文件从common目录移动到对应业务的目录中\n     * @param fileId 文件ID\n     * @param fileSource 文件来源\n     * @return 移动后的文件\n     */\n    MFile moveFile(Long fileId, Integer fileSource);\n\n    /**\n     * 查询文件列表(分页)\n     * @param queryVO 查询条件\n     * @return 文件分页数据\n     */\n    PageResp<FileVO> getPageFileList(FileQueryVO queryVO);\n\n    /**\n     * 移动文件\n     * 用于将前端传递的多个FileVO文件移动，并更新文件信息，已指定fileSource的只更新文件状态\n     * @param files 文件信息\n     * @param fileSource 文件来源\n     * @return 文件ID，逗号分隔\n     */\n    String moveFile(List<FileVO> files, Integer fileSource);\n\n    /**\n     * 移动文件\n     * 用于根据文件url将文件移动，并更新文件信息，已指定fileSource的只更新文件状态\n     * @param fileUrl 文件url\n     * @param fileSource 文件来源\n     * @return 移动后的文件\n     */\n    MFile moveFile(String fileUrl, Integer fileSource);\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/service/NoticeService.java",
    "content": "package com.minimalist.basic.service;\n\nimport com.minimalist.basic.entity.vo.notice.NoticeQueryVO;\nimport com.minimalist.basic.entity.vo.notice.NoticeVO;\nimport com.minimalist.basic.config.mybatis.bo.PageResp;\nimport com.minimalist.basic.config.mybatis.bo.PageReq;\n\npublic interface NoticeService {\n\n    /**\n     * 添加公告\n     * @param noticeVO 公告信息\n     */\n    void addNotice(NoticeVO noticeVO);\n\n    /**\n     * 删除公告 -> 根据公告ID删除\n     * @param noticeId 公告ID\n     */\n    void deleteNoticeByNoticeId(Long noticeId);\n\n    /**\n     * 修改公告 -> 根据公告ID修改\n     * @param noticeVO 公告信息\n     */\n    void updateNoticeByNoticeId(NoticeVO noticeVO);\n\n    /**\n     * 查询公告列表(分页) -> 公告管理使用\n     * @param queryVO 查询条件\n     * @return 公告分页数据\n     */\n    PageResp<NoticeVO> getPageNoticeList(NoticeQueryVO queryVO);\n\n    /**\n     * 根据公告ID查询公告\n     * @param noticeId 公告ID\n     * @return 公告信息\n     */\n    NoticeVO getNoticeByNoticeId(Long noticeId);\n\n    /**\n     * 查询公告列表(分页) -> 首页使用\n     * @return 公告分页数据\n     */\n    PageResp<NoticeVO> getPageHomeNoticeList(PageReq pageReq);\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/service/PermService.java",
    "content": "package com.minimalist.basic.service;\n\nimport com.minimalist.basic.entity.po.MPerms;\nimport com.minimalist.basic.entity.vo.perm.PermQueryVO;\nimport com.minimalist.basic.entity.vo.perm.PermVO;\n\nimport java.util.List;\n\npublic interface PermService {\n\n    /**\n     * 根据角色ID获取权限\n     * @param roleIds 角色ID集合\n     * @return 权限平铺数据\n     */\n    List<MPerms> getPermsByRoleId(List<Long> roleIds);\n\n    /**\n     * 转换权限树\n     * @param permsList 权限数据集合\n     * @return 权限VO数据集合\n     */\n    List<PermVO> permsToTree(List<MPerms> permsList);\n\n    /**\n     * 添加权限\n     * @param permVO 权限数据\n     */\n    void addPerm(PermVO permVO);\n\n    /**\n     * 根据权限ID删除权限\n     * @param permId 权限ID\n     */\n    void deletePermByPermId(Long permId);\n\n    /**\n     * 根据权限ID修改权限\n     * @param permVO 权限数据\n     */\n    void updatePermByPermId(PermVO permVO);\n\n    /**\n     * 查询权限列表（全部不分页）\n     * @param queryVO 查询参数\n     * @return 权限树\n     */\n    List<PermVO> getPermList(PermQueryVO queryVO);\n\n    /**\n     * 查询系统租户权限列表 -> (只获取正常状态的权限)\n     * @return 权限树\n     */\n    List<PermVO> getEnablePermList();\n\n    /**\n     * 查询租户权限列表 -> (只获取正常状态的权限)\n     * @return 权限树\n     */\n    List<PermVO> getTenantEnablePermList();\n\n    /**\n     * 根据权限ID查询权限\n     * @param permId 权限ID\n     * @return 权限数据\n     */\n    PermVO getPermByPermId(Long permId);\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/service/PostService.java",
    "content": "package com.minimalist.basic.service;\n\nimport com.minimalist.basic.entity.vo.post.PostQueryVO;\nimport com.minimalist.basic.entity.vo.post.PostVO;\nimport com.minimalist.basic.config.mybatis.bo.PageResp;\n\nimport java.util.List;\n\npublic interface PostService {\n\n    /**\n     * 添加岗位\n     * @param postVO 岗位数据\n     */\n    void addPost(PostVO postVO);\n\n    /**\n     * 删除岗位 -> 根据岗位ID删除\n     * @param postId 岗位ID\n     */\n    void deletePostByPostId(Long postId);\n\n    /**\n     * 修改岗位 -> 根据岗位ID修改\n     * @param postVO 岗位数据\n     */\n    void updatePostByPostId(PostVO postVO);\n\n    /**\n     * 查询岗位列表(分页)\n     * @param queryVO 查询参数\n     * @return 岗位列表\n     */\n    PageResp<PostVO> getPagePostList(PostQueryVO queryVO);\n\n    /**\n     * 根据岗位ID查询岗位\n     * @param postId 岗位ID\n     * @return 岗位数据\n     */\n    PostVO getPostByPostId(Long postId);\n\n    /**\n     * 根据用户ID查询用户岗位\n     * @param userId 用户ID\n     * @return 岗位列表\n     */\n    List<PostVO> getPostByUserId(Long userId);\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/service/RoleService.java",
    "content": "package com.minimalist.basic.service;\n\nimport com.minimalist.basic.entity.po.MRole;\nimport com.minimalist.basic.entity.vo.role.RoleQueryVO;\nimport com.minimalist.basic.entity.vo.role.RoleVO;\nimport com.minimalist.basic.config.mybatis.bo.PageResp;\n\nimport java.util.List;\n\npublic interface RoleService {\n\n    /**\n     * 根据用户ID查询角色\n     * @param userId 用户ID\n     * @return 角色集合\n     */\n    List<RoleVO> getRolesByUserId(Long userId);\n\n    /**\n     * 添加角色\n     * @param roleVO 角色数据\n     */\n    void addRole(RoleVO roleVO);\n\n    /**\n     * 根据角色ID删除角色\n     * @param roleId 角色ID\n     */\n    void deleteRoleByRoleId(Long roleId);\n\n    /**\n     * 根据角色ID修改角色\n     * @param roleVO 角色数据\n     */\n    void updateRoleByRoleId(RoleVO roleVO);\n\n    /**\n     * 查询角色(分页) -> 角色管理使用\n     * @param queryVO 查询条件\n     * @return 角色分页数据\n     */\n    PageResp<RoleVO> getPageRoleList(RoleQueryVO queryVO);\n\n    /**\n     * 根据角色ID查询角色\n     * @param roleId 角色ID\n     * @return 角色信息\n     */\n    RoleVO getRoleByRoleId(Long roleId);\n\n    /**\n     * 根据租户ID查询角色列表\n     * @param tenantId 租户ID\n     * @return 角色列表\n     */\n    List<MRole> getRoleByTenantId(Long tenantId);\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/service/StorageService.java",
    "content": "package com.minimalist.basic.service;\n\nimport com.minimalist.basic.config.mybatis.bo.PageResp;\nimport com.minimalist.basic.entity.vo.storage.StorageQueryVO;\nimport com.minimalist.basic.entity.vo.storage.StorageVO;\n\npublic interface StorageService {\n\n    /**\n     * 添加存储\n     * @param storageVO 存储信息\n     */\n    void addStorage(StorageVO storageVO);\n\n    /**\n     * 删除存储 -> 根据存储ID删除\n     * @param storageId 存储ID\n     */\n    void deleteStorageByStorageId(Long storageId);\n\n    /**\n     * 修改存储 -> 根据存储ID修改\n     * @param storageVO 存储信息\n     */\n    void updateStorageByStorageId(StorageVO storageVO);\n\n    /**\n     * 查询存储列表(分页)\n     * @param queryVO 查询条件\n     * @return 存储列表\n     */\n    PageResp<StorageVO> getPageStorageList(StorageQueryVO queryVO);\n\n    /**\n     * 根据存储ID查询存储信息\n     * @param storageId 存储ID\n     * @return 存储信息\n     */\n    StorageVO getStorageByStorageId(Long storageId);\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/service/TenantPackageService.java",
    "content": "package com.minimalist.basic.service;\n\nimport com.minimalist.basic.entity.po.MPerms;\nimport com.minimalist.basic.entity.vo.tenant.TenantPackageQueryVO;\nimport com.minimalist.basic.entity.vo.tenant.TenantPackageVO;\nimport com.minimalist.basic.config.mybatis.bo.PageResp;\n\nimport java.util.List;\n\npublic interface TenantPackageService {\n\n    /**\n     * 添加租户套餐\n     * @param tenantPackageVO 租户套餐信息\n     */\n    void addTenantPackage(TenantPackageVO tenantPackageVO);\n\n    /**\n     * 删除租户套餐 -> 根据租户套餐ID删除租户套餐\n     * @param tenantPackageId 租户套餐ID\n     */\n    void deleteTenantPackageByTenantPackageId(Long tenantPackageId);\n\n    /**\n     * 修改租户套餐 -> 根据租户套餐ID修改\n     * @param tenantPackageVO 租户套餐信息\n     */\n    void updateTenantPackageByTenantPackageId(TenantPackageVO tenantPackageVO);\n\n    /**\n     * 查询租户套餐(分页)\n     * @param queryVO 查询条件\n     * @return 租户套餐分页数据\n     */\n    PageResp<TenantPackageVO> getPageTenantPackageList(TenantPackageQueryVO queryVO);\n\n    /**\n     * 根据租户套餐ID查询租户套餐\n     * @param tenantPackageId 租户套餐ID\n     * @return 租户套餐数据\n     */\n    TenantPackageVO getTenantPackageByTenantPackageId(Long tenantPackageId);\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/service/TenantService.java",
    "content": "package com.minimalist.basic.service;\n\nimport com.minimalist.basic.entity.vo.tenant.TenantQueryVO;\nimport com.minimalist.basic.entity.vo.tenant.TenantVO;\nimport com.minimalist.basic.config.mybatis.bo.PageResp;\n\npublic interface TenantService {\n\n    /**\n     * 添加租户\n     * @param tenantVO 租户信息\n     */\n    void addTenant(TenantVO tenantVO);\n\n    /**\n     * 删除租户 -> 根据租户ID删除\n     * @param tenantId 租户ID\n     */\n    void deleteTenantByTenantId(Long tenantId);\n\n    /**\n     * 修改租户 -> 根据租户ID修改\n     * @param tenantVO 租户信息\n     */\n    void updateTenantByTenantId(TenantVO tenantVO);\n\n    /**\n     * 查询租户(分页)\n     * @param queryVO 查询条件\n     * @return 租户分页数据\n     */\n    PageResp<TenantVO> getPageTenantList(TenantQueryVO queryVO);\n\n    /**\n     * 根据租户ID查询租户\n     * @param tenantId 租户ID\n     * @return 租户数据\n     */\n    TenantVO getTenantByTenantId(Long tenantId);\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/service/UserService.java",
    "content": "package com.minimalist.basic.service;\n\nimport cn.dev33.satoken.stp.SaTokenInfo;\nimport com.minimalist.basic.entity.po.MUser;\nimport com.minimalist.basic.entity.vo.user.*;\nimport com.minimalist.basic.config.mybatis.bo.PageResp;\nimport org.springframework.stereotype.Service;\n\n@Service\npublic interface UserService {\n\n    /**\n     * 新增用户\n     * @param userVO 用户实体\n     */\n    void addUser(UserVO userVO);\n\n    /**\n     * 删除用户\n     * @param userId 用户ID\n     */\n    void deleteUserByUserId(Long userId);\n\n    /**\n     * 修改用户\n     * @param userVO 用户数据\n     */\n    void updateUserByUserId(UserVO userVO);\n\n    /**\n     * 查询用户(分页)\n     * @param queryVO 查询条件\n     * @return 用户分页数据\n     */\n    PageResp<UserVO> getPageUserList(UserQueryVO queryVO);\n\n    /**\n     * 根据用户ID查询用户\n     * @param userId 用户ID\n     * @return 用户信息\n     */\n    UserVO getUserByUserId(Long userId);\n\n    /**\n     * 获取用户信息\n     * @return 用户VO\n     */\n    UserInfoVO getUserInfo();\n\n    /**\n     * 获取图形验证码\n     * @return 图形验证码\n     */\n    ImageCaptchaVO getImageCaptcha();\n\n    /**\n     * 校验图形验证码\n     * @param captcha 验证码\n     * @param captchaId 验证码ID\n     * @return true通过校验  false未通过校验\n     */\n    boolean checkImageCaptcha(String captcha, String captchaId);\n\n    /**\n     * 用户登录\n     * @param reqVO 用户登录信息\n     * @return token\n     */\n    SaTokenInfo userLogin(UserLoginReqVO reqVO);\n\n    /**\n     * 重置密码\n     * @param passwordVO 重置密码实体\n     */\n    void resetPassword(RePasswordVO passwordVO);\n\n    /**\n     * 用户设置 -> 修改用户信息\n     * @param settingVO 用户信息\n     */\n    void updateUserInfo(UserSettingVO settingVO);\n\n    /**\n     * 修改用户头像\n     * @param userAvatar 用户头像base64编码\n     */\n    void updateUserAvatar(String userAvatar);\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/service/impl/ConfigServiceImpl.java",
    "content": "package com.minimalist.basic.service.impl;\n\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.util.ObjectUtil;\nimport cn.hutool.json.JSONUtil;\nimport com.minimalist.basic.entity.enums.ConfigEnum;\nimport com.minimalist.basic.entity.enums.RespEnum;\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.config.exception.BusinessException;\nimport com.minimalist.basic.config.mybatis.bo.PageResp;\nimport com.minimalist.basic.config.redis.RedisManager;\nimport com.minimalist.basic.entity.po.MConfig;\nimport com.minimalist.basic.entity.vo.config.ConfigQueryVO;\nimport com.minimalist.basic.entity.vo.config.ConfigVO;\nimport com.minimalist.basic.mapper.MConfigMapper;\nimport com.minimalist.basic.service.ConfigService;\nimport com.minimalist.basic.utils.CommonConstant;\nimport com.minimalist.basic.utils.RedisKeyConstant;\nimport com.minimalist.basic.utils.UnqIdUtil;\nimport com.mybatisflex.core.paginate.Page;\nimport com.mybatisflex.core.query.QueryWrapper;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport java.util.List;\n\n@Service\npublic class ConfigServiceImpl implements ConfigService {\n\n    @Autowired\n    private MConfigMapper configMapper;\n\n    @Autowired\n    private RedisManager redisManager;\n\n    /**\n     * 添加参数\n     * @param configVO 参数配置信息\n     */\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void addConfig(ConfigVO configVO) {\n        //检查键名唯一性\n        MConfig config = configMapper.selectConfigByConfigKey(configVO.getConfigKey(), null);\n        Assert.isNull(config, () -> new BusinessException(ConfigEnum.ErrorMsg.CONTAIN_CONFIG_KEY.getDesc()));\n        MConfig insertConfig = BeanUtil.copyProperties(configVO, MConfig.class);\n        insertConfig.setConfigId(UnqIdUtil.uniqueId());\n        configMapper.insert(insertConfig, true);\n        //发布消息 - 新增\n        redisManager.publishMessage(RedisKeyConstant.SYSTEM_CONFIG_TOPIC_KEY + \".\" + CommonConstant.ADD, JSONUtil.toJsonStr(configVO));\n    }\n\n    /**\n     * 删除参数 -> 根据参数ID删除\n     * @param configId 参数ID\n     */\n    @Override\n    public void deleteConfigByConfigId(Long configId) {\n        MConfig config = configMapper.selectConfigByConfigId(configId);\n        Assert.notNull(config, () -> new BusinessException(ConfigEnum.ErrorMsg.NONENTITY_CONFIG.getDesc()));\n        Assert.isFalse(config.getConfigKey().startsWith(\"system.\"),\n                () -> new BusinessException(ConfigEnum.ErrorMsg.CANNOT_DEL_SYSTEM_CONFIG.getDesc()));\n        int deleteCount = configMapper.deleteConfigByConfigId(configId);\n        Assert.isTrue(deleteCount == 1, () -> new BusinessException(RespEnum.FAILED.getDesc()));\n        //发布消息 - 删除\n        redisManager.publishMessage(RedisKeyConstant.SYSTEM_CONFIG_TOPIC_KEY + \".\" + CommonConstant.DELETE, config.getConfigKey());\n    }\n\n    /**\n     * 修改参数\n     * @param configVO 参数配置信息\n     */\n    @Override\n    public void updateConfigByConfigId(ConfigVO configVO) {\n        //检查键名唯一性\n        MConfig config = configMapper.selectConfigByConfigKey(configVO.getConfigKey(), null);\n        if (ObjectUtil.isNotNull(config) && !configVO.getConfigId().equals(config.getConfigId())) {\n            throw new BusinessException(ConfigEnum.ErrorMsg.CONTAIN_CONFIG_KEY.getDesc());\n        }\n        MConfig updateConfig = BeanUtil.copyProperties(configVO, MConfig.class);\n        configMapper.updateConfigByConfigId(updateConfig);\n        //发布消息 - 修改\n        redisManager.publishMessage(RedisKeyConstant.SYSTEM_CONFIG_TOPIC_KEY + \".\" + CommonConstant.UPDATE, JSONUtil.toJsonStr(configVO));\n    }\n\n    /**\n     * 查询参数配置列表(分页)\n     * @param queryVO 查询条件\n     * @return 参数配置列表\n     */\n    @Override\n    public PageResp<ConfigVO> getPageConfigList(ConfigQueryVO queryVO) {\n        Page<MConfig> configPage = configMapper.selectPageConfigList(queryVO);\n        List<ConfigVO> configVOList = BeanUtil.copyToList(configPage.getRecords(), ConfigVO.class);\n        return new PageResp<>(configVOList, configPage.getTotalRow());\n    }\n\n    /**\n     * 根据参数ID查询参数\n     * @param configId 参数ID\n     * @return 参数信息\n     */\n    @Override\n    public ConfigVO getConfigByConfigId(Long configId) {\n        return BeanUtil.copyProperties(configMapper.selectConfigByConfigId(configId), ConfigVO.class);\n    }\n\n    /**\n     * 根据参数Key查询参数 - 状态为正常\n     * @param configKey 参数Key\n     * @return 参数信息\n     */\n    @Override\n    public ConfigVO getConfigByConfigKey(String configKey) {\n        ConfigVO configVO = CommonConstant.systemConfigMap.get(configKey);\n        if (ObjectUtil.isNull(configVO)) {\n            MConfig mConfig = configMapper.selectConfigByConfigKey(configKey, StatusEnum.STATUS_1.getCode());\n            configVO = BeanUtil.copyProperties(mConfig, ConfigVO.class);\n            //重新放入缓存\n            CommonConstant.systemConfigMap.put(configKey, configVO);\n        }\n        return configVO;\n    }\n\n    /**\n     * 刷新配置缓存\n     */\n    @Override\n    public void refreshConfigCache() {\n        List<MConfig> configList = configMapper.selectListByQuery(\n                QueryWrapper.create().eq(MConfig::getStatus, StatusEnum.STATUS_1.getCode()));\n        for (MConfig config : configList) {\n            ConfigVO configVO = BeanUtil.copyProperties(config, ConfigVO.class);\n            //缓存系统配置到Map\n            CommonConstant.systemConfigMap.put(configVO.getConfigKey(), configVO);\n        }\n    }\n}\n\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/service/impl/DeptServiceImpl.java",
    "content": "package com.minimalist.basic.service.impl;\n\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.collection.CollectionUtil;\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.util.ObjectUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.minimalist.basic.entity.enums.DeptEnum;\nimport com.minimalist.basic.entity.po.MDept;\nimport com.minimalist.basic.entity.po.MUserDept;\nimport com.minimalist.basic.entity.vo.dept.DeptQueryVO;\nimport com.minimalist.basic.entity.vo.dept.DeptVO;\nimport com.minimalist.basic.entity.vo.user.UserQueryVO;\nimport com.minimalist.basic.entity.vo.user.UserVO;\nimport com.minimalist.basic.mapper.MDeptMapper;\nimport com.minimalist.basic.mapper.MUserDeptMapper;\nimport com.minimalist.basic.mapper.MUserMapper;\nimport com.minimalist.basic.service.DeptService;\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.config.exception.BusinessException;\nimport com.minimalist.basic.utils.CommonConstant;\nimport com.minimalist.basic.utils.UnqIdUtil;\nimport com.mybatisflex.core.logicdelete.LogicDeleteManager;\nimport com.mybatisflex.core.paginate.Page;\nimport com.mybatisflex.core.query.QueryWrapper;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.Optional;\n\n@Service\npublic class DeptServiceImpl implements DeptService {\n\n    @Autowired\n    private MUserMapper userMapper;\n\n    @Autowired\n    private MDeptMapper deptMapper;\n\n    @Autowired\n    private MUserDeptMapper userDeptMapper;\n\n    /**\n     * 添加部门\n     * @param deptVO 部门数据\n     */\n    @Override\n    public void addDept(DeptVO deptVO) {\n        MDept mDept = BeanUtil.copyProperties(deptVO, MDept.class);\n        mDept.setDeptId(UnqIdUtil.uniqueId());\n        mDept.setStatus(StatusEnum.STATUS_1.getCode());\n        //如果不是顶级\n        if (CommonConstant.ZERO != deptVO.getParentDeptId()) {\n            MDept parentDept = deptMapper.selectDeptByDeptId(deptVO.getParentDeptId());\n            //祖级列表\n            String ancestors = Optional.ofNullable(parentDept.getAncestors())\n                    .map(a -> a + \",\")\n                    .orElse(\"\") + parentDept.getDeptId();\n            mDept.setAncestors(ancestors);\n        }\n        //新增\n        deptMapper.insert(mDept, true);\n    }\n\n    /**\n     * 删除部门 -> 根据部门ID删除\n     * @param deptId 部门ID\n     */\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void deleteDeptByDeptId(Long deptId) {\n        //检查是否包含下级，包含下级不允许删除\n        long childrenCount = deptMapper.selectChildrenCountByDeptId(deptId);\n        Assert.isFalse(childrenCount > 0, () -> new BusinessException(DeptEnum.ErrorMsg.CONTAIN_CHILDREN.getDesc()));\n        //检查是否有用户在该部门，有用户在该部门不允许删除\n        UserQueryVO query = new UserQueryVO();\n        query.setPageNum(1L);\n        query.setPageSize(1L);\n        query.setDeptId(deptId);\n        Page<UserVO> userVOPage = userMapper.selectPageUserList(query);\n        Assert.isFalse(userVOPage.getTotalRow() > 0, () -> new BusinessException(DeptEnum.ErrorMsg.USER_DEPT_CHILDREN.getDesc()));\n        //删除部门\n        deptMapper.deleteDeptByDeptId(deptId);\n        //删除部门和用户关联关系\n        LogicDeleteManager.execWithoutLogicDelete(()->\n                userDeptMapper.deleteByQuery(QueryWrapper.create().eq(MUserDept::getDeptId, deptId))\n        );\n    }\n\n    /**\n     * 修改部门 -> 根据部门ID修改\n     * @param deptVO 部门数据\n     */\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void updateDeptByDeptId(DeptVO deptVO) {\n        //查询本级部门\n        MDept currentDept = deptMapper.selectDeptByDeptId(deptVO.getDeptId());\n        Assert.notNull(currentDept, () -> new BusinessException(DeptEnum.ErrorMsg.NONENTITY_DEPT.getDesc()));\n        MDept updateDept = BeanUtil.copyProperties(deptVO, MDept.class);\n        //如果不是顶级部门，查询上级部门\n        if (CommonConstant.ZERO != deptVO.getParentDeptId()) {\n            MDept parentDept = deptMapper.selectDeptByDeptId(deptVO.getParentDeptId());\n            //祖级列表\n            String ancestors = Optional.ofNullable(parentDept.getAncestors())\n                    .map(a -> a + \",\")\n                    .orElse(\"\") + parentDept.getDeptId();\n            updateDept.setAncestors(ancestors);\n            //修改当前部门所有下级\n            updateChildrenDeptAncestors(deptVO.getDeptId(), currentDept.getAncestors(), ancestors);\n        } else {\n            //修改当前部门所有下级\n            updateChildrenDeptAncestors(deptVO.getDeptId(), currentDept.getAncestors(), \"\");\n        }\n        deptMapper.updateDeptByDeptId(updateDept);\n    }\n\n    /**\n     * 根据部门ID修改下级部门的祖级\n     * @param deptId 部门ID\n     * @param oldAncestors 旧的祖级\n     * @param newAncestors 新的祖级\n     */\n    private void updateChildrenDeptAncestors(Long deptId, String oldAncestors, String newAncestors) {\n        //修改当前部门所有下级\n        List<MDept> children = deptMapper.selectChildrenDeptByDeptId(deptId);\n        for (MDept child : children) {\n            if (StrUtil.isBlank(oldAncestors)) {\n                if (StrUtil.isNotBlank(newAncestors)) {\n                    child.setAncestors(newAncestors);\n                }\n            } else {\n                child.setAncestors(child.getAncestors().replaceFirst(oldAncestors, newAncestors));\n            }\n            deptMapper.updateDeptByDeptId(child);\n        }\n    }\n\n    /**\n     * 查询部门列表（全部不分页） -> 部门管理使用\n     * @param queryVO 查询参数\n     * @return 部门树\n     */\n    @Override\n    public List<DeptVO> getDeptList(DeptQueryVO queryVO) {\n        return deptToTree(deptMapper.selectDeptList(queryVO));\n    }\n\n    /**\n     * 根据部门ID查询部门\n     * @param deptId 部门ID\n     * @return 部门数据\n     */\n    @Override\n    public DeptVO getDeptByDeptId(Long deptId) {\n        return BeanUtil.copyProperties(deptMapper.selectDeptByDeptId(deptId), DeptVO.class);\n    }\n\n    /**\n     * 查询部门列表 -> 其他地方使用(只获取正常状态的部门)\n     * @return 部门树\n     */\n    @Override\n    public List<DeptVO> getEnableDeptList() {\n        //部门树\n        return deptToTree(deptMapper.selectDeptDict());\n    }\n\n    /**\n     * 根据部门ID查询部门\n     * @param deptIds 部门ID列表\n     * @return 用户所属部门列表\n     */\n    @Override\n    public List<DeptVO> getDeptByDeptIds(List<Long> deptIds) {\n        List<MDept> deptList = deptMapper.selectDeptByDeptIds(deptIds);\n        return BeanUtil.copyToList(deptList, DeptVO.class);\n    }\n\n    /**\n     * 转换部门树\n     * @param deptList 部门数据集合\n     * @return 部门数据集合\n     */\n    private List<DeptVO> deptToTree(List<MDept> deptList) {\n        if (CollectionUtil.isEmpty(deptList)) { return CollectionUtil.list(false); }\n        //查找顶级节点\n        List<DeptVO> rootNodeList = deptList.stream()\n                .filter(p -> ObjectUtil.isNotNull(p.getParentDeptId()))\n                .filter(p -> CommonConstant.ZERO == p.getParentDeptId())\n                .map(p -> BeanUtil.copyProperties(p, DeptVO.class))\n                .sorted(Comparator.comparing(DeptVO::getDeptSort))\n                .toList();\n        //查找子节点\n        findChildren(deptList, rootNodeList);\n        return rootNodeList;\n    }\n\n    /**\n     * 查找子节点\n     * @param deptList 部门数据集合\n     * @param rootNodeList 部门顶级节点数据集合\n     */\n    private void findChildren(List<MDept> deptList, List<DeptVO> rootNodeList) {\n        //遍历顶级节点\n        rootNodeList.forEach(node -> {\n            //存储子节点\n            List<DeptVO> childrenNodes = CollectionUtil.list(false);\n            //从部门数据集合中查找子节点\n            deptList.forEach(p -> {\n                //节点是否关联\n                if (node.getDeptId().equals(p.getParentDeptId())) {\n                    childrenNodes.add(BeanUtil.copyProperties(p, DeptVO.class));\n                }\n                //显示排序\n                childrenNodes.sort(Comparator.comparing(DeptVO::getDeptSort));\n            });\n            //如果有关联的子节点\n            if (CollectionUtil.isNotEmpty(childrenNodes)) {\n                //将查询到的子节点挂在顶级节点上\n                node.setChildren(childrenNodes);\n                //对子节点继续递归，查找子节点的下级\n                findChildren(deptList, childrenNodes);\n            }\n        });\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/service/impl/DictServiceImpl.java",
    "content": "package com.minimalist.basic.service.impl;\n\nimport cn.dev33.satoken.stp.StpUtil;\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.collection.CollectionUtil;\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.map.MapUtil;\nimport cn.hutool.core.util.ObjectUtil;\nimport cn.hutool.core.util.ReflectUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.minimalist.basic.config.eDict.BeanMethod;\nimport com.minimalist.basic.entity.enums.DictEnum;\nimport com.minimalist.basic.entity.po.MDict;\nimport com.minimalist.basic.entity.vo.dict.*;\nimport com.minimalist.basic.mapper.MDictMapper;\nimport com.minimalist.basic.service.DictService;\nimport com.minimalist.basic.service.EDictService;\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.config.exception.BusinessException;\nimport com.minimalist.basic.config.eDict.EDictConstant;\nimport com.minimalist.basic.config.mybatis.bo.PageResp;\nimport com.minimalist.basic.config.redis.RedisManager;\nimport com.minimalist.basic.utils.RedisKeyConstant;\nimport com.minimalist.basic.utils.UnqIdUtil;\nimport com.mybatisflex.core.paginate.Page;\nimport com.mybatisflex.core.query.QueryWrapper;\nimport org.redisson.api.*;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport java.time.LocalDateTime;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.TimeUnit;\nimport java.util.stream.Collectors;\n\n@Service\npublic class DictServiceImpl implements DictService {\n\n    @Autowired\n    private MDictMapper dictMapper;\n\n    @Autowired\n    private RedisManager redisManager;\n\n    @Autowired\n    private RedissonClient redissonClient;\n\n    /**\n     * 新增字典\n     * @param dictInfoVO 字典实体\n     */\n    @Override\n    public void addDict(DictInfoVO dictInfoVO) {\n        //状态默认正常\n        dictInfoVO.getDictDataList().forEach(d -> d.setStatus(StatusEnum.STATUS_1.getCode()));\n        //数据转换，构建字典实体数据\n        List<MDict> dictList = buildDictData(dictInfoVO, true);\n        //批量新增\n        dictMapper.insertBatch(dictList);\n        //缓存处理\n        setDictCacheHandler(CollectionUtil.list(false, dictInfoVO.getDictType()));\n    }\n\n    /**\n     * 删除字典 -> 根据字典ID删除\n     * @param dictId 字典ID\n     */\n    @Override\n    public void deleteDictByDictId(Long dictId) {\n        //查询字典\n        MDict mDict = dictMapper.selectDictByDictId(dictId);\n        Assert.notNull(mDict, () -> new BusinessException(DictEnum.ErrorMsg.NONENTITY_DICT.getDesc()));\n        //删除字典\n        dictMapper.deleteDictByDictId(dictId);\n        //删除缓存\n        String dictCacheKey = StrUtil.indexedFormat(RedisKeyConstant.DICT_CACHE_KEY, mDict.getDictType());\n        redisManager.delete(dictCacheKey);\n    }\n\n    /**\n     * 删除字典 -> 根据字典类型删除\n     * @param dictType 字典类型\n     */\n    @Override\n    public void deleteDictByDictType(String dictType) {\n        //删除字典\n        dictMapper.deleteDictByDictType(dictType);\n        //删除缓存\n        String dictCacheKey = StrUtil.indexedFormat(RedisKeyConstant.DICT_CACHE_KEY, dictType);\n        redisManager.delete(dictCacheKey);\n    }\n\n    /**\n     * 修改字典\n     * @param dictInfoVO 字典实体\n     */\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void updateDictByDictId(DictInfoVO dictInfoVO) {\n        //需要新增的数据 -> 没有dictId的数据\n        List<DictDataVO> dueInsertData = CollectionUtil.list(false);\n        Iterator<DictDataVO> dictDataVOIterator = dictInfoVO.getDictDataList().iterator();\n        while (dictDataVOIterator.hasNext()) {\n            DictDataVO dictDataVO = dictDataVOIterator.next();\n            if (ObjectUtil.isNull(dictDataVO.getDictId())) {\n                dueInsertData.add(dictDataVO);\n                dictDataVOIterator.remove();\n            }\n        }\n        //处理新增的数据\n        if (CollectionUtil.isNotEmpty(dueInsertData)) {\n            List<MDict> dictList = dueInsertData.stream().map(dict -> {\n                MDict mDict = new MDict();\n                mDict.setDictId(UnqIdUtil.uniqueId());\n                mDict.setDictKey(dict.getDictKey());\n                mDict.setDictValue(dict.getDictValue());\n                mDict.setDictOrder(dict.getDictOrder());\n                mDict.setStatus(dict.getStatus());\n                mDict.setDictType(dictInfoVO.getDictType());\n                mDict.setDictName(dictInfoVO.getDictName());\n                mDict.setDictDesc(dictInfoVO.getDictDesc());\n                mDict.setCreateId(StpUtil.getLoginIdAsLong());\n                mDict.setCreateTime(LocalDateTime.now());\n                mDict.setUpdateId(StpUtil.getLoginIdAsLong());\n                mDict.setUpdateTime(LocalDateTime.now());\n                return mDict;\n            }).toList();\n            //批量新增\n            dictMapper.insertBatch(dictList);\n        }\n        //处理修改的数据\n        if (CollectionUtil.isNotEmpty(dictInfoVO.getDictDataList())) {\n            List<MDict> dictList = buildDictData(dictInfoVO, false);\n            for (MDict mDict : dictList) {\n                dictMapper.updateDictByDictId(mDict);\n            }\n        }\n        //缓存处理\n        setDictCacheHandler(CollectionUtil.list(false, dictInfoVO.getDictType()));\n    }\n\n    /**\n     * 查询字典列表(分页)\n     * @param queryVO 查询实体\n     * @return 分页字典数据列表\n     */\n    @Override\n    public PageResp<DictVO> getPageDictList(DictQueryVO queryVO) {\n        Page<DictVO> dictVOPage = dictMapper.selectPageDictList(queryVO);\n        return new PageResp<>(dictVOPage.getRecords(), dictVOPage.getTotalRow());\n    }\n\n    /**\n     * 根据字典类型查询字典\n     * @param dictType 字典类型\n     * @return 字典数据\n     */\n    @Override\n    public DictInfoVO getDictByDictType(String dictType) {\n        //查询字典\n        List<MDict> dictList = dictMapper.selectDictListByDictType(CollectionUtil.list(false, dictType));\n        Assert.notEmpty(dictList, () -> new BusinessException(DictEnum.ErrorMsg.NONENTITY_DICT.getDesc()));\n        //构建返回数据\n        DictInfoVO dictInfoVO = new DictInfoVO();\n        dictInfoVO.setDictName(dictList.get(0).getDictName());\n        dictInfoVO.setDictDesc(dictList.get(0).getDictDesc());\n        dictInfoVO.setDictType(dictList.get(0).getDictType());\n        List<DictDataVO> dictDataVOList = dictList.stream().map(dict -> {\n            DictDataVO dictDataVO = new DictDataVO();\n            dictDataVO.setDictId(dict.getDictId());\n            dictDataVO.setDictKey(dict.getDictKey());\n            dictDataVO.setDictValue(dict.getDictValue());\n            dictDataVO.setDictOrder(dict.getDictOrder());\n            dictDataVO.setDictClass(dict.getDictClass());\n            dictDataVO.setStatus(dict.getStatus());\n            return dictDataVO;\n        }).collect(Collectors.toList());\n        dictInfoVO.setDictDataList(dictDataVOList);\n        return dictInfoVO;\n    }\n\n    /**\n     * 根据字典类型查询字典\n     * @param dictTypes 字典类型，为空查询所有字典数据\n     * @return 字典数据列表\n     */\n    @Override\n    public List<DictCacheVO> getDictList(List<String> dictTypes) {\n        //区分是否要获取额外字典数据\n        Iterator<String> dictTypeIter = dictTypes.iterator();\n        //获取额外字典\n        List<String> extraDictTypeList = CollectionUtil.list(false);\n        while (dictTypeIter.hasNext()) {\n            String dictType = dictTypeIter.next();\n            //如果该字典为额外字典数据\n            if (EDictConstant.dictMethodMap.containsKey(dictType)) {\n                extraDictTypeList.add(dictType);\n                dictTypeIter.remove();\n            }\n        }\n        //返回结果\n        List<DictCacheVO> result = CollectionUtil.list(false);\n        //额外字典获取\n        if (CollectionUtil.isNotEmpty(extraDictTypeList)) {\n            extraDictTypeList.forEach(ed -> {\n                BeanMethod<EDictService> beanMethod = (BeanMethod<EDictService>) EDictConstant.dictMethodMap.get(ed);\n                DictCacheVO dictCacheVO = ReflectUtil.invoke(beanMethod.getBean(), beanMethod.getMethod(), ed);\n                result.add(dictCacheVO);\n            });\n        }\n        //常规字典获取\n        if (CollectionUtil.isNotEmpty(dictTypes)) {\n            //从缓存中获取字典\n            List<DictCacheVO> dictCache = getDictCacheHandler(dictTypes);\n            //检查从缓存获取的字典是否缺失，缺失说明redis中数据过期\n            if (dictTypes.size() != dictCache.size()) {\n                //将缺失的字典type找出来\n                List<String> dueDictTypeList = CollectionUtil.list(false);\n                for (String dictType : dictTypes) {\n                    boolean c = false;\n                    for (DictCacheVO dictCacheVO : dictCache) {\n                        if (dictCacheVO.getDictType().equals(dictType)) {\n                            c = true;\n                            break;\n                        }\n                    }\n                    if (!c) {\n                        dueDictTypeList.add(dictType);\n                    }\n                }\n                //将未获取到的字典，重新获取\n                if (CollectionUtil.isNotEmpty(dueDictTypeList)) {\n                    dictCache.addAll(setDictCacheHandler(dueDictTypeList));\n                }\n            }\n            result.addAll(dictCache);\n        }\n        return result;\n    }\n\n    /**\n     * 添加字典数据到缓存\n     */\n    private List<DictCacheVO> setDictCacheHandler(List<String> dictType) {\n        //返回结果\n        List<DictCacheVO> resultList = CollectionUtil.list(false);\n        //查询数据库中的字典数据\n        List<MDict> mDicts;\n        if (CollectionUtil.isEmpty(dictType)) {\n            //查询全部\n            mDicts = dictMapper.selectListByQuery(QueryWrapper.create()\n                    .eq(MDict::getStatus, StatusEnum.STATUS_1.getCode())\n                    .orderBy(MDict::getDictOrder, true));\n        } else {\n            //按字典类型查询\n            mDicts = dictMapper.selectDictListByDictType(dictType);\n        }\n        if (CollectionUtil.isNotEmpty(mDicts)) {\n            //转Map，key：字典类型，value：字典数据集合\n            Map<String, List<MDict>> dictMap = mDicts.stream().collect(Collectors.groupingBy(MDict::getDictType));\n            //批量存储到缓存\n            RBatch batch = redissonClient.createBatch();\n            dictMap.keySet().forEach(dt -> {\n                List<MDict> dictList = dictMap.get(dt);\n                String dictCacheKey = StrUtil.indexedFormat(RedisKeyConstant.DICT_CACHE_KEY, dt);\n                RBucketAsync<List<DictCacheVO.DictKV>> bucket = batch.getBucket(dictCacheKey);\n                //拷贝\n                List<DictCacheVO.DictKV> dictKVList = BeanUtil.copyToList(dictList, DictCacheVO.DictKV.class);\n                bucket.setAsync(dictKVList, RedisKeyConstant.DICT_CACHE_EX + redisManager.randomSeconds(), TimeUnit.SECONDS);\n                //添加到返回值\n                DictCacheVO dictCacheVO = new DictCacheVO();\n                dictCacheVO.setDictType(dictList.get(0).getDictType());\n                dictCacheVO.setDictList(dictKVList);\n                resultList.add(dictCacheVO);\n            });\n            batch.execute();\n        }\n        return resultList;\n    }\n\n    /**\n     * 从缓存中获取字典数据\n     * @param dictType 字典类型，为空则获取所有字典数据\n     * @return 字典数据列表\n     */\n    private List<DictCacheVO> getDictCacheHandler(List<String> dictType) {\n        List<DictCacheVO> resultList = CollectionUtil.list(false);\n        //获取部分字典数据\n        if (CollectionUtil.isNotEmpty(dictType)) {\n            //批量获取字典缓存\n            RBatch batch = redissonClient.createBatch();\n            dictType.forEach(dt -> {\n                String dictCacheKey = StrUtil.indexedFormat(RedisKeyConstant.DICT_CACHE_KEY, dt);\n                RBucketAsync<List<DictCacheVO.DictKV>> bucket = batch.getBucket(dictCacheKey);\n                bucket.getAsync();\n            });\n            BatchResult<?> batchResult = batch.execute();\n            List<?> responses = batchResult.getResponses();\n            if (CollectionUtil.isNotEmpty(responses)) {\n                responses.forEach(resp -> {\n                    List<DictCacheVO.DictKV> dictVOList = (List<DictCacheVO.DictKV>) resp;\n                    if (CollectionUtil.isNotEmpty(dictVOList)) {\n                        DictCacheVO dictCacheVO = new DictCacheVO();\n                        dictCacheVO.setDictType(dictVOList.get(0).getDictType());\n                        dictCacheVO.setDictList(dictVOList);\n                        resultList.add(dictCacheVO);\n                    }\n                });\n            }\n        } else {\n            //获取全部字典数据\n            RKeys keys = redissonClient.getKeys();\n            String keyPrefix = StrUtil.subBefore(RedisKeyConstant.DICT_CACHE_KEY, \":\", false);\n            Iterable<String> keysByPattern = keys.getKeysByPattern(keyPrefix + \":*\");\n            Set<String> keySet = CollectionUtil.set(false);\n            keysByPattern.forEach(keySet::add);\n            RMap<String, List<DictCacheVO.DictKV>> cacheMap = redissonClient.getMap(keyPrefix);\n            Map<String, List<DictCacheVO.DictKV>> dictCacheMap = cacheMap.getAll(keySet);\n            if (MapUtil.isNotEmpty(dictCacheMap)) {\n                dictCacheMap.values().forEach(dictVOList -> {\n                    DictCacheVO dictCacheVO = new DictCacheVO();\n                    dictCacheVO.setDictType(dictVOList.get(0).getDictType());\n                    dictCacheVO.setDictList(dictVOList);\n                    resultList.add(dictCacheVO);\n                });\n            }\n        }\n        return resultList;\n    }\n\n    /**\n     * 构建字典实体数据\n     * @param dictInfoVO 字典数据\n     * @return 字典实体\n     */\n    private List<MDict> buildDictData(DictInfoVO dictInfoVO, boolean unqId) {\n        return dictInfoVO.getDictDataList().stream()\n                .map(dict -> {\n                    MDict mDict = new MDict();\n                    if (unqId) {\n                        mDict.setDictId(UnqIdUtil.uniqueId());\n                        mDict.setCreateId(StpUtil.getLoginIdAsLong());\n                        mDict.setCreateTime(LocalDateTime.now());\n                    } else {\n                        mDict.setDictId(dict.getDictId());\n                    }\n                    mDict.setDictKey(dict.getDictKey());\n                    mDict.setDictValue(dict.getDictValue());\n                    mDict.setDictOrder(dict.getDictOrder());\n                    mDict.setStatus(dict.getStatus());\n                    mDict.setDictClass(dict.getDictClass());\n                    mDict.setDictType(dictInfoVO.getDictType());\n                    mDict.setDictName(dictInfoVO.getDictName());\n                    mDict.setDictDesc(dictInfoVO.getDictDesc());\n                    mDict.setUpdateId(StpUtil.getLoginIdAsLong());\n                    mDict.setUpdateTime(LocalDateTime.now());\n                    return mDict;\n                }).toList();\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/service/impl/EDictServiceImpl.java",
    "content": "package com.minimalist.basic.service.impl;\n\nimport cn.hutool.core.collection.CollectionUtil;\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.entity.po.*;\nimport com.minimalist.basic.entity.vo.dict.DictCacheVO;\nimport com.minimalist.basic.entity.vo.tenant.TenantVO;\nimport com.minimalist.basic.mapper.*;\nimport com.minimalist.basic.service.EDictService;\nimport com.minimalist.basic.config.eDict.EDict;\nimport com.minimalist.basic.config.eDict.EDictConstant;\nimport com.minimalist.basic.utils.CommonConstant;\nimport com.minimalist.basic.utils.TenantUtil;\nimport com.mybatisflex.core.query.QueryWrapper;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Service;\nimport java.util.List;\n\n/**\n * 额外的字典数据，这些数据不会出现在字典管理表中，因为这些数据是来源于其他表\n */\n@Service\npublic class EDictServiceImpl implements EDictService {\n\n    @Autowired\n    private MDeptMapper deptMapper;\n\n    @Autowired\n    private MUserMapper userMapper;\n\n    @Autowired\n    private MRoleMapper roleMapper;\n\n    @Autowired\n    private MPostMapper postMapper;\n\n    @Autowired\n    private MTenantMapper tenantMapper;\n\n    @Autowired\n    private MTenantPackageMapper tenantPackageMapper;\n\n    @Autowired\n    private MStorageMapper storageMapper;\n\n    /**\n     * 获取部门字典数据\n     * @return 字典数据列表\n     */\n    @Override\n    @EDict(dictType = EDictConstant.DEPT_LIST)\n    public DictCacheVO getDeptDictData() {\n        DictCacheVO dictCacheVO = new DictCacheVO();\n        dictCacheVO.setDictType(EDictConstant.DEPT_LIST);\n        //查询部门列表\n        List<MDept> deptList = deptMapper.selectDeptDict();\n        if (CollectionUtil.isNotEmpty(deptList)) {\n            List<DictCacheVO.DictKV> dictKVList = deptList.stream().map(dept -> {\n                DictCacheVO.DictKV dictKV = new DictCacheVO.DictKV();\n                dictKV.setDictKey(dept.getDeptId().toString());\n                dictKV.setDictValue(dept.getDeptName());\n                dictKV.setDictType(EDictConstant.DEPT_LIST);\n                return dictKV;\n            }).toList();\n            dictCacheVO.setDictList(dictKVList);\n        }\n        return dictCacheVO;\n    }\n\n    /**\n     * 获取用户字典数据\n     * @return 字典数据列表\n     */\n    @Override\n    @EDict(dictType = EDictConstant.USER_LIST)\n    public DictCacheVO getUserDictData() {\n        return getUserDictList(EDictConstant.USER_LIST);\n    }\n\n    /**\n     * 获取租户套餐字典数据（额外字典数据）\n     * @return 字典数据列表\n     */\n    @Override\n    @EDict(dictType = EDictConstant.TENANT_PACKAGE_LIST)\n    public DictCacheVO getTenantPackageDictData() {\n        DictCacheVO dictCacheVO = new DictCacheVO();\n        dictCacheVO.setDictType(EDictConstant.TENANT_PACKAGE_LIST);\n        //查询所有租户套餐\n        List<MTenantPackage> tenantPackageList = tenantPackageMapper.selectTenantPackageDict();\n        if (CollectionUtil.isNotEmpty(tenantPackageList)) {\n            List<DictCacheVO.DictKV> dictKVList = tenantPackageList.stream().map(dept -> {\n                DictCacheVO.DictKV dictKV = new DictCacheVO.DictKV();\n                dictKV.setDictKey(dept.getPackageId().toString());\n                dictKV.setDictValue(dept.getPackageName());\n                dictKV.setDictType(EDictConstant.TENANT_PACKAGE_LIST);\n                return dictKV;\n            }).toList();\n            dictCacheVO.setDictList(dictKVList);\n        }\n        return dictCacheVO;\n    }\n\n    /**\n     * 获取角色字典数据\n     * @return 字典数据列表\n     */\n    @Override\n    @EDict(dictType = EDictConstant.ROLE_LIST)\n    public DictCacheVO getRoleDictData() {\n        DictCacheVO dictCacheVO = new DictCacheVO();\n        dictCacheVO.setDictType(EDictConstant.ROLE_LIST);\n        //查询角色列表\n        List<MRole> roleList = roleMapper.selectRoleDict();\n        if (CollectionUtil.isNotEmpty(roleList)) {\n            List<DictCacheVO.DictKV> dictKVList = roleList.stream().map(role -> {\n                DictCacheVO.DictKV dictKV = new DictCacheVO.DictKV();\n                dictKV.setDictKey(role.getRoleId().toString());\n                dictKV.setDictValue(role.getRoleName());\n                dictKV.setDictType(EDictConstant.ROLE_LIST);\n                return dictKV;\n            }).toList();\n            dictCacheVO.setDictList(dictKVList);\n        }\n        return dictCacheVO;\n    }\n\n    /**\n     * 获取岗位字典数据（额外字典数据）\n     * @return 字典数据列表\n     */\n    @Override\n    @EDict(dictType = EDictConstant.POST_LIST)\n    public DictCacheVO getPostDictData() {\n        DictCacheVO dictCacheVO = new DictCacheVO();\n        dictCacheVO.setDictType(EDictConstant.POST_LIST);\n        //查询岗位列表\n        List<MPost> postList = postMapper.selectPostDict();\n        if (CollectionUtil.isNotEmpty(postList)) {\n            List<DictCacheVO.DictKV> dictKVList = postList.stream().map(post -> {\n                DictCacheVO.DictKV dictKV = new DictCacheVO.DictKV();\n                dictKV.setDictKey(post.getPostId().toString());\n                dictKV.setDictValue(post.getPostName());\n                dictKV.setDictType(EDictConstant.POST_LIST);\n                return dictKV;\n            }).toList();\n            dictCacheVO.setDictList(dictKVList);\n        }\n        return dictCacheVO;\n    }\n\n    /**\n     * 获取租户字典数据（额外字典数据）\n     * @return 字典数据列表\n     */\n    @Override\n    @EDict(dictType = EDictConstant.TENANT_LIST)\n    public DictCacheVO getTenantDictData() {\n        DictCacheVO dictCacheVO = new DictCacheVO();\n        dictCacheVO.setDictType(EDictConstant.TENANT_LIST);\n        //查询租户列表\n        List<MTenant> tenantList = tenantMapper.selectTenantDict();\n        if (CollectionUtil.isNotEmpty(tenantList)) {\n            List<DictCacheVO.DictKV> dictKVList = tenantList.stream()\n                    .map(tenant -> {\n                DictCacheVO.DictKV dictKV = new DictCacheVO.DictKV();\n                dictKV.setDictKey(tenant.getTenantId().toString());\n                dictKV.setDictValue(tenant.getTenantName());\n                dictKV.setDictType(EDictConstant.TENANT_LIST);\n                return dictKV;\n            }).toList();\n            dictCacheVO.setDictList(dictKVList);\n        }\n        return dictCacheVO;\n    }\n\n    /**\n     * 获取存储方式数据（额外字典数据）\n     * @return 字典数据列表\n     */\n    @Override\n    @EDict(dictType = EDictConstant.STORAGE_LIST)\n    public DictCacheVO getStorageDictData() {\n        DictCacheVO dictCacheVO = new DictCacheVO();\n        dictCacheVO.setDictType(EDictConstant.STORAGE_LIST);\n        //系统租户，查询全部存储\n        if (TenantUtil.checkIsSystemTenant()) {\n            List<MStorage> mStorages = storageMapper.selectListByQuery(QueryWrapper.create()\n                    .eq(MStorage::getStatus, StatusEnum.STATUS_1.getCode()));\n            List<DictCacheVO.DictKV> dictKVList = mStorages.stream()\n                    .map(storage -> {\n                        DictCacheVO.DictKV dictKV = new DictCacheVO.DictKV();\n                        dictKV.setDictKey(storage.getStorageId().toString());\n                        dictKV.setDictValue(storage.getStorageName());\n                        dictKV.setDictType(EDictConstant.STORAGE_LIST);\n                        return dictKV;\n                    }).toList();\n            dictCacheVO.setDictList(dictKVList);\n        } else {\n            //普通租户，查询租户自己的存储\n            TenantVO tenantVO = CommonConstant.tenantMap.get(TenantUtil.getTenantId());\n            MStorage storage = storageMapper.selectStorageByStorageId(tenantVO.getStorageId());\n            List<DictCacheVO.DictKV> dictKVList = CollectionUtil.list(false);\n            DictCacheVO.DictKV dictKV = new DictCacheVO.DictKV();\n            dictKV.setDictKey(storage.getStorageId().toString());\n            dictKV.setDictValue(storage.getStorageName());\n            dictKV.setDictType(EDictConstant.STORAGE_LIST);\n            dictKVList.add(dictKV);\n            dictCacheVO.setDictList(dictKVList);\n        }\n        return dictCacheVO;\n    }\n\n    /**\n     * 获取用户字典数据\n     * @param dictType 字典类型\n     * @return 字典数据\n     */\n    private DictCacheVO getUserDictList(String dictType) {\n        //返回结果\n        DictCacheVO dictCacheVO = new DictCacheVO();\n        dictCacheVO.setDictType(dictType);\n        //查询用户列表\n        List<MUser> userList = userMapper.selectUserDict();\n        if (CollectionUtil.isNotEmpty(userList)) {\n            List<DictCacheVO.DictKV> dictKVList = userList.stream().map(dept -> {\n                DictCacheVO.DictKV dictKV = new DictCacheVO.DictKV();\n                dictKV.setDictKey(dept.getUserId().toString());\n                dictKV.setDictValue(dept.getNickname());\n                dictKV.setDictType(dictType);\n                return dictKV;\n            }).toList();\n            dictCacheVO.setDictList(dictKVList);\n        }\n        return dictCacheVO;\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/service/impl/FileServiceImpl.java",
    "content": "package com.minimalist.basic.service.impl;\n\nimport cn.dev33.satoken.stp.StpUtil;\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.collection.CollectionUtil;\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.util.ObjectUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.minimalist.basic.config.exception.BusinessException;\nimport com.minimalist.basic.config.fileHandler.FileManager;\nimport com.minimalist.basic.config.fileHandler.handler.FileHandler;\nimport com.minimalist.basic.entity.enums.FileEnum;\nimport com.minimalist.basic.entity.enums.StorageEnum;\nimport com.minimalist.basic.config.mybatis.bo.PageResp;\nimport com.minimalist.basic.entity.enums.TenantEnum;\nimport com.minimalist.basic.entity.po.MFile;\nimport com.minimalist.basic.entity.po.MStorage;\nimport com.minimalist.basic.entity.po.MTenant;\nimport com.minimalist.basic.entity.vo.file.*;\nimport com.minimalist.basic.mapper.MFileMapper;\nimport com.minimalist.basic.mapper.MStorageMapper;\nimport com.minimalist.basic.mapper.MTenantMapper;\nimport com.minimalist.basic.service.FileService;\nimport com.minimalist.basic.utils.TenantUtil;\nimport com.mybatisflex.core.logicdelete.LogicDeleteManager;\nimport com.mybatisflex.core.paginate.Page;\nimport com.mybatisflex.core.query.QueryWrapper;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Service;\nimport org.springframework.web.multipart.MultipartFile;\nimport java.time.LocalDateTime;\nimport java.util.List;\nimport java.util.StringJoiner;\n\n@Service\npublic class FileServiceImpl implements FileService {\n\n    @Autowired\n    private MFileMapper fileMapper;\n\n    @Autowired\n    private MStorageMapper storageMapper;\n\n    @Autowired\n    private FileManager fileManager;\n\n    @Autowired\n    private MTenantMapper tenantMapper;\n\n    /**\n     * 单文件上传\n     * @param fileUploadVO 文件及携带参数\n     * @return 文件信息\n     */\n    @Override\n    public FileUploadRespVO uploadFile(FileUploadVO fileUploadVO) {\n        MStorage storage = getStorage(fileUploadVO.getStorageId());\n        Assert.notNull(storage, () -> new BusinessException(StorageEnum.ErrorMsg.NONENTITY_STORAGE.getDesc()));\n        FileHandler fileHandler = fileManager.getFileHandler(storage.getStorageType());\n        //上传文件\n        MFile mFile = fileHandler.uploadFile(fileUploadVO.getFile(), fileUploadVO.getFileSource(), storage);\n        fileMapper.insert(mFile, true);\n        return BeanUtil.copyProperties(mFile, FileUploadRespVO.class);\n    }\n\n    /**\n     * 多上传文件\n     * @param uploadBatchVO 文件及携带参数\n     * @return 文件信息\n     */\n    @Override\n    public List<FileUploadRespVO> uploadFileBatch(FileUploadBatchVO uploadBatchVO) {\n        MStorage storage = getStorage(uploadBatchVO.getStorageId());\n        Assert.notNull(storage, () -> new BusinessException(StorageEnum.ErrorMsg.NONENTITY_STORAGE.getDesc()));\n        FileHandler fileHandler = fileManager.getFileHandler(storage.getStorageType());\n        //循环上传多个文件\n        List<MFile> files = CollectionUtil.list(false);\n        for (MultipartFile file : uploadBatchVO.getFiles()) {\n            MFile mFile = fileHandler.uploadFile(file, uploadBatchVO.getFileSource(), storage);\n            files.add(mFile);\n        }\n        //批量插入文件信息\n        fileMapper.insertBatch(files);\n        return BeanUtil.copyToList(files, FileUploadRespVO.class);\n    }\n\n    /**\n     * 删除文件\n     * @param fileId 文件ID\n     */\n    @Override\n    public void deleteFile(Long fileId) {\n        //查询文件\n        MFile mFile = fileMapper.selectFileByFileId(fileId);\n        Assert.notNull(mFile, () -> new BusinessException(FileEnum.ErrorMsg.NONENTITY_FILE.getDesc()));\n        MStorage storage = getStorage(mFile.getStorageId());\n        FileHandler fileHandler = fileManager.getFileHandler(storage.getStorageType());\n        //删除文件\n        boolean deleteFile = fileHandler.deleteFile(mFile, storage);\n        if (!deleteFile) {\n            throw new BusinessException(FileEnum.ErrorMsg.FILE_DELETE_FAIL.getDesc());\n        }\n        //删除数据库文件记录\n        LogicDeleteManager.execWithoutLogicDelete(()->\n                fileMapper.deleteByQuery(QueryWrapper.create().eq(MFile::getFileId, fileId))\n        );\n    }\n\n    /**\n     * 移动文件\n     * 在前端文件选择组件上传文件时不需要指定文件来源，默认会上传到common目录，\n     * 后端处理时可以将文件从common目录移动到对应业务的目录中\n     * @param fileId     文件ID\n     * @param fileSource 文件来源\n     * @return 移动后的文件\n     */\n    @Override\n    public MFile moveFile(Long fileId, Integer fileSource) {\n        //查询文件\n        MFile file = fileMapper.selectFileByFileId(fileId);\n        Assert.notNull(file, () -> new BusinessException(FileEnum.ErrorMsg.NONENTITY_FILE.getDesc()));\n        MStorage storage = getStorage(file.getStorageId());\n        //设置文件来源 - 在文件选择组件中上传的文件没有文件来源，所以这里要设置\n        file.setFileSource(fileSource);\n        //修改文件信息\n        file.setUpdateId(StpUtil.getLoginIdAsLong());\n        file.setUpdateTime(LocalDateTime.now());\n        //移动文件\n        FileHandler fileHandler = fileManager.getFileHandler(storage.getStorageType());\n        fileHandler.moveFile(file, storage);\n        //更新文件信息\n        fileMapper.updateByQuery(file, QueryWrapper.create().eq(MFile::getFileId, fileId));\n        return file;\n    }\n\n    /**\n     * 查询文件列表(分页)\n     * @param queryVO 查询条件\n     * @return 文件分页数据\n     */\n    @Override\n    public PageResp<FileVO> getPageFileList(FileQueryVO queryVO) {\n        Page<MFile> filePage = fileMapper.selectPageFileList(queryVO);\n        //数据转换\n        List<FileVO> fileVOList = CollectionUtil.list(false);\n        for (MFile record : filePage.getRecords()) {\n            FileVO fileVO = BeanUtil.copyProperties(record, FileVO.class);\n            //文件类型后缀\n            if (StrUtil.isNotBlank(fileVO.getFileType())) {\n                String ft = StrUtil.subAfter(fileVO.getFileType(), \"/\", true);\n                fileVO.setFileTypeSuffix(ft);\n            }\n            fileVOList.add(fileVO);\n        }\n        return new PageResp<>(fileVOList, filePage.getTotalRow());\n    }\n\n    /**\n     * 移动文件\n     * 用于将前端传递的多个FileVO文件移动，并更新文件信息，已指定fileSource的只更新文件状态\n     * @param files 文件信息\n     * @param fileSource 文件来源\n     * @return 文件ID，逗号分隔\n     */\n    @Override\n    public String moveFile(List<FileVO> files, Integer fileSource) {\n        StringJoiner fileIds = new StringJoiner(\",\");\n        //处理公告封面图片\n        if (CollectionUtil.isNotEmpty(files)) {\n            for (FileVO fileVO : files) {\n                //已指定文件来源 - 跳过\n                if (ObjectUtil.isNotNull(fileVO.getFileSource()) && fileVO.getFileSource() != -1) {\n                    //文件ID，逗号分隔\n                    fileIds.add(fileVO.getFileId().toString());\n                    continue;\n                }\n                //移动文件到对应的目录\n                MFile newFile = moveFile(fileVO.getFileId(), fileSource);\n                //文件ID，逗号分隔\n                fileIds.add(fileVO.getFileId().toString());\n            }\n        }\n        return fileIds.toString();\n    }\n\n    /**\n     * 移动文件\n     * 用于根据文件url将文件移动，并更新文件信息，已指定fileSource的只更新文件状态\n     * @param fileUrl    文件url\n     * @param fileSource 文件来源\n     * @return 移动后的文件\n     */\n    @Override\n    public MFile moveFile(String fileUrl, Integer fileSource) {\n        //从url中提取文件名\n        String cleanUrl = StrUtil.subBefore(fileUrl, \"?\", true);\n        String fileName = StrUtil.subAfter(cleanUrl, \"/\", true);\n        //根据文件名查询文件\n        MFile file = fileMapper.selectOneByQuery(QueryWrapper.create().eq(MFile::getNewFileName, fileName));\n        //未查询到文件，跳过\n        if (ObjectUtil.isNull(file)) {\n            return null;\n        }\n        //已指定文件来源 - 跳过\n        if (ObjectUtil.isNotNull(file.getFileSource()) && file.getFileSource() != -1) {\n            return null;\n        }\n        //移动文件到对应的目录\n        return moveFile(file.getFileId(), fileSource);\n    }\n\n    /**\n     * 获取存储信息\n     * @param storageId 存储ID\n     * @return 存储信息\n     */\n    private MStorage getStorage(Long storageId) {\n        if (ObjectUtil.isNotNull(storageId)) {\n            return storageMapper.selectStorageByStorageId(storageId);\n        }\n        //未指定存储，根据租户ID获取\n        MTenant tenant = tenantMapper.selectTenantByTenantId(TenantUtil.getTenantId());\n        Assert.notNull(tenant, () -> new BusinessException(TenantEnum.ErrorMsg.QUERY_NULL_TENANT.getDesc()));\n        return storageMapper.selectStorageByStorageId(tenant.getStorageId());\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/service/impl/NoticeServiceImpl.java",
    "content": "package com.minimalist.basic.service.impl;\n\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.collection.CollectionUtil;\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.util.ObjectUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.baomidou.dynamic.datasource.annotation.DSTransactional;\nimport com.minimalist.basic.entity.enums.FileEnum;\nimport com.minimalist.basic.entity.enums.NoticeEnum;\nimport com.minimalist.basic.entity.po.MFile;\nimport com.minimalist.basic.entity.po.MNotice;\nimport com.minimalist.basic.entity.vo.file.FileVO;\nimport com.minimalist.basic.entity.vo.notice.NoticeQueryVO;\nimport com.minimalist.basic.entity.vo.notice.NoticeVO;\nimport com.minimalist.basic.mapper.MFileMapper;\nimport com.minimalist.basic.mapper.MNoticeMapper;\nimport com.minimalist.basic.service.FileService;\nimport com.minimalist.basic.service.NoticeService;\nimport com.minimalist.basic.config.exception.BusinessException;\nimport com.minimalist.basic.config.mybatis.bo.PageResp;\nimport com.minimalist.basic.config.mybatis.bo.PageReq;\nimport com.minimalist.basic.utils.TextUtil;\nimport com.minimalist.basic.utils.UnqIdUtil;\nimport com.mybatisflex.core.paginate.Page;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Service;\nimport java.time.LocalDateTime;\nimport java.util.*;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\n@Slf4j\n@Service\npublic class NoticeServiceImpl implements NoticeService {\n\n    @Autowired\n    private MNoticeMapper noticeMapper;\n\n    @Autowired\n    private MFileMapper fileMapper;\n\n    @Autowired\n    private FileService fileService;\n\n    /**\n     * 添加公告\n     * @param noticeVO 公告信息\n     */\n    @Override\n    @DSTransactional(rollbackFor = Exception.class)\n    public void addNotice(NoticeVO noticeVO) {\n        MNotice mNotice = BeanUtil.copyProperties(noticeVO, MNotice.class);\n        //公告ID\n        long noticeId = UnqIdUtil.uniqueId();\n        mNotice.setNoticeId(noticeId);\n        //延期发布处理\n        if (ObjectUtil.isNotNull(noticeVO.getNoticeTimeInterval())) {\n            //延期发布 -> 发布时间置为延期发布的时间\n            mNotice.setPublishTime(noticeVO.getNoticeTimeInterval());\n        } else {\n            //不延期 -> 发布时间置为当前时间\n            mNotice.setPublishTime(LocalDateTime.now());\n        }\n        //公告相关文件处理\n        String fileIds = saveNoticeFileHandler(noticeVO);\n        mNotice.setNoticePicFileId(fileIds);\n        //内容处理 -> 编码\n        mNotice.setNoticeContent(TextUtil.encode(noticeVO.getNoticeContent()));\n        noticeMapper.insert(mNotice, true);\n    }\n\n    /**\n     * 删除公告 -> 根据公告ID删除\n     * @param noticeId 公告ID\n     */\n    @Override\n    @DSTransactional(rollbackFor = Exception.class)\n    public void deleteNoticeByNoticeId(Long noticeId) {\n        //查询公告\n        MNotice mNotice = noticeMapper.selectNoticeByNoticeId(noticeId);\n        Assert.notNull(mNotice, () -> new BusinessException(NoticeEnum.ErrorMsg.NONENTITY_NOTICE.getDesc()));\n        //删除公告\n        noticeMapper.deleteNoticeByNoticeId(noticeId);\n    }\n\n    /**\n     * 修改公告 -> 根据公告ID修改\n     * @param noticeVO 公告信息\n     */\n    @Override\n    @DSTransactional(rollbackFor = Exception.class)\n    public void updateNoticeByNoticeId(NoticeVO noticeVO) {\n        //查询公告\n        MNotice mNotice = noticeMapper.selectNoticeByNoticeId(noticeVO.getNoticeId());\n        Assert.notNull(mNotice, () -> new BusinessException(NoticeEnum.ErrorMsg.NONENTITY_NOTICE.getDesc()));\n        MNotice newNotice = BeanUtil.copyProperties(noticeVO, MNotice.class);\n        //延期发布处理\n        if (ObjectUtil.isNotNull(noticeVO.getNoticeTimeInterval())) {\n            //延期发布 -> 发布时间置为延期发布的时间\n            newNotice.setPublishTime(noticeVO.getNoticeTimeInterval());\n        } else {\n            //不延期 -> 不处理，不修改发布时间\n        }\n        //公告相关文件处理\n        String fileIds = saveNoticeFileHandler(noticeVO);\n        newNotice.setNoticePicFileId(fileIds);\n        //内容处理 -> 编码\n        newNotice.setNoticeContent(TextUtil.encode(noticeVO.getNoticeContent()));\n        //修改\n        noticeMapper.updateNoticeByNoticeId(newNotice);\n    }\n\n    /**\n     * 查询公告列表(分页) -> 公告管理使用\n     * @param queryVO 查询条件\n     * @return 公告分页数据\n     */\n    @Override\n    public PageResp<NoticeVO> getPageNoticeList(NoticeQueryVO queryVO) {\n        //分页查询\n        Page<MNotice> mNoticePage = noticeMapper.selectPageNoticeList(queryVO);\n        //数据转换\n        List<NoticeVO> noticeVOS = BeanUtil.copyToList(mNoticePage.getRecords(), NoticeVO.class);\n        //汇总封面图文件ID\n        List<Long> noticePicFileIdList = noticeVOS.stream().filter(n -> StrUtil.isNotBlank(n.getNoticePicFileId()))\n                .flatMap(n -> {\n                    List<Long> fileIdList = TextUtil.splitAndListStrToListLong(n.getNoticePicFileId());\n                    return Arrays.stream(fileIdList.toArray(Long[]::new));\n                }).toList();\n        if (CollectionUtil.isNotEmpty(noticePicFileIdList)) {\n            //根据文件ID查询文件\n            List<MFile> fileList = fileMapper.selectFileByFileIds(noticePicFileIdList);\n            //将文件按照URL转Map，key：文件ID，value：文件实体\n            Map<Long, MFile> fileMap = fileList.stream().collect(Collectors.toMap(MFile::getFileId, Function.identity(), (v1, v2) -> v1));\n            noticeVOS.forEach(n -> {\n                //内容清空\n                n.setNoticeContent(null);\n                //封面图文件处理\n                if (StrUtil.isNotBlank(n.getNoticePicFileId())) {\n                    List<Long> fileIdList = TextUtil.splitAndListStrToListLong(n.getNoticePicFileId());\n                    List<FileVO> fileVOList = CollectionUtil.list(false);\n                    fileIdList.forEach(fileId -> {\n                        if (fileMap.containsKey(fileId)) {\n                            MFile mFile = fileMap.get(fileId);\n                            fileVOList.add(BeanUtil.copyProperties(mFile, FileVO.class));\n                        }\n                    });\n                    //设置封面图文件\n                    n.setNoticePicFile(fileVOList);\n                }\n            });\n        } else {\n            //将内容清空，因为列表不需要展示内容\n            noticeVOS.forEach(n -> n.setNoticeContent(null));\n        }\n        return new PageResp<>(noticeVOS, mNoticePage.getTotalRow());\n    }\n\n    /**\n     * 根据公告ID查询公告\n     * @param noticeId 公告ID\n     * @return 公告信息\n     */\n    @Override\n    public NoticeVO getNoticeByNoticeId(Long noticeId) {\n        MNotice mNotice = noticeMapper.selectNoticeByNoticeId(noticeId);\n        //内容 -> 解码\n        mNotice.setNoticeContent(TextUtil.decode(mNotice.getNoticeContent()));\n        NoticeVO noticeVO = BeanUtil.copyProperties(mNotice, NoticeVO.class);\n        //查询公告封面图\n        if (StrUtil.isNotBlank(mNotice.getNoticePicFileId())) {\n            List<Long> noticePicFileIdList = TextUtil.splitAndListStrToListLong(mNotice.getNoticePicFileId());\n            List<MFile> mFiles = fileMapper.selectFileByFileIds(noticePicFileIdList);\n            List<FileVO> fileVOList = BeanUtil.copyToList(mFiles, FileVO.class);\n            noticeVO.setNoticePicFile(fileVOList);\n        }\n        return noticeVO;\n    }\n\n    /**\n     * 查询公告列表(分页) -> 首页使用\n     * @return 公告分页数据\n     */\n    @Override\n    public PageResp<NoticeVO> getPageHomeNoticeList(PageReq pageReq) {\n        //分页查询\n        Page<MNotice> mNoticePage = noticeMapper.selectHomePageNoticeList(pageReq);\n        //数据转换\n        List<NoticeVO> noticeVOS = BeanUtil.copyToList(mNoticePage.getRecords(), NoticeVO.class);\n        //将内容清空，因为列表不需要展示内容\n        noticeVOS.forEach(n -> n.setNoticeContent(null));\n        return new PageResp<>(noticeVOS, mNoticePage.getTotalRow());\n    }\n\n    /**\n     * 公告文件的处理\n     * @param newNotice 公告信息\n     * @return 封面图文件ID\n     */\n    private String saveNoticeFileHandler(NoticeVO newNotice) {\n        //处理富文本中的图片 - 从富文本内容中提取文件名\n        Set<String> fileUrls = TextUtil.extractFileUrl(newNotice.getNoticeContent());\n        for (String fileUrl : fileUrls) {\n            MFile newFile = fileService.moveFile(fileUrl, FileEnum.FileSource.NOTICE_CONTENT_IMG.getCode());\n            if (ObjectUtil.isNull(newFile)) {\n                continue;\n            }\n            //将新的url替换富文本内容中的旧url\n            String replace = StrUtil.replace(newNotice.getNoticeContent(), fileUrl, newFile.getFileUrl());\n            newNotice.setNoticeContent(replace);\n        }\n        //处理公告封面图片-图片文件ID，逗号分隔\n        String fileIds = fileService.moveFile(newNotice.getNoticePicFile(),\n                FileEnum.FileSource.NOTICE_COVER_IMG.getCode());\n        newNotice.setNoticePicFileId(fileIds);\n        return fileIds;\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/service/impl/PermServiceImpl.java",
    "content": "package com.minimalist.basic.service.impl;\n\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.collection.CollectionUtil;\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.util.ObjectUtil;\nimport com.minimalist.basic.entity.enums.PermEnum;\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.entity.po.*;\nimport com.minimalist.basic.entity.vo.perm.PermQueryVO;\nimport com.minimalist.basic.entity.vo.perm.PermVO;\nimport com.minimalist.basic.mapper.*;\nimport com.minimalist.basic.service.PermService;\nimport com.minimalist.basic.config.exception.BusinessException;\nimport com.minimalist.basic.utils.CommonConstant;\nimport com.minimalist.basic.utils.TenantUtil;\nimport com.minimalist.basic.utils.UnqIdUtil;\nimport com.mybatisflex.core.query.QueryWrapper;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Service;\nimport java.util.Comparator;\nimport java.util.List;\n\n@Service\npublic class PermServiceImpl implements PermService {\n\n    @Autowired\n    private MPermsMapper permsMapper;\n\n    @Autowired\n    private MRolePermMapper rolePermMapper;\n\n    @Autowired\n    private MTenantMapper tenantMapper;\n\n    @Autowired\n    private MTenantPackagePermMapper tenantPackagePermMapper;\n\n    /**\n     * 根据角色ID获取权限\n     * @param roleIds 角色ID集合\n     * @return 权限平铺数据\n     */\n    @Override\n    public List<MPerms> getPermsByRoleId(List<Long> roleIds) {\n        //查询角色与权限关联关系\n        List<MRolePerm> rolePermRelation = rolePermMapper.selectRolePermByRoleIds(roleIds);\n        if (CollectionUtil.isEmpty(rolePermRelation)) {\n            return CollectionUtil.list(false);\n        }\n        //权限ids\n        List<Long> permsIds = rolePermRelation.stream().map(MRolePerm::getPermId).distinct().toList();\n        //返回权限平铺数据\n        return permsMapper.selectPermsByPermsIds(permsIds);\n    }\n\n    /**\n     * 转换权限树\n     * @param permsList 权限数据集合\n     * @return 权限数据集合\n     */\n    public List<PermVO> permsToTree(List<MPerms> permsList) {\n        if (CollectionUtil.isEmpty(permsList)) {\n            return CollectionUtil.list(false);\n        }\n        //查找顶级节点\n        List<PermVO> rootNodeList = permsList.stream()\n                .filter(p -> ObjectUtil.isNotNull(p.getParentPermId()))\n                .filter(p -> CommonConstant.ZERO == p.getParentPermId())\n                .map(p -> BeanUtil.copyProperties(p, PermVO.class))\n                .sorted(Comparator.comparing(PermVO::getPermSort))\n                .toList();\n        //查找子节点\n        findChildren(permsList, rootNodeList);\n        return rootNodeList;\n    }\n\n    /**\n     * 添加权限\n     * @param permVO 权限数据\n     */\n    @Override\n    public void addPerm(PermVO permVO) {\n        MPerms mPerms = BeanUtil.copyProperties(permVO, MPerms.class);\n        mPerms.setPermId(UnqIdUtil.uniqueId());\n        permsMapper.insert(mPerms, true);\n    }\n\n    /**\n     * 删除权限 -> 根据权限ID删除\n     * @param permId 权限ID\n     */\n    @Override\n    public void deletePermByPermId(Long permId) {\n        //检查是否包含下级，包含下级不允许删除\n        long childrenCount = permsMapper.selectChildrenCountByPermId(permId);\n        Assert.isFalse(childrenCount > 0, () -> new BusinessException(PermEnum.ErrorMsg.CONTAIN_CHILDREN.getDesc()));\n        permsMapper.deletePermsByPermId(permId);\n    }\n\n    /**\n     * 根据权限ID修改权限\n     * @param permVO 权限数据\n     */\n    @Override\n    public void updatePermByPermId(PermVO permVO) {\n        MPerms newPerms = BeanUtil.copyProperties(permVO, MPerms.class);\n        permsMapper.updatePermsByPermId(newPerms);\n    }\n\n    /**\n     * 查询权限列表（全部不分页）\n     * @param queryVO 查询参数\n     * @return 权限树\n     */\n    @Override\n    public List<PermVO> getPermList(PermQueryVO queryVO) {\n        return permsToTree(permsMapper.selectPermList(queryVO));\n    }\n\n    /**\n     * 查询系统租户权限列表（只查询启用的权限）\n     * @return 权限树\n     */\n    @Override\n    public List<PermVO> getEnablePermList() {\n        QueryWrapper queryWrapper = QueryWrapper.create().eq(MPerms::getStatus, StatusEnum.STATUS_1.getCode());\n        List<MPerms> enablePermList = permsMapper.selectListByQuery(queryWrapper);\n        return permsToTree(enablePermList);\n    }\n\n    /**\n     * 查询租户权限列表 -> (只获取正常状态的权限)\n     * @return 权限树\n     */\n    @Override\n    public List<PermVO> getTenantEnablePermList() {\n        //获取要操作的租户ID\n        long tenantId = TenantUtil.getTenantId();\n        //如果是系统租户，查询全部\n        if (CommonConstant.ZERO == tenantId) {\n            return getEnablePermList();\n        }\n        //查询租户及套餐权限\n        MTenant tenant = tenantMapper.selectTenantByTenantId(tenantId);\n        List<MTenantPackagePerm> tenantPackagePerms = tenantPackagePermMapper.selectTenantPackagePermByTenantPackageId(tenant.getPackageId());\n        List<Long> permIds = tenantPackagePerms.stream().map(MTenantPackagePerm::getPermId).toList();\n        List<MPerms> enablePermList = permsMapper.selectPermsByPermsIds(permIds);\n        return permsToTree(enablePermList);\n    }\n\n    /**\n     * 根据权限ID查询权限\n     * @param permId 权限ID\n     * @return 权限数据\n     */\n    @Override\n    public PermVO getPermByPermId(Long permId) {\n        return BeanUtil.copyProperties(permsMapper.selectPermsByPermId(permId), PermVO.class);\n    }\n\n    /**\n     * 查找子节点\n     * @param permsList 权限数据集合\n     * @param rootNodeList 权限顶级节点数据集合\n     */\n    private void findChildren(List<MPerms> permsList, List<PermVO> rootNodeList) {\n        //遍历顶级节点\n        rootNodeList.forEach(node -> {\n            //存储子节点\n            List<PermVO> childrenNodes = CollectionUtil.list(false);\n            //从权限数据集合中查找子节点\n            permsList.forEach(p -> {\n                //节点是否关联\n                if (node.getPermId().equals(p.getParentPermId())) {\n                    childrenNodes.add(BeanUtil.copyProperties(p, PermVO.class));\n                }\n                //显示排序\n                childrenNodes.sort(Comparator.comparing(PermVO::getPermSort));\n            });\n            //如果有关联的子节点\n            if (CollectionUtil.isNotEmpty(childrenNodes)) {\n                //将查询到的子节点挂在顶级节点上\n                node.setChildren(childrenNodes);\n                //对子节点继续递归，查找子节点的下级\n                findChildren(permsList, childrenNodes);\n            }\n        });\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/service/impl/PostServiceImpl.java",
    "content": "package com.minimalist.basic.service.impl;\n\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.collection.CollectionUtil;\nimport cn.hutool.core.lang.Assert;\nimport com.minimalist.basic.entity.enums.PostEnum;\nimport com.minimalist.basic.entity.po.MPost;\nimport com.minimalist.basic.entity.po.MUserPost;\nimport com.minimalist.basic.entity.vo.post.PostQueryVO;\nimport com.minimalist.basic.entity.vo.post.PostVO;\nimport com.minimalist.basic.mapper.MPostMapper;\nimport com.minimalist.basic.mapper.MUserPostMapper;\nimport com.minimalist.basic.service.PostService;\nimport com.minimalist.basic.config.exception.BusinessException;\nimport com.minimalist.basic.config.mybatis.bo.PageResp;\nimport com.minimalist.basic.utils.UnqIdUtil;\nimport com.mybatisflex.core.logicdelete.LogicDeleteManager;\nimport com.mybatisflex.core.paginate.Page;\nimport com.mybatisflex.core.query.QueryWrapper;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Service;\nimport java.util.List;\n\n@Service\npublic class PostServiceImpl implements PostService {\n\n    @Autowired\n    private MPostMapper postMapper;\n\n    @Autowired\n    private MUserPostMapper userPostMapper;\n\n    /**\n     * 添加岗位\n     * @param postVO 岗位数据\n     */\n    @Override\n    public void addPost(PostVO postVO) {\n        MPost mPost = postMapper.selectPostByPostCode(postVO.getPostCode());\n        Assert.isNull(mPost, () -> new BusinessException(PostEnum.ErrorMsg.EXISTS_POST.getDesc()));\n        long postId = UnqIdUtil.uniqueId();\n        mPost = BeanUtil.copyProperties(postVO, MPost.class);\n        mPost.setPostId(postId);\n        postMapper.insert(mPost, true);\n    }\n\n    /**\n     * 删除岗位 -> 根据岗位ID删除\n     * @param postId 岗位ID\n     */\n    @Override\n    public void deletePostByPostId(Long postId) {\n        //删除岗位\n        postMapper.deletePostByPostId(postId);\n        //删除岗位与用户关联关系\n        LogicDeleteManager.execWithoutLogicDelete(()->\n                userPostMapper.deleteByQuery(QueryWrapper.create().eq(MUserPost::getPostId, postId))\n        );\n    }\n\n    /**\n     * 修改岗位 -> 根据岗位ID修改\n     * @param postVO 岗位数据\n     */\n    @Override\n    public void updatePostByPostId(PostVO postVO) {\n        MPost newPost = BeanUtil.copyProperties(postVO, MPost.class);\n        postMapper.updatePostByPostId(newPost);\n    }\n\n    /**\n     * 查询岗位列表(分页)\n     * @param queryVO 查询参数\n     * @return 岗位列表\n     */\n    @Override\n    public PageResp<PostVO> getPagePostList(PostQueryVO queryVO) {\n        Page<PostVO> postVOPage = postMapper.selectPagePostList(queryVO);\n        return new PageResp<>(postVOPage.getRecords(), postVOPage.getTotalRow());\n    }\n\n    /**\n     * 根据岗位ID查询岗位\n     * @param postId 岗位ID\n     * @return 岗位数据\n     */\n    @Override\n    public PostVO getPostByPostId(Long postId) {\n        return BeanUtil.copyProperties(postMapper.selectPostByPostId(postId), PostVO.class);\n    }\n\n    /**\n     * 根据用户ID查询用户岗位\n     * @param userId 用户ID\n     * @return 岗位列表\n     */\n    @Override\n    public List<PostVO> getPostByUserId(Long userId) {\n        //查询用户与岗位关联数据\n        List<MUserPost> userPostList = userPostMapper.selectUserPostRelation(userId);\n        if (CollectionUtil.isEmpty(userPostList)) {\n            return CollectionUtil.list(false);\n        }\n        List<Long> postIds = userPostList.stream().map(MUserPost::getPostId).toList();\n        return postMapper.selectPostByPostIds(postIds);\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/service/impl/RoleServiceImpl.java",
    "content": "package com.minimalist.basic.service.impl;\n\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.collection.CollectionUtil;\nimport cn.hutool.core.lang.Assert;\nimport com.minimalist.basic.entity.enums.RoleEnum;\nimport com.minimalist.basic.entity.po.MRole;\nimport com.minimalist.basic.entity.po.MRolePerm;\nimport com.minimalist.basic.entity.po.MUserRole;\nimport com.minimalist.basic.entity.vo.role.RoleQueryVO;\nimport com.minimalist.basic.entity.vo.role.RoleVO;\nimport com.minimalist.basic.mapper.MRoleMapper;\nimport com.minimalist.basic.mapper.MRolePermMapper;\nimport com.minimalist.basic.mapper.MUserRoleMapper;\nimport com.minimalist.basic.service.RoleService;\nimport com.minimalist.basic.config.exception.BusinessException;\nimport com.minimalist.basic.config.mybatis.bo.PageResp;\nimport com.minimalist.basic.utils.UnqIdUtil;\nimport com.mybatisflex.core.logicdelete.LogicDeleteManager;\nimport com.mybatisflex.core.paginate.Page;\nimport com.mybatisflex.core.query.QueryWrapper;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.stream.Collectors;\n\n@Service\npublic class RoleServiceImpl implements RoleService {\n\n    @Autowired\n    private MRoleMapper roleMapper;\n\n    @Autowired\n    private MUserRoleMapper userRoleMapper;\n\n    @Autowired\n    private MRolePermMapper rolePermMapper;\n\n    /**\n     * 根据用户ID查询角色\n     * @param userId 用户ID\n     * @return 角色集合\n     */\n    @Override\n    public List<RoleVO> getRolesByUserId(Long userId) {\n        //查询用户与角色关联关系\n        List<MUserRole> userRoleRelation = userRoleMapper.selectUserRoleRelation(userId);\n        if (CollectionUtil.isEmpty(userRoleRelation)) {\n            return CollectionUtil.list(false);\n        }\n        List<Long> roleIds = userRoleRelation.stream().map(MUserRole::getRoleId).distinct().toList();\n        return roleMapper.selectRoleByRoleIds(roleIds);\n    }\n\n    /**\n     * 添加角色\n     * @param roleVO 角色数据\n     */\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void addRole(RoleVO roleVO) {\n        //根据编码查询，检查是否重复添加\n        MRole mRole = roleMapper.selectRoleByRoleCode(roleVO.getRoleCode());\n        Assert.isNull(mRole, () -> new BusinessException(RoleEnum.ErrorMsg.EXISTS_ROLE.getDesc()));\n        //角色ID\n        long roleId = UnqIdUtil.uniqueId();\n        mRole = BeanUtil.copyProperties(roleVO, MRole.class);\n        mRole.setRoleId(roleId);\n        roleMapper.insert(mRole, true);\n        //插入角色和权限关联数据\n        List<MRolePerm> mRolePerms = permIdToRolePerm(roleVO.getPermissionsIds(), roleId);\n        rolePermMapper.insertBatch(mRolePerms);\n    }\n\n    /**\n     * 根据角色ID删除角色\n     * @param roleId 角色ID\n     */\n    @Override\n    public void deleteRoleByRoleId(Long roleId) {\n        //删除角色\n        roleMapper.deleteRoleByRoleId(roleId);\n        //删除角色与权限关联数据\n        LogicDeleteManager.execWithoutLogicDelete(()->\n                rolePermMapper.deleteByQuery(QueryWrapper.create().eq(MRolePerm::getRoleId, roleId))\n        );\n        //删除角色与用户关联数据\n        LogicDeleteManager.execWithoutLogicDelete(()->\n                userRoleMapper.deleteByQuery(QueryWrapper.create().eq(MUserRole::getRoleId, roleId))\n        );\n    }\n\n    /**\n     * 根据角色ID修改角色\n     * @param roleVO 角色数据\n     */\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void updateRoleByRoleId(RoleVO roleVO) {\n        MRole newRole = BeanUtil.copyProperties(roleVO, MRole.class);\n        roleMapper.updateRoleByRoleId(newRole);\n        //删除原角色与权限关联信息\n        LogicDeleteManager.execWithoutLogicDelete(()->\n                rolePermMapper.deleteByQuery(QueryWrapper.create().eq(MRolePerm::getRoleId, roleVO.getRoleId()))\n        );\n        //添加新角色与权限关联信息\n        List<MRolePerm> mRolePerms = permIdToRolePerm(roleVO.getPermissionsIds(), roleVO.getRoleId());\n        rolePermMapper.insertBatch(mRolePerms);\n    }\n\n    /**\n     * 查询角色(分页) -> 角色管理使用\n     * @param queryVO 查询条件\n     * @return 角色分页数据\n     */\n    @Override\n    public PageResp<RoleVO> getPageRoleList(RoleQueryVO queryVO) {\n        Page<RoleVO> roleVOPage = roleMapper.selectPageRoleList(queryVO);\n        return new PageResp<>(roleVOPage.getRecords(), roleVOPage.getTotalRow());\n    }\n\n    /**\n     * 根据角色ID查询角色\n     * @param roleId 角色ID\n     * @return 角色信息\n     */\n    @Override\n    public RoleVO getRoleByRoleId(Long roleId) {\n        MRole mRole = roleMapper.selectRoleByRoleId(roleId);\n        RoleVO roleVO = BeanUtil.copyProperties(mRole, RoleVO.class);\n        //根据角色查询权限，回显数据\n        List<MRolePerm> rolePerms = rolePermMapper.selectRolePermByRoleId(roleId);\n        List<String> permIds = rolePerms.stream().map(rp -> rp.getPermId().toString()).toList();\n        roleVO.setCheckedPermIds(permIds);\n        return roleVO;\n    }\n\n    /**\n     * 根据租户ID查询角色列表\n     * @param tenantId 租户ID\n     * @return 角色列表\n     */\n    @Override\n    public List<MRole> getRoleByTenantId(Long tenantId) {\n        return roleMapper.selectListByQuery(QueryWrapper.create().eq(MRole::getTenantId, tenantId));\n    }\n\n    /**\n     * 权限ID集合转换角色权限关联数据\n     * @param permIds 权限ID集合\n     * @param roleId 角色ID\n     * @return 角色与权限关联数据\n     */\n    private List<MRolePerm> permIdToRolePerm(List<Long> permIds, long roleId) {\n        return permIds.stream().map(p -> {\n            MRolePerm rolePerm = new MRolePerm();\n            rolePerm.setRoleId(roleId);\n            rolePerm.setPermId(p);\n            return rolePerm;\n        }).collect(Collectors.toList());\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/service/impl/StorageServiceImpl.java",
    "content": "package com.minimalist.basic.service.impl;\n\nimport cn.hutool.core.bean.BeanUtil;\nimport com.minimalist.basic.config.fileHandler.FileManager;\nimport com.minimalist.basic.config.fileHandler.handler.FileHandler;\nimport com.minimalist.basic.config.mybatis.bo.PageResp;\nimport com.minimalist.basic.entity.po.MStorage;\nimport com.minimalist.basic.entity.vo.storage.StorageQueryVO;\nimport com.minimalist.basic.entity.vo.storage.StorageVO;\nimport com.minimalist.basic.mapper.MStorageMapper;\nimport com.minimalist.basic.service.StorageService;\nimport com.minimalist.basic.utils.UnqIdUtil;\nimport com.mybatisflex.core.paginate.Page;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\n@Service\npublic class StorageServiceImpl implements StorageService {\n\n    @Autowired\n    private MStorageMapper storageMapper;\n\n    @Autowired\n    private FileManager fileManager;\n\n    /**\n     * 添加存储\n     * @param storageVO 存储信息\n     */\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void addStorage(StorageVO storageVO) {\n        //校验存储配置\n        FileHandler fileHandler = fileManager.getFileHandler(storageVO.getStorageType());\n        String storageConfig = fileHandler.valid(storageVO.getStorageConfig());\n        MStorage storage = BeanUtil.copyProperties(storageVO, MStorage.class);\n        long storageId = UnqIdUtil.uniqueId();\n        storage.setStorageId(storageId);\n        storage.setStorageConfig(storageConfig);\n        storageMapper.insert(storage, true);\n    }\n\n    /**\n     * 删除存储 -> 根据存储ID删除\n     * @param storageId 存储ID\n     */\n    @Override\n    public void deleteStorageByStorageId(Long storageId) {\n        storageMapper.deleteStorageByStorageId(storageId);\n    }\n\n    /**\n     * 修改存储 -> 根据存储ID修改\n     * @param storageVO 存储信息\n     */\n    @Override\n    public void updateStorageByStorageId(StorageVO storageVO) {\n        //校验存储配置\n        FileHandler fileHandler = fileManager.getFileHandler(storageVO.getStorageType());\n        String storageConfig = fileHandler.valid(storageVO.getStorageConfig());\n        MStorage storage = BeanUtil.copyProperties(storageVO, MStorage.class);\n        storage.setStorageConfig(storageConfig);\n        storageMapper.updateStorageByStorageId(storage);\n    }\n\n    /**\n     * 查询存储列表(分页)\n     * @param queryVO 查询条件\n     * @return 存储列表\n     */\n    @Override\n    public PageResp<StorageVO> getPageStorageList(StorageQueryVO queryVO) {\n        Page<StorageVO> storageVOPage = storageMapper.selectPageStorageList(queryVO);\n        return new PageResp<>(storageVOPage.getRecords(), storageVOPage.getTotalRow());\n    }\n\n    /**\n     * 根据存储ID查询存储信息\n     * @param storageId 存储ID\n     * @return 存储信息\n     */\n    @Override\n    public StorageVO getStorageByStorageId(Long storageId) {\n        return BeanUtil.copyProperties(storageMapper.selectStorageByStorageId(storageId), StorageVO.class);\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/service/impl/TenantPackageServiceImpl.java",
    "content": "package com.minimalist.basic.service.impl;\n\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.collection.CollectionUtil;\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.util.ObjectUtil;\nimport com.minimalist.basic.entity.enums.TenantEnum;\nimport com.minimalist.basic.entity.po.*;\nimport com.minimalist.basic.entity.vo.tenant.TenantPackageQueryVO;\nimport com.minimalist.basic.entity.vo.tenant.TenantPackageVO;\nimport com.minimalist.basic.manager.TenantManager;\nimport com.minimalist.basic.mapper.*;\nimport com.minimalist.basic.service.RoleService;\nimport com.minimalist.basic.service.TenantPackageService;\nimport com.minimalist.basic.entity.enums.StatusEnum;\nimport com.minimalist.basic.config.exception.BusinessException;\nimport com.minimalist.basic.config.mybatis.bo.PageResp;\nimport com.minimalist.basic.utils.CommonConstant;\nimport com.minimalist.basic.utils.UnqIdUtil;\nimport com.mybatisflex.core.logicdelete.LogicDeleteManager;\nimport com.mybatisflex.core.paginate.Page;\nimport com.mybatisflex.core.query.QueryWrapper;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n@Service\npublic class TenantPackageServiceImpl implements TenantPackageService {\n\n    @Autowired\n    private MTenantPackageMapper tenantPackageMapper;\n\n    @Autowired\n    private MTenantPackagePermMapper tenantPackagePermMapper;\n\n    @Autowired\n    private MTenantMapper tenantMapper;\n\n    @Autowired\n    private MPermsMapper permsMapper;\n\n    @Autowired\n    private RoleService roleService;\n\n    @Autowired\n    private TenantManager tenantManager;\n\n    /**\n     * 添加租户套餐\n     * @param tenantPackageVO 租户套餐信息\n     */\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void addTenantPackage(TenantPackageVO tenantPackageVO) {\n        MTenantPackage mTenantPackage = BeanUtil.copyProperties(tenantPackageVO, MTenantPackage.class);\n        long tenantPackageId = UnqIdUtil.uniqueId();\n        mTenantPackage.setPackageId(tenantPackageId);\n        tenantPackageMapper.insert(mTenantPackage, true);\n        //插入套餐与权限的关联数据\n        List<MTenantPackagePerm> mTenantPackagePerms = buildTenantPackagePerm(tenantPackageVO.getPermissionsIds(), tenantPackageId);\n        tenantPackagePermMapper.insertBatch(mTenantPackagePerms);\n    }\n\n    /**\n     * 删除租户套餐 -> 根据租户套餐ID删除租户套餐\n     * @param tenantPackageId 租户套餐ID\n     */\n    @Override\n    public void deleteTenantPackageByTenantPackageId(Long tenantPackageId) {\n        //检查租户套餐是否被使用，被使用则不能删除\n        long tenantCount = tenantMapper.selectTenantCountByTenantPackageId(tenantPackageId);\n        Assert.isTrue(tenantCount <= 0, () -> new BusinessException(TenantEnum.ErrorMsg.USE_TENANT_PACKAGE.getDesc()));\n        //删除租户套餐\n        tenantPackageMapper.deleteTenantPackageByTenantPackageId(tenantPackageId);\n        //删除套餐与权限关联数据\n        tenantPackagePermMapper.deleteByQuery(QueryWrapper.create().eq(MTenantPackagePerm::getPackageId, tenantPackageId));\n    }\n\n    /**\n     * 修改租户套餐 -> 根据租户套餐ID修改\n     * @param tenantPackageVO 租户套餐信息\n     */\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void updateTenantPackageByTenantPackageId(TenantPackageVO tenantPackageVO) {\n        //修改租户套餐数据\n        MTenantPackage newTenantPackage = BeanUtil.copyProperties(tenantPackageVO, MTenantPackage.class);\n        tenantPackageMapper.updateTenantPackageByTenantPackageId(newTenantPackage);\n        //查询租户套餐的权限 - 修改前的权限\n        List<MTenantPackagePerm> oldPerms = tenantPackagePermMapper.selectTenantPackagePermByTenantPackageId(tenantPackageVO.getPackageId());\n        String op = oldPerms.stream().map(p -> p.getPermId().toString()).collect(Collectors.joining(\",\"));\n        //修改后的套餐权限\n        String np = tenantPackageVO.getPermissionsIds().stream().map(Object::toString).collect(Collectors.joining(\",\"));\n        //如果套餐的旧权限和修改后的新权限不一致，需要修改所有使用改套餐租户的权限\n        if (!op.equals(np)) {\n            //套餐的权限修改\n            //1. 删除原套餐与权限关联数据\n            LogicDeleteManager.execWithoutLogicDelete(()->\n                    tenantPackagePermMapper.deleteByQuery(QueryWrapper.create().eq(MTenantPackagePerm::getPackageId, newTenantPackage.getPackageId()))\n            );\n            //2. 插入新套餐与权限关联数据\n            List<MTenantPackagePerm> mTenantPackagePerms = buildTenantPackagePerm(tenantPackageVO.getPermissionsIds(), newTenantPackage.getPackageId());\n            tenantPackagePermMapper.insertBatch(mTenantPackagePerms);\n            //根据套餐查租户\n            List<MTenant> tenants = tenantMapper.selectTenantByTenantPackageId(tenantPackageVO.getPackageId());\n            for (MTenant tenant : tenants) {\n                //查询租户下所有角色\n                List<MRole> roleList = roleService.getRoleByTenantId(tenant.getTenantId());\n                //修改租户权限\n                tenantManager.updateTenantPermission(roleList, tenantPackageVO.getPackageId());\n            }\n        }\n    }\n\n    /**\n     * 查询租户套餐(分页)\n     * @param queryVO 查询条件\n     * @return 租户套餐分页数据\n     */\n    @Override\n    public PageResp<TenantPackageVO> getPageTenantPackageList(TenantPackageQueryVO queryVO) {\n        Page<TenantPackageVO> tenantPackageVOPage = tenantPackageMapper.selectPageTenantPackageList(queryVO);\n        return new PageResp<>(tenantPackageVOPage.getRecords(), tenantPackageVOPage.getTotalRow());\n    }\n\n    /**\n     * 根据租户套餐ID查询租户套餐\n     * @param tenantPackageId 租户套餐ID\n     * @return 租户套餐数据\n     */\n    @Override\n    public TenantPackageVO getTenantPackageByTenantPackageId(Long tenantPackageId) {\n        //根据租户ID查询租户套餐\n        MTenantPackage mTenantPackage = tenantPackageMapper.selectTenantPackageByTenantPackageId(tenantPackageId);\n        TenantPackageVO tenantPackageVO = BeanUtil.copyProperties(mTenantPackage, TenantPackageVO.class);\n        //套餐选中权限回显\n        List<MTenantPackagePerm> tenantPackagePerms = tenantPackagePermMapper.selectTenantPackagePermByTenantPackageId(tenantPackageId);\n        List<String> permIds = tenantPackagePerms.stream().map(p -> p.getPermId().toString()).toList();\n        tenantPackageVO.setCheckedPermIds(permIds);\n        return tenantPackageVO;\n    }\n\n    /**\n     * 构建租户套餐与权限关联数据\n     * @param permissionsIds 权限ID集合\n     * @param tenantPackageId 租户ID\n     * @return 租户套餐与权限关联数据\n     */\n    private List<MTenantPackagePerm> buildTenantPackagePerm(List<Long> permissionsIds, Long tenantPackageId) {\n        return permissionsIds.stream().map(permId -> {\n            MTenantPackagePerm tpp = new MTenantPackagePerm();\n            tpp.setPackageId(tenantPackageId);\n            tpp.setPermId(permId);\n            return tpp;\n        }).toList();\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/service/impl/TenantServiceImpl.java",
    "content": "package com.minimalist.basic.service.impl;\n\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.collection.CollectionUtil;\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.util.ObjectUtil;\nimport cn.hutool.core.util.RandomUtil;\nimport cn.hutool.json.JSONUtil;\nimport com.minimalist.basic.config.redis.RedisManager;\nimport com.minimalist.basic.entity.enums.RoleEnum;\nimport com.minimalist.basic.entity.enums.TenantEnum;\nimport com.minimalist.basic.entity.po.*;\nimport com.minimalist.basic.entity.vo.tenant.TenantDatasourceVO;\nimport com.minimalist.basic.entity.vo.tenant.TenantQueryVO;\nimport com.minimalist.basic.entity.vo.tenant.TenantVO;\nimport com.minimalist.basic.entity.vo.user.UserVO;\nimport com.minimalist.basic.manager.TenantManager;\nimport com.minimalist.basic.manager.UserManager;\nimport com.minimalist.basic.mapper.*;\nimport com.minimalist.basic.service.RoleService;\nimport com.minimalist.basic.service.TenantService;\nimport com.minimalist.basic.config.exception.BusinessException;\nimport com.minimalist.basic.config.mybatis.bo.PageResp;\nimport com.minimalist.basic.utils.CommonConstant;\nimport com.minimalist.basic.utils.RedisKeyConstant;\nimport com.minimalist.basic.utils.UnqIdUtil;\nimport com.mybatisflex.core.paginate.Page;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport java.util.*;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\n@Service\npublic class TenantServiceImpl implements TenantService {\n\n    @Autowired\n    private MTenantMapper tenantMapper;\n\n    @Autowired\n    private MTenantDatasourceMapper tenantDatasourceMapper;\n\n    @Autowired\n    private MUserMapper userMapper;\n\n    @Autowired\n    private MRoleMapper roleMapper;\n\n    @Autowired\n    private RoleService roleService;\n\n    @Autowired\n    private MTenantPackagePermMapper tenantPackagePermMapper;\n\n    @Autowired\n    private UserManager userManager;\n\n    @Autowired\n    private MUserRoleMapper userRoleMapper;\n\n    @Autowired\n    private TenantManager tenantManager;\n\n    @Autowired\n    private MRolePermMapper rolePermMapper;\n\n    @Autowired\n    private RedisManager redisManager;\n\n    /**\n     * 添加租户\n     * @param tenantVO 租户信息\n     */\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void addTenant(TenantVO tenantVO) {\n        //根据租户名查询租户，租户名不能重复\n        checkTenantNameExists(tenantVO.getTenantName());\n        MTenant mTenant = BeanUtil.copyProperties(tenantVO, MTenant.class);\n        long tenantId = UnqIdUtil.uniqueId();\n\n        //为租户创建用户\n        UserVO userInfo = tenantVO.getUser();\n        checkAddTenantUser(userInfo);\n        long userId = UnqIdUtil.uniqueId();\n        userInfo.setUserId(userId);\n        addTenantUser(userInfo, tenantId);\n\n        //为租户创建角色\n        long roleId = UnqIdUtil.uniqueId();\n        addTenantRole(roleId, tenantId, tenantVO.getPackageId());\n        //用户与角色关联关系\n        addTenantUserRole(userId, roleId);\n\n        //隔离方式为数据库隔离，则插入租户数据源数据\n        if (TenantEnum.DataIsolation.DB.getCode().equals(tenantVO.getDataIsolation())) {\n            TenantDatasourceVO tenantDatasourceVO = tenantVO.getTenantDatasource();\n            //数据源名称\n            mTenant.setDatasource(tenantDatasourceVO.getDatasourceName());\n            //插入多租户数据源\n            MTenantDatasource tenantDatasource = new MTenantDatasource();\n            tenantDatasource.setTenantId(tenantId);\n            tenantDatasource.setDatasourceId(UnqIdUtil.uniqueId());\n            tenantDatasource.setDatasourceName(tenantDatasourceVO.getDatasourceName());\n            tenantDatasource.setDatasourceUrl(tenantDatasourceVO.getDatasourceUrl());\n            tenantDatasource.setUsername(tenantDatasourceVO.getUsername());\n            tenantDatasource.setPassword(tenantDatasourceVO.getPassword());\n            tenantDatasourceMapper.insert(tenantDatasource, true);\n        } else {\n            //字段隔离\n            mTenant.setDatasource(TenantEnum.MASTER);\n            mTenant.setDataIsolation(TenantEnum.DataIsolation.COLUMN.getCode());\n        }\n\n        //插入租户数据\n        mTenant.setUserId(userId);\n        mTenant.setTenantId(tenantId);\n        tenantMapper.insert(mTenant, true);\n\n        //发布消息 - 缓存租户信息\n        redisManager.publishMessage(RedisKeyConstant.TENANT_DATA_TOPIC_KEY + \".\" + CommonConstant.ADD, JSONUtil.toJsonStr(tenantVO));\n    }\n\n    /**\n     * 删除租户 -> 根据租户ID删除\n     * @param tenantId 租户ID\n     */\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void deleteTenantByTenantId(Long tenantId) {\n        //删除租户数据源信息\n        tenantDatasourceMapper.deleteTenantDatasourceByTenantId(tenantId);\n        //发布消息 - 删除缓存中的租户信息\n        redisManager.publishMessage(RedisKeyConstant.TENANT_DATA_TOPIC_KEY + \".\" + CommonConstant.DELETE, String.valueOf(tenantId));\n        //删除租户数据\n        tenantMapper.deleteTenantByTenantId(tenantId);\n    }\n\n    /**\n     * 修改租户 -> 根据租户ID修改\n     * @param tenantVO 租户信息\n     */\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void updateTenantByTenantId(TenantVO tenantVO) {\n        //根据租户ID查询租户\n        MTenant tenant = tenantMapper.selectTenantByTenantId(tenantVO.getTenantId());\n        Assert.notNull(tenant, () -> new BusinessException(TenantEnum.ErrorMsg.NONENTITY_TENANT.getDesc()));\n        MTenant newTenant = BeanUtil.copyProperties(tenantVO, MTenant.class);\n\n        //删除租户数据源信息\n        tenantDatasourceMapper.deleteTenantDatasourceByTenantId(tenant.getTenantId());\n\n        //检查租户数据源是否需要更新\n        if (TenantEnum.DataIsolation.DB.getCode().equals(tenantVO.getDataIsolation())) {\n            //数据库隔离\n            TenantDatasourceVO tenantDatasourceVO = tenantVO.getTenantDatasource();\n            //数据源名称\n            newTenant.setDatasource(tenantDatasourceVO.getDatasourceName());\n            //插入多租户数据源\n            MTenantDatasource tenantDatasource = new MTenantDatasource();\n            tenantDatasource.setTenantId(tenant.getTenantId());\n            tenantDatasource.setDatasourceId(UnqIdUtil.uniqueId());\n            tenantDatasource.setDatasourceName(tenantDatasourceVO.getDatasourceName());\n            tenantDatasource.setDatasourceUrl(tenantDatasourceVO.getDatasourceUrl());\n            tenantDatasource.setUsername(tenantDatasourceVO.getUsername());\n            tenantDatasource.setPassword(tenantDatasourceVO.getPassword());\n            tenantDatasourceMapper.insert(tenantDatasource, true);\n        } else {\n            //字段隔离\n            newTenant.setDatasource(TenantEnum.MASTER);\n            newTenant.setDataIsolation(TenantEnum.DataIsolation.COLUMN.getCode());\n        }\n\n        //更新租户\n        tenantMapper.updateTenantByTenantId(newTenant);\n        //如果租户套餐变更，则修改租户套餐\n        if (!tenantVO.getPackageId().equals(tenant.getPackageId())) {\n            //查询租户下所有角色\n            List<MRole> roleList = roleService.getRoleByTenantId(tenant.getTenantId());\n            //修改租户权限\n            tenantManager.updateTenantPermission(roleList, tenantVO.getPackageId());\n        }\n\n        //发布消息 - 缓存租户信息\n        redisManager.publishMessage(RedisKeyConstant.TENANT_DATA_TOPIC_KEY + \".\" + CommonConstant.ADD, JSONUtil.toJsonStr(tenantVO));\n    }\n\n    /**\n     * 查询租户(分页)\n     * @param queryVO 查询条件\n     * @return 租户分页数据\n     */\n    @Override\n    public PageResp<TenantVO> getPageTenantList(TenantQueryVO queryVO) {\n        //查询租户分页数据\n        Page<TenantVO> tenantVOPage = tenantMapper.selectPageTenantList(queryVO);\n        //查询租户绑定的用户信息\n        if (CollectionUtil.isNotEmpty(tenantVOPage.getRecords())) {\n            List<Long> userIdList = tenantVOPage.getRecords().stream().map(TenantVO::getUserId).toList();\n            List<MUser> mUserList = userMapper.selectUserByUserIds(userIdList);\n            Map<Long, MUser> userMap = mUserList.stream()\n                    .collect(Collectors.toMap(MUser::getUserId, Function.identity(), (v1, v2) -> v1));\n            tenantVOPage.getRecords().forEach(t -> {\n                MUser user = userMap.get(t.getUserId());\n                if (ObjectUtil.isNotNull(user)) {\n                    t.setContactName(user.getUserRealName());\n                    t.setPhone(user.getPhone());\n                    t.setEmail(user.getEmail());\n                }\n            });\n        }\n        return new PageResp<>(tenantVOPage.getRecords(), tenantVOPage.getTotalRow());\n    }\n\n    /**\n     * 根据租户ID查询租户\n     * @param tenantId 租户ID\n     * @return 租户数据\n     */\n    @Override\n    public TenantVO getTenantByTenantId(Long tenantId) {\n        MTenant mTenant = tenantMapper.selectTenantByTenantId(tenantId);\n        MUser mUser = userMapper.selectUserByUserId(mTenant.getUserId());\n        TenantVO tenantVO = BeanUtil.copyProperties(mTenant, TenantVO.class);\n        tenantVO.setContactName(mUser.getUserRealName());\n        tenantVO.setPhone(mUser.getPhone());\n        tenantVO.setEmail(mUser.getEmail());\n        //查询数据源信息\n        if (TenantEnum.DataIsolation.DB.getCode().equals(tenantVO.getDataIsolation())) {\n            MTenantDatasource tenantDatasource = tenantDatasourceMapper.selectTenantDatasourceByTenantId(tenantId);\n            tenantVO.setTenantDatasource(BeanUtil.copyProperties(tenantDatasource, TenantDatasourceVO.class));\n        }\n        return tenantVO;\n    }\n\n    /**\n     * 校验租户名是否存在，存在则抛出异常\n     * @param tenantName 租户名\n     */\n    private void checkTenantNameExists(String tenantName) {\n        MTenant tenant = tenantMapper.selectTenantByTenantName(tenantName);\n        Assert.isNull(tenant, () -> new BusinessException(TenantEnum.ErrorMsg.EXISTS_TENANT.getDesc()));\n    }\n\n    /**\n     * 校验租户的用户信息\n     * @param user 用户信息\n     */\n    private void checkAddTenantUser(UserVO user) {\n        Assert.notNull(user, () -> new BusinessException(TenantEnum.ErrorMsg.ADD_TENANT_USER_NULL.getDesc()));\n        Assert.notBlank(user.getUsername(), () -> new BusinessException(TenantEnum.ErrorMsg.ADD_TENANT_USERNAME_NULL.getDesc()));\n        Assert.notBlank(user.getPassword(), () -> new BusinessException(TenantEnum.ErrorMsg.ADD_TENANT_PASSWORD_NULL.getDesc()));\n        Assert.notBlank(user.getNickname(), () -> new BusinessException(TenantEnum.ErrorMsg.ADD_TENANT_NICKNAME_NULL.getDesc()));\n        Assert.notBlank(user.getUserRealName(), () -> new BusinessException(TenantEnum.ErrorMsg.ADD_TENANT_REALNAME_NULL.getDesc()));\n        Assert.notBlank(user.getPhone(), () -> new BusinessException(TenantEnum.ErrorMsg.ADD_TENANT_PHONE_NULL.getDesc()));\n        Assert.notNull(user.getUserSex(), () -> new BusinessException(TenantEnum.ErrorMsg.ADD_TENANT_USERSEX_NULL.getDesc()));\n    }\n\n    private void addTenantRole(Long roleId, Long tenantId, Long tenantPackageId) {\n        MRole role = new MRole();\n        role.setRoleId(roleId);\n        role.setRoleName(RoleEnum.Role.ADMIN.getName());\n        role.setRoleCode(RoleEnum.Role.ADMIN.getCode());\n        role.setRoleSort(CommonConstant.ZERO);\n        role.setRemark(\"系统自动创建角色\");\n        role.setTenantId(tenantId);\n        //插入角色\n        roleMapper.insert(role, true);\n        //插入角色和权限关联数据\n        List<MTenantPackagePerm> mTenantPackagePerms = tenantPackagePermMapper.selectTenantPackagePermByTenantPackageId(tenantPackageId);\n        List<MRolePerm> rolePerms = mTenantPackagePerms.stream().map(tpp -> {\n            MRolePerm rolePerm = new MRolePerm();\n            rolePerm.setRoleId(roleId);\n            rolePerm.setPermId(tpp.getPermId());\n            return rolePerm;\n        }).toList();\n        rolePermMapper.insertBatch(rolePerms);\n\n    }\n\n    private void addTenantUser(UserVO userInfo, Long tenantId) {\n        MUser user = new MUser();\n        user.setUserId(userInfo.getUserId());\n        user.setUsername(userInfo.getUsername());\n        user.setNickname(userInfo.getNickname());\n        user.setUserRealName(userInfo.getUserRealName());\n        user.setEmail(userInfo.getEmail());\n        user.setPhone(userInfo.getPhone());\n        user.setUserSex(userInfo.getUserSex());\n        //生成盐值，密码加密\n        String salt = RandomUtil.randomString(6);\n        user.setSalt(salt);\n        user.setPassword(userManager.passwordEncrypt(userInfo.getPassword(), salt));\n        user.setTenantId(tenantId);\n        userMapper.insert(user, true);\n    }\n\n    private void addTenantUserRole(Long userId, Long roleId) {\n        MUserRole userRole = new MUserRole();\n        userRole.setUserId(userId);\n        userRole.setRoleId(roleId);\n        userRoleMapper.insert(userRole, true);\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/service/impl/UserServiceImpl.java",
    "content": "package com.minimalist.basic.service.impl;\n\nimport cn.dev33.satoken.stp.SaTokenInfo;\nimport cn.dev33.satoken.stp.StpUtil;\nimport cn.hutool.captcha.CircleCaptcha;\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.codec.Base64;\nimport cn.hutool.core.collection.CollectionUtil;\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.util.IdUtil;\nimport cn.hutool.core.util.ObjectUtil;\nimport cn.hutool.core.util.RandomUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.minimalist.basic.entity.enums.*;\nimport com.minimalist.basic.entity.po.MPerms;\nimport com.minimalist.basic.entity.po.MUser;\nimport com.minimalist.basic.entity.po.MUserDept;\nimport com.minimalist.basic.entity.po.MUserPost;\nimport com.minimalist.basic.entity.po.MUserRole;\nimport com.minimalist.basic.entity.vo.config.ConfigVO;\nimport com.minimalist.basic.entity.vo.role.RoleVO;\nimport com.minimalist.basic.entity.vo.tenant.TenantVO;\nimport com.minimalist.basic.entity.vo.user.ImageCaptchaVO;\nimport com.minimalist.basic.entity.vo.user.RePasswordVO;\nimport com.minimalist.basic.entity.vo.user.UserInfoVO;\nimport com.minimalist.basic.entity.vo.user.UserLoginReqVO;\nimport com.minimalist.basic.entity.vo.user.UserQueryVO;\nimport com.minimalist.basic.entity.vo.user.UserSettingVO;\nimport com.minimalist.basic.entity.vo.user.UserVO;\nimport com.minimalist.basic.manager.TenantManager;\nimport com.minimalist.basic.manager.UserManager;\nimport com.minimalist.basic.mapper.MUserDeptMapper;\nimport com.minimalist.basic.mapper.MUserMapper;\nimport com.minimalist.basic.mapper.MUserPostMapper;\nimport com.minimalist.basic.mapper.MUserRoleMapper;\nimport com.minimalist.basic.service.*;\nimport com.minimalist.basic.config.exception.BusinessException;\nimport com.minimalist.basic.config.mybatis.bo.PageResp;\nimport com.minimalist.basic.config.redis.RedisManager;\nimport com.minimalist.basic.config.tenant.TenantIgnore;\nimport com.minimalist.basic.utils.*;\nimport com.mybatisflex.core.paginate.Page;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.util.StringUtils;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\n@Slf4j\n@Service\npublic class UserServiceImpl implements UserService {\n\n    @Autowired\n    private MUserMapper userMapper;\n\n    @Autowired\n    private RoleService roleService;\n\n    @Autowired\n    private MUserRoleMapper userRoleMapper;\n\n    @Autowired\n    private MUserDeptMapper userDeptMapper;\n\n    @Autowired\n    private PermService permService;\n\n    @Autowired\n    private RedisManager redisManager;\n\n    @Autowired\n    private MUserPostMapper userPostMapper;\n\n    @Autowired\n    private PostService postService;\n\n    @Autowired\n    private DeptService deptService;\n\n    @Autowired\n    private UserManager userManager;\n\n    @Autowired\n    private TenantService tenantService;\n\n    @Autowired\n    private TenantManager tenantManager;\n\n    @Autowired\n    private ConfigService configService;\n\n    /**\n     * 新增用户\n     * @param userVO 用户实体\n     */\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void addUser(UserVO userVO) {\n        //校验用户名唯一\n        com.mybatisflex.core.tenant.TenantManager.withoutTenantCondition(() ->\n            userManager.checkUsernameUniqueness(userVO.getUsername(), null)\n        );\n        //校验邮箱唯一\n        com.mybatisflex.core.tenant.TenantManager.withoutTenantCondition(() ->\n                userManager.checkUserEmailUniqueness(userVO.getEmail(), null)\n        );\n        //校验租户的套餐是否满足条件\n        tenantManager.checkTenantPackage(TenantUtil.getTenantId());\n        //新增用户数据\n        MUser user = BeanUtil.copyProperties(userVO, MUser.class);\n        long userId = UnqIdUtil.uniqueId();\n        user.setUserId(userId);\n        //生成盐值，密码加密\n        String salt = RandomUtil.randomString(6);\n        user.setSalt(salt);\n        if (StrUtil.isNotBlank(userVO.getPassword())) {\n            user.setPassword(userManager.passwordEncrypt(userVO.getPassword(), salt));\n        } else {\n            //设置默认密码 123456qwerty\n            user.setPassword(userManager.passwordEncrypt(\"123456qwerty\", salt));\n        }\n        userMapper.insert(user, true);\n        //新增用户关联信息\n        userManager.insertUserRelation(userVO.getRoleIds(), userVO.getPostIds(), userVO.getDeptIds(), userId);\n    }\n\n    /**\n     * 删除用户\n     * @param userId 用户ID\n     */\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void deleteUserByUserId(Long userId) {\n        //删除用户\n        userMapper.deleteUserByUserId(userId);\n        //删除用户关联信息\n        userManager.deleteUserRelation(userId);\n    }\n\n    /**\n     * 修改用户\n     * @param userVO 用户数据\n     */\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void updateUserByUserId(UserVO userVO) {\n        //校验用户名唯一\n        com.mybatisflex.core.tenant.TenantManager.withoutTenantCondition(() ->\n                userManager.checkUsernameUniqueness(userVO.getUsername(), userVO.getUserId())\n        );\n        //校验邮箱唯一\n        com.mybatisflex.core.tenant.TenantManager.withoutTenantCondition(() ->\n                userManager.checkUserEmailUniqueness(userVO.getEmail(), userVO.getUserId())\n        );\n        //校验该租户套餐是否满足条件\n        tenantManager.checkTenantPackage(TenantUtil.getTenantId());\n        //查询用户信息\n        MUser oldUser = userMapper.selectUserByUserId(userVO.getUserId());\n        //修改用户信息\n        MUser newUser = BeanUtil.copyProperties(userVO, MUser.class);\n        //是否需要修改密码\n        if (StrUtil.isNotBlank(userVO.getPassword())) {\n            //密码加密\n            newUser.setPassword(userManager.passwordEncrypt(userVO.getPassword(), oldUser.getSalt()));\n        }\n        //修改用户\n        userMapper.updateUserByUserId(newUser);\n        //删除用户关联信息\n        userManager.deleteUserRelation(userVO.getUserId());\n        //新增用户关联信息\n        userManager.insertUserRelation(userVO.getRoleIds(), userVO.getPostIds(), userVO.getDeptIds(), userVO.getUserId());\n    }\n\n    /**\n     * 查询用户(分页)\n     * @param queryVO 查询条件\n     * @return 用户分页数据\n     */\n    @Override\n    public PageResp<UserVO> getPageUserList(UserQueryVO queryVO) {\n        Page<UserVO> userPage = userMapper.selectPageUserList(queryVO);\n        return new PageResp<>(userPage.getRecords(), userPage.getTotalRow());\n    }\n\n    /**\n     * 根据用户ID查询用户\n     * @param userId 用户ID\n     * @return 用户信息\n     */\n    @Override\n    public UserVO getUserByUserId(Long userId) {\n        MUser mUser = userMapper.selectUserByUserId(userId);\n        //拷贝数据\n        UserVO userVO = BeanUtil.copyProperties(mUser, UserVO.class);\n        //查询用户与岗位关联数据\n        List<MUserPost> userPostList = userPostMapper.selectUserPostRelation(userVO.getUserId());\n        userVO.setPostIds(userPostList.stream().map(MUserPost::getPostId).collect(Collectors.toSet()));\n        //查询用户与角色关联数据\n        List<MUserRole> userRoleList = userRoleMapper.selectUserRoleRelation(userVO.getUserId());\n        userVO.setRoleIds(userRoleList.stream().map(MUserRole::getRoleId).collect(Collectors.toSet()));\n        //查询用户与部门关联数据\n        List<MUserDept> userDeptList = userDeptMapper.selectUserDeptRelation(userVO.getUserId());\n        //部门选中回显\n        List<String> deptIds = userDeptList.stream().map(d -> d.getDeptId().toString()).toList();\n        userVO.setCheckedDeptIds(deptIds);\n        return userVO;\n    }\n\n    /**\n     * 获取用户信息\n     */\n    @Override\n    public UserInfoVO getUserInfo() {\n        //获取当前登陆人的userId\n        Long userId = StpUtil.getLoginIdAsLong();\n        //如果多租户开启 && 当前登陆人是系统租户 && 要查询其他租户数据\n        if (TenantUtil.checkTenantOnOff() && TenantUtil.checkIsSystemTenant() && TenantUtil.checkQueryTenantData()) {\n            //获取当前操作的租户信息，可能涉及租户切换\n            TenantVO tenantVO = CommonConstant.tenantMap.get(TenantUtil.getTenantId());\n            if (ObjectUtil.isNull(tenantVO)) {\n                throw new BusinessException(\"获取租户信息为空，请检查\");\n            }\n            //取当前操作租户的用户ID，切换为该租户的管理员身份\n            userId = tenantVO.getUserId();\n        }\n        //查询用户\n        MUser user = userMapper.selectUserByUserId(userId);\n        if (ObjectUtil.isNull(user)) {\n            return new UserInfoVO();\n        }\n        UserInfoVO userInfoVO = BeanUtil.copyProperties(user, UserInfoVO.class);\n        //根据用户ID查询角色\n        List<RoleVO> roles = roleService.getRolesByUserId(userId);\n        //角色不为空，根据角色处理权限信息\n        if (CollectionUtil.isNotEmpty(roles)) {\n            //存放角色标识符\n            Set<String> roleCodes = CollectionUtil.set(false);\n            //汇总角色ID\n            List<Long> roleIds = roles.stream()\n                    //状态 = 正常\n                    .filter(r -> StatusEnum.STATUS_1.getCode().equals(r.getStatus()))\n                    .map(r -> {\n                        //角色标识符\n                        roleCodes.add(r.getRoleCode());\n                        //返回角色ID\n                        return r.getRoleId();\n                    }).distinct().toList();\n            //存放菜单数据\n            List<MPerms> menuList = CollectionUtil.list(false);\n            //根据角色ID查询权限 - 返回权限平铺数据\n            List<MPerms> permList = permService.getPermsByRoleId(roleIds);\n            //汇总权限标识符集合\n            Set<String> permCodes = permList.stream()\n                    //状态 = 正常\n                    .filter(p -> StatusEnum.STATUS_1.getCode().equals(p.getStatus()))\n                    .map(p -> {\n                        //如果是菜单，存储到菜单集合\n                        if (PermEnum.PermType.MENU.getCode().equals(p.getPermType())) {\n                            menuList.add(p);\n                        }\n                        //返回权限编码\n                        return p.getPermCode();\n                    })\n                    .filter(StrUtil::isNotBlank).collect(Collectors.toSet());\n            //将角色标识符存入用户实体\n            userInfoVO.setRoles(roleCodes);\n            //将权限标识符存入用户实体\n            userInfoVO.setPerms(permCodes);\n            //将菜单存入用户实体\n            userInfoVO.setMenus(permService.permsToTree(menuList));\n            //将权限数据向redis存储一份\n            redisManager.set(StrUtil.indexedFormat(RedisKeyConstant.USER_ROLE_CACHE_KEY, userId), roleCodes, RedisKeyConstant.USER_PERM_CACHE_EX);\n            redisManager.set(StrUtil.indexedFormat(RedisKeyConstant.USER_PERM_CACHE_KEY, userId), permCodes, RedisKeyConstant.USER_PERM_CACHE_EX);\n        }\n        //用户岗位\n        userInfoVO.setPostList(postService.getPostByUserId(userId));\n        //用户所属部门\n        List<MUserDept> userDeptList = userDeptMapper.selectUserDeptRelation(userId);\n        //部门选中回显\n        List<Long> deptIds = userDeptList.stream().map(MUserDept::getDeptId).toList();\n        userInfoVO.setDeptList(deptService.getDeptByDeptIds(deptIds));\n        return userInfoVO;\n    }\n\n    /**\n     * 获取图形验证码\n     * @return 图形验证码\n     */\n    @Override\n    public ImageCaptchaVO getImageCaptcha() {\n        ConfigVO config = configService.getConfigByConfigKey(CommonConstant.SYSTEM_CONFIG_CAPTCHA_ENABLE);\n        boolean loginCaptchaEnable = Boolean.parseBoolean(config.getConfigValue());\n        ImageCaptchaVO imageCaptchaVO = new ImageCaptchaVO();\n        imageCaptchaVO.setEnable(loginCaptchaEnable);\n        if (!loginCaptchaEnable) {\n            return imageCaptchaVO;\n        }\n        //验证码4个随机字符\n        CircleCaptcha circleCaptcha = new CircleCaptcha(280, 100, 4, 25);\n        //验证码转小写\n        String captcha = circleCaptcha.getCode().toLowerCase();\n        //为验证码生成对应的ID，1个验证码对应1个ID\n        String captchaId = IdUtil.objectId().toLowerCase();\n        //redis验证码的key\n        String key = StrUtil.indexedFormat(RedisKeyConstant.CAPTCHA_CACHE_KEY, captchaId);\n        //存入redis，value是验证码\n        redisManager.set(key, captcha, RedisKeyConstant.CAPTCHA_CACHE_EX);\n        //构建图形验证码\n        imageCaptchaVO.setCaptchaId(captchaId);\n        imageCaptchaVO.setCaptchaImg(circleCaptcha.getImageBase64());\n        return imageCaptchaVO;\n    }\n\n    /**\n     * 校验图形验证码\n     * @param captcha 验证码\n     * @param captchaId 验证码ID\n     * @return true通过校验  false未通过校验\n     */\n    @Override\n    public boolean checkImageCaptcha(String captcha, String captchaId) {\n        String key = StrUtil.indexedFormat(RedisKeyConstant.CAPTCHA_CACHE_KEY, captchaId.toLowerCase());\n        String captchaCache = redisManager.getAndDelete(key);\n        return captcha.toLowerCase().equals(captchaCache);\n    }\n\n    /**\n     * 用户登录\n     * @param reqVO 用户登录信息\n     * @return token\n     */\n    @Override\n    public SaTokenInfo userLogin(UserLoginReqVO reqVO) {\n        ConfigVO config = configService.getConfigByConfigKey(CommonConstant.SYSTEM_CONFIG_CAPTCHA_ENABLE);\n        boolean loginCaptchaEnable = Boolean.parseBoolean(config.getConfigValue());\n        //校验验证码是否正确\n        if (loginCaptchaEnable) {\n            Assert.isTrue(StrUtil.isNotBlank(reqVO.getCaptcha()), () -> new BusinessException(UserEnum.ErrorMsg.CAPTCHA_CONTENT_EMPTY.getDesc()));\n            Assert.isTrue(StrUtil.isNotBlank(reqVO.getCaptchaId()), () -> new BusinessException(UserEnum.ErrorMsg.CAPTCHA_ID_EMPTY.getDesc()));\n            boolean checkImageCaptcha = checkImageCaptcha(reqVO.getCaptcha(), reqVO.getCaptchaId());\n            Assert.isTrue(checkImageCaptcha, () -> new BusinessException(UserEnum.ErrorMsg.CAPTCHA_INCORRECT.getDesc()));\n        }\n        MUser loginUser = userMapper.selectUserByUsername(reqVO.getUsername());\n        Assert.notNull(loginUser, () -> new BusinessException(UserEnum.ErrorMsg.NONENTITY_ACCOUNT.getDesc()));\n        //校验密码是否正确\n        String passwordEncrypt = userManager.passwordEncrypt(reqVO.getPassword(), loginUser.getSalt());\n        Assert.isTrue(loginUser.getPassword().equals(passwordEncrypt), () -> new BusinessException(UserEnum.ErrorMsg.U_OR_P_INCORRECT.getDesc()));\n        //校验用户状态\n        Assert.isTrue(StatusEnum.STATUS_1.getCode().equals(loginUser.getStatus()),\n                () -> new BusinessException(UserEnum.ErrorMsg.USER_FROZEN.getDesc()));\n        //根据用户ID查询租户\n        TenantVO tenantVO = tenantService.getTenantByTenantId(loginUser.getTenantId());\n        //账户未绑定租户\n        Assert.notNull(tenantVO, () -> new BusinessException(UserEnum.ErrorMsg.USER_UNBOUND_TENANT.getDesc()));\n        //租户状态\n        Assert.isTrue(StatusEnum.STATUS_1.getCode().equals(tenantVO.getStatus().intValue()),\n                () -> new BusinessException(TenantEnum.ErrorMsg.DISABLED_TENANT.getDesc()));\n        //检查租户是否过期\n        tenantManager.checkTenantExpireTime(tenantVO.getExpireTime());\n        //登录\n        StpUtil.login(loginUser.getUserId());\n        //在登录时缓存参数 - 缓存租户ID\n        StpUtil.getSession().set(TenantIgnore.TENANT_ID, loginUser.getTenantId());\n        return StpUtil.getTokenInfo();\n    }\n\n    /**\n     * 重置密码\n     * @param passwordVO 重置密码实体\n     */\n    @Override\n    public void resetPassword(RePasswordVO passwordVO) {\n        //查询用户\n        MUser user = userMapper.selectUserByUserId(StpUtil.getLoginIdAsLong());\n        Assert.notNull(user, () -> new BusinessException(UserEnum.ErrorMsg.NONENTITY_ACCOUNT.getDesc()));\n        //校验旧密码\n        String oldPassword = userManager.passwordEncrypt(passwordVO.getOldPassword(), user.getSalt());\n        Assert.isTrue(user.getPassword().equals(oldPassword), () -> new BusinessException(UserEnum.ErrorMsg.OLD_PASSWORD_INCORRECT.getDesc()));\n        //新密码加密\n        user.setPassword(userManager.passwordEncrypt(passwordVO.getNewPassword(), user.getSalt()));\n        //修改\n        userMapper.updateUserByUserId(user);\n    }\n\n    /**\n     * 用户设置 -> 修改用户信息\n     * @param settingVO 用户信息\n     */\n    @Override\n    public void updateUserInfo(UserSettingVO settingVO) {\n        //查询用户\n        MUser user = userMapper.selectUserByUserId(StpUtil.getLoginIdAsLong());\n        Assert.notNull(user, () -> new BusinessException(UserEnum.ErrorMsg.NONENTITY_ACCOUNT.getDesc()));\n        MUser updateUser = BeanUtil.copyProperties(settingVO, MUser.class);\n        //用户ID\n        updateUser.setUserId(user.getUserId());\n        //修改\n        userMapper.updateUserByUserId(updateUser);\n    }\n\n    /**\n     * 修改用户头像\n     * @param userAvatar 用户头像base64编码\n     */\n    @Override\n    public void updateUserAvatar(String userAvatar) {\n        //用户ID\n        Long userId = StpUtil.getLoginIdAsLong();\n        //校验头像大小\n        byte[] base64Decode = Base64.decode(userAvatar);\n        ConfigVO config = configService.getConfigByConfigKey(CommonConstant.SYSTEM_CONFIG_USER_AVATAR_SIZE);\n        long userAvatarSize = Long.parseLong(config.getConfigValue());\n        Assert.isFalse(base64Decode.length > userAvatarSize, () -> new BusinessException(UserEnum.ErrorMsg.USER_AVATAR_SIZE.getDesc()));\n        //查询用户\n        MUser user = userMapper.selectUserByUserId(userId);\n        Assert.notNull(user, () -> new BusinessException(UserEnum.ErrorMsg.NONENTITY_ACCOUNT.getDesc()));\n        //更新用户头像\n        MUser updateUser = new MUser();\n        updateUser.setUserId(userId);\n        updateUser.setUserAvatar(userAvatar);\n        userMapper.updateUserByUserId(updateUser);\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/utils/Add.java",
    "content": "package com.minimalist.basic.utils;\n\n/**\n * 添加时的分组校验\n */\npublic interface Add {\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/utils/CommonConstant.java",
    "content": "package com.minimalist.basic.utils;\n\nimport com.minimalist.basic.entity.vo.config.ConfigVO;\nimport com.minimalist.basic.entity.vo.tenant.TenantVO;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\npublic class CommonConstant {\n\n    /** 系统配置缓存 */\n    public static Map<String, ConfigVO> systemConfigMap = new ConcurrentHashMap<>();\n\n    /** 租户缓存 */\n    public static Map<Long, TenantVO> tenantMap = new ConcurrentHashMap<>();\n\n    /** 数字 0 */\n    public static final int ZERO = 0;\n\n    /** 数字 1 */\n    public static final int ONE = 1;\n\n    /** 增 */\n    public static final String ADD = \"add\";\n\n    /** 删 */\n    public static final String DELETE = \"delete\";\n\n    /** 改 */\n    public static final String UPDATE = \"update\";\n\n    /** 租户ID标识 */\n    public static final String TRACE_ID = \"traceId\";\n\n    /** 多租户开关配置 key */\n    public static final String SYSTEM_CONFIG_TENANT = \"system.config.tenant\";\n\n    /** 系统验证码 key */\n    public static final String SYSTEM_CONFIG_CAPTCHA_ENABLE = \"system.config.captcha.enable\";\n\n    /** 用户头像大小 key */\n    public static final String SYSTEM_CONFIG_USER_AVATAR_SIZE = \"system.config.user.avatar.size\";\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/utils/RedisKeyConstant.java",
    "content": "package com.minimalist.basic.utils;\n\npublic class RedisKeyConstant {\n\n    /**\n     * 图形验证码缓存\n     */\n    public static final String CAPTCHA_CACHE_KEY = \"captcha:{0}\";\n\n    /**\n     * 图形验证码，有效期 1分钟\n     */\n    public static final int CAPTCHA_CACHE_EX = 60;\n\n    /**\n     * 字典缓存前缀 dict:dictType:dictData\n     */\n    public static final String DICT_CACHE_KEY = \"dict:{0}\";\n\n    /**\n     * 字典缓存，有效期 30 天\n     */\n    public static final int DICT_CACHE_EX = 30 * 24 * 60 * 60;\n\n    /**\n     * 防重复提交 redis key\n     */\n    public static final String REPEAT_SUBMIT_KEY = \"repeat_submit:{0}\";\n\n    /**\n     * 用户角色 redis key\n     */\n    public static final String USER_ROLE_CACHE_KEY = \"user_role:{0}\";\n\n    /**\n     * 用户权限 perm key\n     */\n    public static final String USER_PERM_CACHE_KEY = \"user_perm:{0}\";\n\n    /**\n     * 用户权限超时时间\n     */\n    public static final int USER_PERM_CACHE_EX = 7 * 24 * 60 * 60;\n\n    /**\n     * 系统配置 订阅/发布 主题\n     */\n    public static final String SYSTEM_CONFIG_TOPIC_KEY = \"system_config_topic\";\n\n    /**\n     * 租户信息 订阅/发布 主题\n     */\n    public static final String TENANT_DATA_TOPIC_KEY = \"tenant_data_topic\";\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/utils/TenantUtil.java",
    "content": "package com.minimalist.basic.utils;\n\nimport cn.dev33.satoken.stp.StpUtil;\nimport cn.hutool.core.util.ObjectUtil;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.extra.servlet.JakartaServletUtil;\nimport com.minimalist.basic.config.exception.BusinessException;\nimport com.minimalist.basic.config.tenant.TenantIgnore;\nimport com.minimalist.basic.entity.enums.UserEnum;\nimport com.minimalist.basic.entity.vo.config.ConfigVO;\nimport jakarta.servlet.http.Cookie;\nimport jakarta.servlet.http.HttpServletRequest;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.web.context.request.RequestContextHolder;\nimport org.springframework.web.context.request.ServletRequestAttributes;\n\nimport java.nio.charset.StandardCharsets;\nimport java.util.Optional;\n\n@Slf4j\npublic class TenantUtil {\n\n    /**\n     * 校验系统是否开启多租户\n     * @return true开启，false关闭\n     */\n    public static boolean checkTenantOnOff() {\n        //校验系统多租户是否开启\n        ConfigVO configVO = CommonConstant.systemConfigMap.get(CommonConstant.SYSTEM_CONFIG_TENANT);\n        if (ObjectUtil.isNull(configVO)) {\n            log.warn(\"校验系统多租户开关，获取CommonConstant.systemConfigMap缓存为空，请检查！！！\");\n            return true;\n        }\n        return Boolean.parseBoolean(configVO.getConfigValue());\n    }\n\n    /**\n     * 获取要操作的租户ID\n     * @return 租户ID\n     */\n    public static long getTenantId() {\n        //1. 从header中获取租户ID，获取到直接返回\n        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();\n        String tenantId = JakartaServletUtil.getHeader(request, TenantIgnore.TENANT_ID, StandardCharsets.UTF_8);\n        if (StrUtil.isNotBlank(tenantId)) {\n            return Long.parseLong(tenantId);\n        }\n\n        //2. 从当前登陆人身上获取租户ID\n        long loginUserTenantId = getLoginUserTenantId();\n        if (loginUserTenantId == -1) {\n            //未登录，抛出异常\n            throw new BusinessException(UserEnum.ErrorMsg.AUTH_EXPIRED.getDesc());\n        }\n\n        //3. 校验当前登陆人是否为系统租户\n        boolean isSystemTenant = CommonConstant.ZERO == loginUserTenantId;\n        if (isSystemTenant) {\n            //是系统租户，校验是查系统数据，还是查其他租户数据\n            //前端在租户切换后，会将切换的租户ID放到cookie中\n            Long changeTenantId = TenantUtil.getCookieChangeTenantId();\n            if (ObjectUtil.isNull(changeTenantId)) {\n                //changeTenantId为空，表示租户未进行切换，直接返回系统租户ID\n                return loginUserTenantId;\n            }\n\n            //租户切换，返回切换后的租户ID，这表示虽然当前登录的是系统租户，但系统租户下要查询其他租户的数据\n            return changeTenantId;\n        } else {\n            //非系统租户，直接返回\n            return loginUserTenantId;\n        }\n    }\n\n    /**\n     * 检查是否要查询其他租户数据\n     * 前端在租户切换后，会将切换的租户ID放到cookie中\n     * @return true是，false否\n     */\n    public static boolean checkQueryTenantData() {\n        return TenantUtil.getCookieChangeTenantId() != null;\n    }\n\n\n    /**\n     * 检查是否为系统租户，系统租户ID = 0\n     * @return 是/否\n     */\n    public static boolean checkIsSystemTenant() {\n        return CommonConstant.ZERO == getLoginUserTenantId();\n    }\n\n    /**\n     * 获取当前登陆人的租户ID\n     * 当前登陆人的租户ID是在登录系统后通过 `StpUtil.getSession().set(TenantIgnore.TENANT_ID, tenantId);` 设置进去的\n     * 所以取的时候，还需要从 session 中取\n     * @return 租户ID\n     */\n    public static long getLoginUserTenantId() {\n        try {\n            return Optional.ofNullable(StpUtil.getSession().getString(TenantIgnore.TENANT_ID))\n                    .map(Long::valueOf)\n                    .orElse(-1L);\n        } catch (Exception e) {\n            return -1;\n        }\n    }\n\n    /**\n     * 获取cookie中，租户切换的租户ID\n     * @return cookie中租户切换的租户ID\n     */\n    public static Long getCookieChangeTenantId() {\n        try {\n            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();\n            Cookie cookie = JakartaServletUtil.getCookie(request, TenantIgnore.CHANGE_TENANT_ID);\n            return Optional.ofNullable(cookie).map(Cookie::getValue).filter(StrUtil::isNotBlank).map(Long::valueOf).orElse(null);\n        } catch (Exception e) {\n            return null;\n        }\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/utils/TextUtil.java",
    "content": "package com.minimalist.basic.utils;\n\nimport cn.hutool.core.collection.CollectionUtil;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.http.HtmlUtil;\nimport lombok.extern.slf4j.Slf4j;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.util.*;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n@Slf4j\npublic class TextUtil {\n\n    /** <img src=\"\" /> 数据 */\n    private static Pattern p_image = Pattern.compile(\"<img.*src\\\\s*=\\\\s*(.*?)[^>]*?>\", Pattern.CASE_INSENSITIVE);\n\n    /** <img src=\"\"></img> 数据 */\n    private static Pattern r_image = Pattern.compile(\"src\\\\s*=\\\\s*\\\"?(.*?)(\\\"|>|\\\\s+)\");\n\n    /**\n     * 从富文本中获取图片URL\n     * @param richText 富文本内容\n     * @return 图片URL列表\n     */\n    public static List<String> getImgUrlByRichText(String richText) {\n        List<String> urlList = CollectionUtil.list(false);\n        Matcher pMatcher = p_image.matcher(richText);\n        while (pMatcher.find()) {\n            //得到<img />数据\n            String img = pMatcher.group();\n            //匹配<img>中的src数据\n            Matcher rMatcher = r_image.matcher(img);\n            while (rMatcher.find()) {\n                urlList.add(rMatcher.group(1));\n            }\n        }\n        return urlList;\n    }\n\n    /**\n     * 内容编码\n     * @param text 内容\n     * @return 编码后的内容\n     */\n    public static String encode(String text) {\n        return HtmlUtil.escape(text);\n    }\n\n    /**\n     * 内容解码\n     * @param text 内容\n     * @return 解码后的内容\n     */\n    public static String decode(String text) {\n        return HtmlUtil.unescape(text);\n    }\n\n    /**\n     * 分割字符串，并使List<String>转为List<Long>\n     * @param str 字符串，逗号分割\n     * @return List<Long>\n     */\n    public static List<Long> splitAndListStrToListLong(String str) {\n        if (StrUtil.isBlank(str)) { return CollectionUtil.list(false); }\n        List<String> split = StrUtil.split(str, \",\");\n        return split.stream().map(Long::parseLong).toList();\n    }\n\n    //url匹配正则\n    private static final Pattern URL_PATTERN = Pattern.compile(\n            \"(?i)\\\\b((?:https?|ftp|file)://|www\\\\.|ftp\\\\.)\"  // 协议头匹配\n                    + \"[^\\\\s\\\"'<>(){}]+\"  // 核心内容匹配（排除常见边界符）\n                    + \"(?=[\\\\s\\\"'<>(){}]|$)\",  // 前瞻确保在边界处停止\n            Pattern.CASE_INSENSITIVE);\n\n    /**\n     * 从富文本内容中提取url\n     * @param content 富文本内容\n     * @return url列表\n     */\n    public static Set<String> extractFileUrl(String content) {\n        Set<String> urls = new HashSet<>();\n        Matcher matcher = URL_PATTERN.matcher(decode(content));\n        while (matcher.find()) {\n            String url = matcher.group();\n            urls.add(url);\n        }\n        return urls;\n    }\n\n    /**\n     * url格式化，去除多个 /\n     * @param url url\n     * @return 格式化后的url\n     */\n    public static String urlNormalize(String url) {\n        try {\n            URI uri = new URI(url);\n            String normalizedPath = uri.getPath().replaceAll(\"/+\", \"/\");\n            URI formattedUri = new URI(\n                    uri.getScheme(),\n                    uri.getAuthority(),\n                    new URI(normalizedPath).normalize().getPath(), // 标准化路径\n                    uri.getQuery(),\n                    uri.getFragment()\n            );\n            return formattedUri.toString();\n        } catch (URISyntaxException e) {\n            log.warn(\"格式化url失败：\", e);\n        }\n        return url;\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/utils/UnqIdUtil.java",
    "content": "package com.minimalist.basic.utils;\n\nimport cn.hutool.core.util.IdUtil;\n\npublic class UnqIdUtil {\n\n    /**\n     * 获取唯一ID\n     * @return ID\n     */\n    public static long uniqueId() {\n        return IdUtil.getSnowflakeNextId();\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/utils/Update.java",
    "content": "package com.minimalist.basic.utils;\n\n/**\n * 更新时的分组校验\n */\npublic interface Update {\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/java/com/minimalist/basic/utils/ValidateUtil.java",
    "content": "package com.minimalist.basic.utils;\n\nimport cn.hutool.core.collection.CollectionUtil;\nimport com.minimalist.basic.config.exception.BusinessException;\nimport jakarta.validation.ConstraintViolation;\nimport jakarta.validation.Validation;\nimport jakarta.validation.Validator;\nimport jakarta.validation.ValidatorFactory;\n\nimport java.util.List;\nimport java.util.Set;\nimport java.util.StringJoiner;\nimport java.util.stream.Collectors;\n\npublic class ValidateUtil {\n\n    /**\n     * 手动校验\n     */\n    public static <T> void valid(T t) {\n        ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();\n        Validator validator = validatorFactory.getValidator();\n        Set<ConstraintViolation<T>> errors = validator.validate(t);\n        List<String> errorMsg = errors.stream().map(ConstraintViolation::getMessage).collect(Collectors.toList());\n        if (CollectionUtil.isNotEmpty(errorMsg)) {\n            StringJoiner sj = new StringJoiner(\"，\");\n            errorMsg.forEach(sj::add);\n            throw new BusinessException(sj.toString());\n        }\n    }\n\n}\n"
  },
  {
    "path": "minimalist-basic/src/main/resources/mappers/MConfigMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper\n        PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.minimalist.basic.mapper.MConfigMapper\">\n\n</mapper>\n"
  },
  {
    "path": "minimalist-basic/src/main/resources/mappers/MDeptMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper\n        PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.minimalist.basic.mapper.MDeptMapper\">\n\n</mapper>\n"
  },
  {
    "path": "minimalist-basic/src/main/resources/mappers/MDictMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper\n        PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.minimalist.basic.mapper.MDictMapper\">\n\n</mapper>\n"
  },
  {
    "path": "minimalist-basic/src/main/resources/mappers/MFileMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper\n        PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.minimalist.basic.mapper.MFileMapper\">\n\n</mapper>\n"
  },
  {
    "path": "minimalist-basic/src/main/resources/mappers/MNoticeMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper\n        PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.minimalist.basic.mapper.MNoticeMapper\">\n\n</mapper>\n"
  },
  {
    "path": "minimalist-basic/src/main/resources/mappers/MPermsMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper\n        PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.minimalist.basic.mapper.MPermsMapper\">\n\n</mapper>\n"
  },
  {
    "path": "minimalist-basic/src/main/resources/mappers/MPostMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper\n        PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.minimalist.basic.mapper.MPostMapper\">\n\n</mapper>\n"
  },
  {
    "path": "minimalist-basic/src/main/resources/mappers/MRoleDeptMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper\n        PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.minimalist.basic.mapper.MRoleDeptMapper\">\n\n</mapper>\n"
  },
  {
    "path": "minimalist-basic/src/main/resources/mappers/MRoleMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper\n        PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.minimalist.basic.mapper.MRoleMapper\">\n\n</mapper>\n"
  },
  {
    "path": "minimalist-basic/src/main/resources/mappers/MRolePermMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper\n        PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.minimalist.basic.mapper.MRolePermMapper\">\n\n</mapper>\n"
  },
  {
    "path": "minimalist-basic/src/main/resources/mappers/MStorageMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper\n        PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.minimalist.basic.mapper.MStorageMapper\">\n\n</mapper>\n"
  },
  {
    "path": "minimalist-basic/src/main/resources/mappers/MTenantDatasourceMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper\n        PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.minimalist.basic.mapper.MTenantDatasourceMapper\">\n\n</mapper>\n"
  },
  {
    "path": "minimalist-basic/src/main/resources/mappers/MTenantMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper\n        PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.minimalist.basic.mapper.MTenantMapper\">\n\n</mapper>\n"
  },
  {
    "path": "minimalist-basic/src/main/resources/mappers/MTenantPackageMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper\n        PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.minimalist.basic.mapper.MTenantPackageMapper\">\n\n</mapper>\n"
  },
  {
    "path": "minimalist-basic/src/main/resources/mappers/MTenantPackagePermMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper\n        PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.minimalist.basic.mapper.MTenantPackagePermMapper\">\n\n</mapper>\n"
  },
  {
    "path": "minimalist-basic/src/main/resources/mappers/MUserDeptMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper\n        PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.minimalist.basic.mapper.MUserDeptMapper\">\n\n</mapper>\n"
  },
  {
    "path": "minimalist-basic/src/main/resources/mappers/MUserMapper.xml",
    "content": "<?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<mapper namespace=\"com.minimalist.basic.mapper.MUserMapper\">\n\n</mapper>\n"
  },
  {
    "path": "minimalist-basic/src/main/resources/mappers/MUserPostMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper\n        PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.minimalist.basic.mapper.MUserPostMapper\">\n\n</mapper>\n"
  },
  {
    "path": "minimalist-basic/src/main/resources/mappers/MUserRoleMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper\n        PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.minimalist.basic.mapper.MUserRoleMapper\">\n\n</mapper>\n"
  },
  {
    "path": "minimalist-basic/src/test/java/com/minimalist/basic/MinimalistBasicApplicationTests.java",
    "content": "package com.minimalist.basic;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.context.SpringBootTest;\n\n@SpringBootTest(classes = MinimalistBasicApplicationTests.class)\nclass MinimalistBasicApplicationTests {\n\n    @Test\n    void contextLoads() {\n        System.out.println(\"启动测试\");\n    }\n\n}\n"
  },
  {
    "path": "minimalist-vue3/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "minimalist-vue3/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n    <meta charset=\"UTF-8\"/>\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/src/assets/logo.png\"/>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"/>\n    <title>极简多租户管理系统</title>\n</head>\n<body>\n<div id=\"app\"></div>\n<script type=\"module\" src=\"/src/main.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "minimalist-vue3/package.json",
    "content": "{\n  \"name\": \"minimalist-vue3\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite --mode dev\",\n    \"test\": \"vite --mode dev\",\n    \"prod\": \"vite --mode dev\",\n    \"build\": \"vite build\",\n    \"build:dev\": \"vite build --mode dev\",\n    \"build:test\": \"vite build --mode test\",\n    \"build:prod\": \"vite build --mode prod\",\n    \"preview\": \"vite preview\"\n  },\n  \"dependencies\": {\n    \"@tinymce/tinymce-vue\": \"^5.1.0\",\n    \"@vueuse/core\": \"^10.1.2\",\n    \"@vueuse/integrations\": \"^10.1.2\",\n    \"axios\": \"^1.4.0\",\n    \"nprogress\": \"^0.2.0\",\n    \"pinia\": \"^2.0.36\",\n    \"tinymce\": \"^6.6.0\",\n    \"universal-cookie\": \"^4.0.4\",\n    \"vue\": \"^3.2.47\",\n    \"vue-cropper\": \"^1.0.9\",\n    \"vue-router\": \"^4.1.6\",\n    \"xgplayer\": \"^3.0.22\"\n  },\n  \"devDependencies\": {\n    \"@arco-design/web-vue\": \"^2.45.3\",\n    \"@vitejs/plugin-vue\": \"^4.1.0\",\n    \"vite\": \"^4.3.2\",\n    \"vite-plugin-windicss\": \"^1.8.10\",\n    \"windicss\": \"^3.5.6\"\n  }\n}\n"
  },
  {
    "path": "minimalist-vue3/public/tinymce/skins/content/dark/content.css",
    "content": "body {\n  background-color: #222f3e;\n  color: #fff;\n  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;\n  line-height: 1.4;\n  margin: 1rem;\n}\na {\n  color: #4099ff;\n}\ntable {\n  border-collapse: collapse;\n}\n/* Apply a default padding if legacy cellpadding attribute is missing */\ntable:not([cellpadding]) th,\ntable:not([cellpadding]) td {\n  padding: 0.4rem;\n}\n/* Set default table styles if a table has a positive border attribute\n   and no inline css */\ntable[border]:not([border=\"0\"]):not([style*=\"border-width\"]) th,\ntable[border]:not([border=\"0\"]):not([style*=\"border-width\"]) td {\n  border-width: 1px;\n}\n/* Set default table styles if a table has a positive border attribute\n   and no inline css */\ntable[border]:not([border=\"0\"]):not([style*=\"border-style\"]) th,\ntable[border]:not([border=\"0\"]):not([style*=\"border-style\"]) td {\n  border-style: solid;\n}\n/* Set default table styles if a table has a positive border attribute\n   and no inline css */\ntable[border]:not([border=\"0\"]):not([style*=\"border-color\"]) th,\ntable[border]:not([border=\"0\"]):not([style*=\"border-color\"]) td {\n  border-color: #6d737b;\n}\nfigure {\n  display: table;\n  margin: 1rem auto;\n}\nfigure figcaption {\n  color: #8a8f97;\n  display: block;\n  margin-top: 0.25rem;\n  text-align: center;\n}\nhr {\n  border-color: #6d737b;\n  border-style: solid;\n  border-width: 1px 0 0 0;\n}\ncode {\n  background-color: #6d737b;\n  border-radius: 3px;\n  padding: 0.1rem 0.2rem;\n}\n.mce-content-body:not([dir=rtl]) blockquote {\n  border-left: 2px solid #6d737b;\n  margin-left: 1.5rem;\n  padding-left: 1rem;\n}\n.mce-content-body[dir=rtl] blockquote {\n  border-right: 2px solid #6d737b;\n  margin-right: 1.5rem;\n  padding-right: 1rem;\n}\n"
  },
  {
    "path": "minimalist-vue3/public/tinymce/skins/content/default/content.css",
    "content": "body {\n  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;\n  line-height: 1.4;\n  margin: 1rem;\n}\ntable {\n  border-collapse: collapse;\n}\n/* Apply a default padding if legacy cellpadding attribute is missing */\ntable:not([cellpadding]) th,\ntable:not([cellpadding]) td {\n  padding: 0.4rem;\n}\n/* Set default table styles if a table has a positive border attribute\n   and no inline css */\ntable[border]:not([border=\"0\"]):not([style*=\"border-width\"]) th,\ntable[border]:not([border=\"0\"]):not([style*=\"border-width\"]) td {\n  border-width: 1px;\n}\n/* Set default table styles if a table has a positive border attribute\n   and no inline css */\ntable[border]:not([border=\"0\"]):not([style*=\"border-style\"]) th,\ntable[border]:not([border=\"0\"]):not([style*=\"border-style\"]) td {\n  border-style: solid;\n}\n/* Set default table styles if a table has a positive border attribute\n   and no inline css */\ntable[border]:not([border=\"0\"]):not([style*=\"border-color\"]) th,\ntable[border]:not([border=\"0\"]):not([style*=\"border-color\"]) td {\n  border-color: #ccc;\n}\nfigure {\n  display: table;\n  margin: 1rem auto;\n}\nfigure figcaption {\n  color: #999;\n  display: block;\n  margin-top: 0.25rem;\n  text-align: center;\n}\nhr {\n  border-color: #ccc;\n  border-style: solid;\n  border-width: 1px 0 0 0;\n}\ncode {\n  background-color: #e8e8e8;\n  border-radius: 3px;\n  padding: 0.1rem 0.2rem;\n}\n.mce-content-body:not([dir=rtl]) blockquote {\n  border-left: 2px solid #ccc;\n  margin-left: 1.5rem;\n  padding-left: 1rem;\n}\n.mce-content-body[dir=rtl] blockquote {\n  border-right: 2px solid #ccc;\n  margin-right: 1.5rem;\n  padding-right: 1rem;\n}\n"
  },
  {
    "path": "minimalist-vue3/public/tinymce/skins/content/document/content.css",
    "content": "@media screen {\n  html {\n    background: #f4f4f4;\n    min-height: 100%;\n  }\n}\nbody {\n  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;\n}\n@media screen {\n  body {\n    background-color: #fff;\n    box-shadow: 0 0 4px rgba(0, 0, 0, 0.15);\n    box-sizing: border-box;\n    margin: 1rem auto 0;\n    max-width: 820px;\n    min-height: calc(100vh - 1rem);\n    padding: 4rem 6rem 6rem 6rem;\n  }\n}\ntable {\n  border-collapse: collapse;\n}\n/* Apply a default padding if legacy cellpadding attribute is missing */\ntable:not([cellpadding]) th,\ntable:not([cellpadding]) td {\n  padding: 0.4rem;\n}\n/* Set default table styles if a table has a positive border attribute\n   and no inline css */\ntable[border]:not([border=\"0\"]):not([style*=\"border-width\"]) th,\ntable[border]:not([border=\"0\"]):not([style*=\"border-width\"]) td {\n  border-width: 1px;\n}\n/* Set default table styles if a table has a positive border attribute\n   and no inline css */\ntable[border]:not([border=\"0\"]):not([style*=\"border-style\"]) th,\ntable[border]:not([border=\"0\"]):not([style*=\"border-style\"]) td {\n  border-style: solid;\n}\n/* Set default table styles if a table has a positive border attribute\n   and no inline css */\ntable[border]:not([border=\"0\"]):not([style*=\"border-color\"]) th,\ntable[border]:not([border=\"0\"]):not([style*=\"border-color\"]) td {\n  border-color: #ccc;\n}\nfigure figcaption {\n  color: #999;\n  margin-top: 0.25rem;\n  text-align: center;\n}\nhr {\n  border-color: #ccc;\n  border-style: solid;\n  border-width: 1px 0 0 0;\n}\n.mce-content-body:not([dir=rtl]) blockquote {\n  border-left: 2px solid #ccc;\n  margin-left: 1.5rem;\n  padding-left: 1rem;\n}\n.mce-content-body[dir=rtl] blockquote {\n  border-right: 2px solid #ccc;\n  margin-right: 1.5rem;\n  padding-right: 1rem;\n}\n"
  },
  {
    "path": "minimalist-vue3/public/tinymce/skins/content/tinymce-5/content.css",
    "content": "body {\n  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;\n  line-height: 1.4;\n  margin: 1rem;\n}\ntable {\n  border-collapse: collapse;\n}\n/* Apply a default padding if legacy cellpadding attribute is missing */\ntable:not([cellpadding]) th,\ntable:not([cellpadding]) td {\n  padding: 0.4rem;\n}\n/* Set default table styles if a table has a positive border attribute\n   and no inline css */\ntable[border]:not([border=\"0\"]):not([style*=\"border-width\"]) th,\ntable[border]:not([border=\"0\"]):not([style*=\"border-width\"]) td {\n  border-width: 1px;\n}\n/* Set default table styles if a table has a positive border attribute\n   and no inline css */\ntable[border]:not([border=\"0\"]):not([style*=\"border-style\"]) th,\ntable[border]:not([border=\"0\"]):not([style*=\"border-style\"]) td {\n  border-style: solid;\n}\n/* Set default table styles if a table has a positive border attribute\n   and no inline css */\ntable[border]:not([border=\"0\"]):not([style*=\"border-color\"]) th,\ntable[border]:not([border=\"0\"]):not([style*=\"border-color\"]) td {\n  border-color: #ccc;\n}\nfigure {\n  display: table;\n  margin: 1rem auto;\n}\nfigure figcaption {\n  color: #999;\n  display: block;\n  margin-top: 0.25rem;\n  text-align: center;\n}\nhr {\n  border-color: #ccc;\n  border-style: solid;\n  border-width: 1px 0 0 0;\n}\ncode {\n  background-color: #e8e8e8;\n  border-radius: 3px;\n  padding: 0.1rem 0.2rem;\n}\n.mce-content-body:not([dir=rtl]) blockquote {\n  border-left: 2px solid #ccc;\n  margin-left: 1.5rem;\n  padding-left: 1rem;\n}\n.mce-content-body[dir=rtl] blockquote {\n  border-right: 2px solid #ccc;\n  margin-right: 1.5rem;\n  padding-right: 1rem;\n}\n"
  },
  {
    "path": "minimalist-vue3/public/tinymce/skins/content/tinymce-5-dark/content.css",
    "content": "body {\n  background-color: #2f3742;\n  color: #dfe0e4;\n  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;\n  line-height: 1.4;\n  margin: 1rem;\n}\na {\n  color: #4099ff;\n}\ntable {\n  border-collapse: collapse;\n}\n/* Apply a default padding if legacy cellpadding attribute is missing */\ntable:not([cellpadding]) th,\ntable:not([cellpadding]) td {\n  padding: 0.4rem;\n}\n/* Set default table styles if a table has a positive border attribute\n   and no inline css */\ntable[border]:not([border=\"0\"]):not([style*=\"border-width\"]) th,\ntable[border]:not([border=\"0\"]):not([style*=\"border-width\"]) td {\n  border-width: 1px;\n}\n/* Set default table styles if a table has a positive border attribute\n   and no inline css */\ntable[border]:not([border=\"0\"]):not([style*=\"border-style\"]) th,\ntable[border]:not([border=\"0\"]):not([style*=\"border-style\"]) td {\n  border-style: solid;\n}\n/* Set default table styles if a table has a positive border attribute\n   and no inline css */\ntable[border]:not([border=\"0\"]):not([style*=\"border-color\"]) th,\ntable[border]:not([border=\"0\"]):not([style*=\"border-color\"]) td {\n  border-color: #6d737b;\n}\nfigure {\n  display: table;\n  margin: 1rem auto;\n}\nfigure figcaption {\n  color: #8a8f97;\n  display: block;\n  margin-top: 0.25rem;\n  text-align: center;\n}\nhr {\n  border-color: #6d737b;\n  border-style: solid;\n  border-width: 1px 0 0 0;\n}\ncode {\n  background-color: #6d737b;\n  border-radius: 3px;\n  padding: 0.1rem 0.2rem;\n}\n.mce-content-body:not([dir=rtl]) blockquote {\n  border-left: 2px solid #6d737b;\n  margin-left: 1.5rem;\n  padding-left: 1rem;\n}\n.mce-content-body[dir=rtl] blockquote {\n  border-right: 2px solid #6d737b;\n  margin-right: 1.5rem;\n  padding-right: 1rem;\n}\n"
  },
  {
    "path": "minimalist-vue3/public/tinymce/skins/content/writer/content.css",
    "content": "body {\n  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;\n  line-height: 1.4;\n  margin: 1rem auto;\n  max-width: 900px;\n}\ntable {\n  border-collapse: collapse;\n}\n/* Apply a default padding if legacy cellpadding attribute is missing */\ntable:not([cellpadding]) th,\ntable:not([cellpadding]) td {\n  padding: 0.4rem;\n}\n/* Set default table styles if a table has a positive border attribute\n   and no inline css */\ntable[border]:not([border=\"0\"]):not([style*=\"border-width\"]) th,\ntable[border]:not([border=\"0\"]):not([style*=\"border-width\"]) td {\n  border-width: 1px;\n}\n/* Set default table styles if a table has a positive border attribute\n   and no inline css */\ntable[border]:not([border=\"0\"]):not([style*=\"border-style\"]) th,\ntable[border]:not([border=\"0\"]):not([style*=\"border-style\"]) td {\n  border-style: solid;\n}\n/* Set default table styles if a table has a positive border attribute\n   and no inline css */\ntable[border]:not([border=\"0\"]):not([style*=\"border-color\"]) th,\ntable[border]:not([border=\"0\"]):not([style*=\"border-color\"]) td {\n  border-color: #ccc;\n}\nfigure {\n  display: table;\n  margin: 1rem auto;\n}\nfigure figcaption {\n  color: #999;\n  display: block;\n  margin-top: 0.25rem;\n  text-align: center;\n}\nhr {\n  border-color: #ccc;\n  border-style: solid;\n  border-width: 1px 0 0 0;\n}\ncode {\n  background-color: #e8e8e8;\n  border-radius: 3px;\n  padding: 0.1rem 0.2rem;\n}\n.mce-content-body:not([dir=rtl]) blockquote {\n  border-left: 2px solid #ccc;\n  margin-left: 1.5rem;\n  padding-left: 1rem;\n}\n.mce-content-body[dir=rtl] blockquote {\n  border-right: 2px solid #ccc;\n  margin-right: 1.5rem;\n  padding-right: 1rem;\n}\n"
  },
  {
    "path": "minimalist-vue3/public/tinymce/skins/langs/zh-Hans.js",
    "content": "tinymce.addI18n(\"zh-Hans\",{\"Redo\":\"\\u91cd\\u505a\",\"Undo\":\"\\u64a4\\u9500\",\"Cut\":\"\\u526a\\u5207\",\"Copy\":\"\\u590d\\u5236\",\"Paste\":\"\\u7c98\\u8d34\",\"Select all\":\"\\u5168\\u9009\",\"New document\":\"\\u65b0\\u5efa\\u6587\\u6863\",\"Ok\":\"\\u786e\\u5b9a\",\"Cancel\":\"\\u53d6\\u6d88\",\"Visual aids\":\"\\u7f51\\u683c\\u7ebf\",\"Bold\":\"\\u7c97\\u4f53\",\"Italic\":\"\\u659c\\u4f53\",\"Underline\":\"\\u4e0b\\u5212\\u7ebf\",\"Strikethrough\":\"\\u5220\\u9664\\u7ebf\",\"Superscript\":\"\\u4e0a\\u6807\",\"Subscript\":\"\\u4e0b\\u6807\",\"Clear formatting\":\"\\u6e05\\u9664\\u683c\\u5f0f\",\"Remove\":\"\\u79fb\\u9664\",\"Align left\":\"\\u5de6\\u5bf9\\u9f50\",\"Align center\":\"\\u5c45\\u4e2d\\u5bf9\\u9f50\",\"Align right\":\"\\u53f3\\u5bf9\\u9f50\",\"No alignment\":\"\\u672a\\u5bf9\\u9f50\",\"Justify\":\"\\u4e24\\u7aef\\u5bf9\\u9f50\",\"Bullet list\":\"\\u65e0\\u5e8f\\u5217\\u8868\",\"Numbered list\":\"\\u6709\\u5e8f\\u5217\\u8868\",\"Decrease indent\":\"\\u51cf\\u5c11\\u7f29\\u8fdb\",\"Increase indent\":\"\\u589e\\u52a0\\u7f29\\u8fdb\",\"Close\":\"\\u5173\\u95ed\",\"Formats\":\"\\u683c\\u5f0f\",\"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X/C/V keyboard shortcuts instead.\":\"\\u4f60\\u7684\\u6d4f\\u89c8\\u5668\\u4e0d\\u652f\\u6301\\u6253\\u5f00\\u526a\\u8d34\\u677f\\uff0c\\u8bf7\\u4f7f\\u7528Ctrl+X/C/V\\u7b49\\u5feb\\u6377\\u952e\\u3002\",\"Headings\":\"\\u6807\\u9898\",\"Heading 1\":\"\\u4e00\\u7ea7\\u6807\\u9898\",\"Heading 2\":\"\\u4e8c\\u7ea7\\u6807\\u9898\",\"Heading 3\":\"\\u4e09\\u7ea7\\u6807\\u9898\",\"Heading 4\":\"\\u56db\\u7ea7\\u6807\\u9898\",\"Heading 5\":\"\\u4e94\\u7ea7\\u6807\\u9898\",\"Heading 6\":\"\\u516d\\u7ea7\\u6807\\u9898\",\"Preformatted\":\"\\u9884\\u5148\\u683c\\u5f0f\\u5316\\u7684\",\"Div\":\"Div\",\"Pre\":\"\\u524d\\u8a00\",\"Code\":\"\\u4ee3\\u7801\",\"Paragraph\":\"\\u6bb5\\u843d\",\"Blockquote\":\"\\u5f15\\u6587\\u533a\\u5757\",\"Inline\":\"\\u6587\\u672c\",\"Blocks\":\"\\u6837\\u5f0f\",\"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.\":\"\\u5f53\\u524d\\u4e3a\\u7eaf\\u6587\\u672c\\u7c98\\u8d34\\u6a21\\u5f0f\\uff0c\\u518d\\u6b21\\u70b9\\u51fb\\u53ef\\u4ee5\\u56de\\u5230\\u666e\\u901a\\u7c98\\u8d34\\u6a21\\u5f0f\\u3002\",\"Fonts\":\"\\u5b57\\u4f53\",\"Font sizes\":\"\\u5b57\\u4f53\\u5927\\u5c0f\",\"Class\":\"\\u7c7b\\u578b\",\"Browse for an image\":\"\\u6d4f\\u89c8\\u56fe\\u50cf\",\"OR\":\"\\u6216\",\"Drop an image here\":\"\\u62d6\\u653e\\u4e00\\u5f20\\u56fe\\u50cf\\u81f3\\u6b64\",\"Upload\":\"\\u4e0a\\u4f20\",\"Uploading image\":\"\\u4e0a\\u4f20\\u56fe\\u7247\",\"Block\":\"\\u5757\",\"Align\":\"\\u5bf9\\u9f50\",\"Default\":\"\\u9884\\u8bbe\",\"Circle\":\"\\u7a7a\\u5fc3\\u5706\",\"Disc\":\"\\u5b9e\\u5fc3\\u5706\",\"Square\":\"\\u5b9e\\u5fc3\\u65b9\\u5757\",\"Lower Alpha\":\"\\u5c0f\\u5199\\u82f1\\u6587\\u5b57\\u6bcd\",\"Lower Greek\":\"\\u5c0f\\u5199\\u5e0c\\u814a\\u5b57\\u6bcd\",\"Lower Roman\":\"\\u5c0f\\u5199\\u7f57\\u9a6c\\u6570\\u5b57\",\"Upper Alpha\":\"\\u5927\\u5199\\u82f1\\u6587\\u5b57\\u6bcd\",\"Upper Roman\":\"\\u5927\\u5199\\u7f57\\u9a6c\\u6570\\u5b57\",\"Anchor...\":\"\\u951a\\u70b9...\",\"Anchor\":\"\\u951a\\u70b9\",\"Name\":\"\\u540d\\u79f0\",\"ID\":\"ID\",\"ID should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.\":\"ID\\u5e94\\u8be5\\u4ee5\\u82f1\\u6587\\u5b57\\u6bcd\\u5f00\\u5934\\uff0c\\u540e\\u9762\\u53ea\\u80fd\\u6709\\u82f1\\u6587\\u5b57\\u6bcd\\u3001\\u6570\\u5b57\\u3001\\u7834\\u6298\\u53f7\\u3001\\u70b9\\u3001\\u5192\\u53f7\\u6216\\u4e0b\\u5212\\u7ebf\\u3002\",\"You have unsaved changes are you sure you want to navigate away?\":\"\\u4f60\\u8fd8\\u6709\\u6587\\u6863\\u5c1a\\u672a\\u4fdd\\u5b58\\uff0c\\u786e\\u5b9a\\u8981\\u79bb\\u5f00\\uff1f\",\"Restore last draft\":\"\\u6062\\u590d\\u4e0a\\u6b21\\u7684\\u8349\\u7a3f\",\"Special character...\":\"\\u7279\\u6b8a\\u5b57\\u7b26...\",\"Special Character\":\"\\u7279\\u6b8a\\u5b57\\u7b26\",\"Source code\":\"\\u6e90\\u4ee3\\u7801\",\"Insert/Edit code sample\":\"\\u63d2\\u5165/\\u7f16\\u8f91\\u4ee3\\u7801\\u793a\\u4f8b\",\"Language\":\"\\u8bed\\u8a00\",\"Code sample...\":\"\\u793a\\u4f8b\\u4ee3\\u7801...\",\"Left to right\":\"\\u7531\\u5de6\\u5230\\u53f3\",\"Right to left\":\"\\u7531\\u53f3\\u5230\\u5de6\",\"Title\":\"\\u6807\\u9898\",\"Fullscreen\":\"\\u5168\\u5c4f\",\"Action\":\"\\u52a8\\u4f5c\",\"Shortcut\":\"\\u5feb\\u6377\\u65b9\\u5f0f\",\"Help\":\"\\u5e2e\\u52a9\",\"Address\":\"\\u5730\\u5740\",\"Focus to menubar\":\"\\u79fb\\u52a8\\u7126\\u70b9\\u5230\\u83dc\\u5355\\u680f\",\"Focus to toolbar\":\"\\u79fb\\u52a8\\u7126\\u70b9\\u5230\\u5de5\\u5177\\u680f\",\"Focus to element path\":\"\\u79fb\\u52a8\\u7126\\u70b9\\u5230\\u5143\\u7d20\\u8def\\u5f84\",\"Focus to contextual toolbar\":\"\\u79fb\\u52a8\\u7126\\u70b9\\u5230\\u4e0a\\u4e0b\\u6587\\u83dc\\u5355\",\"Insert link (if link plugin activated)\":\"\\u63d2\\u5165\\u94fe\\u63a5 (\\u5982\\u679c\\u94fe\\u63a5\\u63d2\\u4ef6\\u5df2\\u6fc0\\u6d3b)\",\"Save (if save plugin activated)\":\"\\u4fdd\\u5b58(\\u5982\\u679c\\u4fdd\\u5b58\\u63d2\\u4ef6\\u5df2\\u6fc0\\u6d3b)\",\"Find (if searchreplace plugin activated)\":\"\\u67e5\\u627e(\\u5982\\u679c\\u67e5\\u627e\\u66ff\\u6362\\u63d2\\u4ef6\\u5df2\\u6fc0\\u6d3b)\",\"Plugins installed ({0}):\":\"\\u5df2\\u5b89\\u88c5\\u63d2\\u4ef6 ({0}):\",\"Premium plugins:\":\"\\u4f18\\u79c0\\u63d2\\u4ef6\\uff1a\",\"Learn more...\":\"\\u4e86\\u89e3\\u66f4\\u591a...\",\"You are using {0}\":\"\\u4f60\\u6b63\\u5728\\u4f7f\\u7528 {0}\",\"Plugins\":\"\\u63d2\\u4ef6\",\"Handy Shortcuts\":\"\\u5feb\\u6377\\u952e\",\"Horizontal line\":\"\\u6c34\\u5e73\\u5206\\u5272\\u7ebf\",\"Insert/edit image\":\"\\u63d2\\u5165/\\u7f16\\u8f91\\u56fe\\u7247\",\"Alternative description\":\"\\u66ff\\u4ee3\\u63cf\\u8ff0\",\"Accessibility\":\"\\u8f85\\u52a9\\u529f\\u80fd\",\"Image is decorative\":\"\\u56fe\\u50cf\\u662f\\u88c5\\u9970\\u6027\\u7684\",\"Source\":\"\\u6e90\",\"Dimensions\":\"\\u5c3a\\u5bf8\",\"Constrain proportions\":\"\\u4fdd\\u6301\\u6bd4\\u4f8b\",\"General\":\"\\u4e00\\u822c\",\"Advanced\":\"\\u9ad8\\u7ea7\",\"Style\":\"\\u6837\\u5f0f\",\"Vertical space\":\"\\u5782\\u76f4\\u95f4\\u8ddd\",\"Horizontal space\":\"\\u6c34\\u5e73\\u95f4\\u8ddd\",\"Border\":\"\\u6846\\u7ebf\",\"Insert image\":\"\\u63d2\\u5165\\u56fe\\u7247\",\"Image...\":\"\\u56fe\\u7247...\",\"Image list\":\"\\u56fe\\u7247\\u6e05\\u5355\",\"Resize\":\"\\u8c03\\u6574\\u5927\\u5c0f\",\"Insert date/time\":\"\\u63d2\\u5165\\u65e5\\u671f/\\u65f6\\u95f4\",\"Date/time\":\"\\u65e5\\u671f/\\u65f6\\u95f4\",\"Insert/edit link\":\"\\u63d2\\u5165/\\u7f16\\u8f91\\u94fe\\u63a5\",\"Text to display\":\"\\u8981\\u663e\\u793a\\u7684\\u6587\\u672c\",\"Url\":\"\\u5730\\u5740\",\"Open link in...\":\"\\u94fe\\u63a5\\u6253\\u5f00\\u4f4d\\u7f6e...\",\"Current window\":\"\\u5f53\\u524d\\u7a97\\u53e3\",\"None\":\"\\u65e0\",\"New window\":\"\\u65b0\\u7a97\\u53e3\",\"Open link\":\"\\u6253\\u5f00\\u94fe\\u63a5\",\"Remove link\":\"\\u79fb\\u9664\\u94fe\\u63a5\",\"Anchors\":\"\\u951a\\u70b9\",\"Link...\":\"\\u94fe\\u63a5...\",\"Paste or type a link\":\"\\u7c98\\u8d34\\u6216\\u8f93\\u5165\\u94fe\\u63a5\",\"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?\":\"\\u4f60\\u6240\\u586b\\u5199\\u7684URL\\u5730\\u5740\\u4e3a\\u90ae\\u4ef6\\u5730\\u5740\\uff0c\\u9700\\u8981\\u52a0\\u4e0amailto: \\u524d\\u7f00\\u5417\\uff1f\",\"The URL you entered seems to be an external link. Do you want to add the required http:// prefix?\":\"\\u4f60\\u6240\\u586b\\u5199\\u7684URL\\u5730\\u5740\\u5c5e\\u4e8e\\u5916\\u90e8\\u94fe\\u63a5\\uff0c\\u9700\\u8981\\u52a0\\u4e0ahttp:// \\u524d\\u7f00\\u5417\\uff1f\",\"The URL you entered seems to be an external link. Do you want to add the required https:// prefix?\":\"\\u60a8\\u8f93\\u5165\\u7684 URL \\u4f3c\\u4e4e\\u662f\\u4e00\\u4e2a\\u5916\\u90e8\\u94fe\\u63a5\\u3002\\u60a8\\u60f3\\u6dfb\\u52a0\\u6240\\u9700\\u7684 https:// \\u524d\\u7f00\\u5417\\uff1f\",\"Link list\":\"\\u94fe\\u63a5\\u6e05\\u5355\",\"Insert video\":\"\\u63d2\\u5165\\u89c6\\u9891\",\"Insert/edit video\":\"\\u63d2\\u5165/\\u7f16\\u8f91\\u89c6\\u9891\",\"Insert/edit media\":\"\\u63d2\\u5165/\\u7f16\\u8f91\\u5a92\\u4f53\",\"Alternative source\":\"\\u955c\\u50cf\",\"Alternative source URL\":\"\\u66ff\\u4ee3\\u6765\\u6e90\\u7f51\\u5740\",\"Media poster (Image URL)\":\"\\u5c01\\u9762(\\u56fe\\u7247\\u5730\\u5740)\",\"Paste your embed code below:\":\"\\u5c06\\u5185\\u5d4c\\u4ee3\\u7801\\u7c98\\u8d34\\u5728\\u4e0b\\u9762:\",\"Embed\":\"\\u5185\\u5d4c\",\"Media...\":\"\\u591a\\u5a92\\u4f53...\",\"Nonbreaking space\":\"\\u4e0d\\u95f4\\u65ad\\u7a7a\\u683c\",\"Page break\":\"\\u5206\\u9875\\u7b26\",\"Paste as text\":\"\\u7c98\\u8d34\\u4e3a\\u6587\\u672c\",\"Preview\":\"\\u9884\\u89c8\",\"Print\":\"\\u6253\\u5370\",\"Print...\":\"\\u6253\\u5370...\",\"Save\":\"\\u4fdd\\u5b58\",\"Find\":\"\\u5bfb\\u627e\",\"Replace with\":\"\\u66ff\\u6362\\u4e3a\",\"Replace\":\"\\u66ff\\u6362\",\"Replace all\":\"\\u66ff\\u6362\\u5168\\u90e8\",\"Previous\":\"\\u4e0a\\u4e00\\u4e2a\",\"Next\":\"\\u4e0b\\u4e00\\u4e2a\",\"Find and Replace\":\"\\u67e5\\u627e\\u548c\\u66ff\\u6362\",\"Find and replace...\":\"\\u67e5\\u627e\\u5e76\\u66ff\\u6362...\",\"Could not find the specified string.\":\"\\u672a\\u627e\\u5230\\u641c\\u7d22\\u5185\\u5bb9\\u3002\",\"Match case\":\"\\u5927\\u5c0f\\u5199\\u5339\\u914d\",\"Find whole words only\":\"\\u5168\\u5b57\\u5339\\u914d\",\"Find in selection\":\"\\u5728\\u9009\\u533a\\u4e2d\\u67e5\\u627e\",\"Insert table\":\"\\u63d2\\u5165\\u8868\\u683c\",\"Table properties\":\"\\u8868\\u683c\\u5c5e\\u6027\",\"Delete table\":\"\\u5220\\u9664\\u8868\\u683c\",\"Cell\":\"\\u5355\\u5143\\u683c\",\"Row\":\"\\u884c\",\"Column\":\"\\u680f\\u76ee\",\"Cell properties\":\"\\u5355\\u5143\\u683c\\u5c5e\\u6027\",\"Merge cells\":\"\\u5408\\u5e76\\u5355\\u5143\\u683c\",\"Split cell\":\"\\u62c6\\u5206\\u5355\\u5143\\u683c\",\"Insert row before\":\"\\u5728\\u4e0a\\u65b9\\u63d2\\u5165\\u884c\",\"Insert row after\":\"\\u5728\\u4e0b\\u65b9\\u63d2\\u5165\\u884c\",\"Delete row\":\"\\u5220\\u9664\\u884c\",\"Row properties\":\"\\u884c\\u5c5e\\u6027\",\"Cut row\":\"\\u526a\\u5207\\u884c\",\"Cut column\":\"\\u526a\\u5207\\u5217\",\"Copy row\":\"\\u590d\\u5236\\u884c\",\"Copy column\":\"\\u590d\\u5236\\u5217\",\"Paste row before\":\"\\u7c98\\u8d34\\u884c\\u5230\\u4e0a\\u65b9\",\"Paste column before\":\"\\u7c98\\u8d34\\u6b64\\u5217\\u524d\",\"Paste row after\":\"\\u7c98\\u8d34\\u884c\\u5230\\u4e0b\\u65b9\",\"Paste column after\":\"\\u7c98\\u8d34\\u540e\\u9762\\u7684\\u5217\",\"Insert column before\":\"\\u5728\\u5de6\\u4fa7\\u63d2\\u5165\\u5217\",\"Insert column after\":\"\\u5728\\u53f3\\u4fa7\\u63d2\\u5165\\u5217\",\"Delete column\":\"\\u5220\\u9664\\u5217\",\"Cols\":\"\\u5217\",\"Rows\":\"\\u884c\\u6570\",\"Width\":\"\\u5bbd\\u5ea6\",\"Height\":\"\\u9ad8\\u5ea6\",\"Cell spacing\":\"\\u5355\\u5143\\u683c\\u5916\\u95f4\\u8ddd\",\"Cell padding\":\"\\u5355\\u5143\\u683c\\u5185\\u8fb9\\u8ddd\",\"Row clipboard actions\":\"\\u884c\\u526a\\u8d34\\u677f\\u64cd\\u4f5c\",\"Column clipboard actions\":\"\\u5217\\u526a\\u8d34\\u677f\\u64cd\\u4f5c\",\"Table styles\":\"\\u8868\\u683c\\u6837\\u5f0f\",\"Cell styles\":\"\\u5355\\u5143\\u683c\\u6837\\u5f0f\",\"Column header\":\"\\u5217\\u6807\\u9898\",\"Row header\":\"\\u884c\\u5934\",\"Table caption\":\"\\u8868\\u683c\\u6807\\u9898\",\"Caption\":\"\\u6807\\u9898\",\"Show caption\":\"\\u663e\\u793a\\u6807\\u9898\",\"Left\":\"\\u5de6\",\"Center\":\"\\u5c45\\u4e2d\",\"Right\":\"\\u53f3\",\"Cell type\":\"\\u50a8\\u5b58\\u683c\\u522b\",\"Scope\":\"\\u8303\\u56f4\",\"Alignment\":\"\\u5bf9\\u9f50\",\"Horizontal align\":\"\\u6c34\\u5e73\\u5bf9\\u9f50\",\"Vertical align\":\"\\u5782\\u76f4\\u5bf9\\u9f50\",\"Top\":\"\\u4e0a\\u65b9\\u5bf9\\u9f50\",\"Middle\":\"\\u5c45\\u4e2d\\u5bf9\\u9f50\",\"Bottom\":\"\\u4e0b\\u65b9\\u5bf9\\u9f50\",\"Header cell\":\"\\u8868\\u5934\\u5355\\u5143\\u683c\",\"Row group\":\"\\u884c\\u7ec4\",\"Column group\":\"\\u5217\\u7ec4\",\"Row type\":\"\\u884c\\u7c7b\\u578b\",\"Header\":\"\\u8868\\u5934\",\"Body\":\"\\u8868\\u4f53\",\"Footer\":\"\\u8868\\u5c3e\",\"Border color\":\"\\u6846\\u7ebf\\u989c\\u8272\",\"Solid\":\"\\u5b9e\\u7ebf\",\"Dotted\":\"\\u865a\\u7ebf\",\"Dashed\":\"\\u865a\\u7ebf\",\"Double\":\"\\u53cc\\u7cbe\\u5ea6\",\"Groove\":\"\\u51f9\\u69fd\",\"Ridge\":\"\\u6d77\\u810a\\u5ea7\",\"Inset\":\"\\u5d4c\\u5165\",\"Outset\":\"\\u5916\\u7f6e\",\"Hidden\":\"\\u9690\\u85cf\",\"Insert template...\":\"\\u63d2\\u5165\\u6a21\\u677f...\",\"Templates\":\"\\u6a21\\u677f\",\"Template\":\"\\u6a21\\u677f\",\"Insert Template\":\"\\u63d2\\u5165\\u6a21\\u677f\",\"Text color\":\"\\u6587\\u672c\\u989c\\u8272\",\"Background color\":\"\\u80cc\\u666f\\u989c\\u8272\",\"Custom...\":\"\\u81ea\\u5b9a\\u4e49......\",\"Custom color\":\"\\u81ea\\u5b9a\\u4e49\\u989c\\u8272\",\"No color\":\"\\u65e0\",\"Remove color\":\"\\u79fb\\u9664\\u989c\\u8272\",\"Show blocks\":\"\\u663e\\u793a\\u533a\\u5757\\u8fb9\\u6846\",\"Show invisible characters\":\"\\u663e\\u793a\\u4e0d\\u53ef\\u89c1\\u5b57\\u7b26\",\"Word count\":\"\\u5b57\\u6570\",\"Count\":\"\\u8ba1\\u6570\",\"Document\":\"\\u6587\\u6863\",\"Selection\":\"\\u9009\\u62e9\",\"Words\":\"\\u5355\\u8bcd\",\"Words: {0}\":\"\\u5b57\\u6570\\uff1a{0}\",\"{0} words\":\"{0} \\u5b57\",\"File\":\"\\u6587\\u4ef6\",\"Edit\":\"\\u7f16\\u8f91\",\"Insert\":\"\\u63d2\\u5165\",\"View\":\"\\u67e5\\u770b\",\"Format\":\"\\u683c\\u5f0f\",\"Table\":\"\\u8868\\u683c\",\"Tools\":\"\\u5de5\\u5177\",\"Powered by {0}\":\"\\u7531{0}\\u9a71\\u52a8\",\"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help\":\"\\u7f16\\u8f91\\u533a\\u3002\\u6309ALT-F9\\u6253\\u5f00\\u83dc\\u5355\\uff0c\\u6309ALT-F10\\u6253\\u5f00\\u5de5\\u5177\\u680f\\uff0c\\u6309ALT-0\\u67e5\\u770b\\u5e2e\\u52a9\",\"Image title\":\"\\u56fe\\u7247\\u6807\\u9898\",\"Border width\":\"\\u8fb9\\u6846\\u5bbd\\u5ea6\",\"Border style\":\"\\u8fb9\\u6846\\u6837\\u5f0f\",\"Error\":\"\\u9519\\u8bef\",\"Warn\":\"\\u8b66\\u544a\",\"Valid\":\"\\u6709\\u6548\",\"To open the popup, press Shift+Enter\":\"\\u6309Shitf+Enter\\u952e\\u6253\\u5f00\\u5bf9\\u8bdd\\u6846\",\"Rich Text Area\":\"\\u5bcc\\u6587\\u672c\\u533a\\u57df\",\"Rich Text Area. Press ALT-0 for help.\":\"\\u7f16\\u8f91\\u533a\\u3002\\u6309Alt+0\\u952e\\u6253\\u5f00\\u5e2e\\u52a9\\u3002\",\"System Font\":\"\\u7cfb\\u7edf\\u5b57\\u4f53\",\"Failed to upload image: {0}\":\"\\u56fe\\u7247\\u4e0a\\u4f20\\u5931\\u8d25: {0}\",\"Failed to load plugin: {0} from url {1}\":\"\\u63d2\\u4ef6\\u52a0\\u8f7d\\u5931\\u8d25: {0} \\u6765\\u81ea\\u94fe\\u63a5 {1}\",\"Failed to load plugin url: {0}\":\"\\u63d2\\u4ef6\\u52a0\\u8f7d\\u5931\\u8d25 \\u94fe\\u63a5: {0}\",\"Failed to initialize plugin: {0}\":\"\\u63d2\\u4ef6\\u521d\\u59cb\\u5316\\u5931\\u8d25: {0}\",\"example\":\"\\u793a\\u4f8b\",\"Search\":\"\\u641c\\u7d22\",\"All\":\"\\u5168\\u90e8\",\"Currency\":\"\\u8d27\\u5e01\",\"Text\":\"\\u6587\\u5b57\",\"Quotations\":\"\\u5f15\\u7528\",\"Mathematical\":\"\\u6570\\u5b66\",\"Extended Latin\":\"\\u62c9\\u4e01\\u8bed\\u6269\\u5145\",\"Symbols\":\"\\u7b26\\u53f7\",\"Arrows\":\"\\u7bad\\u5934\",\"User Defined\":\"\\u81ea\\u5b9a\\u4e49\",\"dollar sign\":\"\\u7f8e\\u5143\\u7b26\\u53f7\",\"currency sign\":\"\\u8d27\\u5e01\\u7b26\\u53f7\",\"euro-currency sign\":\"\\u6b27\\u5143\\u7b26\\u53f7\",\"colon sign\":\"\\u5192\\u53f7\",\"cruzeiro sign\":\"\\u514b\\u9c81\\u8d5b\\u7f57\\u5e01\\u7b26\\u53f7\",\"french franc sign\":\"\\u6cd5\\u90ce\\u7b26\\u53f7\",\"lira sign\":\"\\u91cc\\u62c9\\u7b26\\u53f7\",\"mill sign\":\"\\u5bc6\\u5c14\\u7b26\\u53f7\",\"naira sign\":\"\\u5948\\u62c9\\u7b26\\u53f7\",\"peseta sign\":\"\\u6bd4\\u585e\\u5854\\u7b26\\u53f7\",\"rupee sign\":\"\\u5362\\u6bd4\\u7b26\\u53f7\",\"won sign\":\"\\u97e9\\u5143\\u7b26\\u53f7\",\"new sheqel sign\":\"\\u65b0\\u8c22\\u514b\\u5c14\\u7b26\\u53f7\",\"dong sign\":\"\\u8d8a\\u5357\\u76fe\\u7b26\\u53f7\",\"kip sign\":\"\\u8001\\u631d\\u57fa\\u666e\\u7b26\\u53f7\",\"tugrik sign\":\"\\u56fe\\u683c\\u91cc\\u514b\\u7b26\\u53f7\",\"drachma sign\":\"\\u5fb7\\u62c9\\u514b\\u9a6c\\u7b26\\u53f7\",\"german penny symbol\":\"\\u5fb7\\u56fd\\u4fbf\\u58eb\\u7b26\\u53f7\",\"peso sign\":\"\\u6bd4\\u7d22\\u7b26\\u53f7\",\"guarani sign\":\"\\u74dc\\u62c9\\u5c3c\\u7b26\\u53f7\",\"austral sign\":\"\\u6fb3\\u5143\\u7b26\\u53f7\",\"hryvnia sign\":\"\\u683c\\u91cc\\u592b\\u5c3c\\u4e9a\\u7b26\\u53f7\",\"cedi sign\":\"\\u585e\\u5730\\u7b26\\u53f7\",\"livre tournois sign\":\"\\u91cc\\u5f17\\u5f17\\u5c14\\u7b26\\u53f7\",\"spesmilo sign\":\"spesmilo\\u7b26\\u53f7\",\"tenge sign\":\"\\u575a\\u6208\\u7b26\\u53f7\",\"indian rupee sign\":\"\\u5370\\u5ea6\\u5362\\u6bd4\",\"turkish lira sign\":\"\\u571f\\u8033\\u5176\\u91cc\\u62c9\",\"nordic mark sign\":\"\\u5317\\u6b27\\u9a6c\\u514b\",\"manat sign\":\"\\u9a6c\\u7eb3\\u7279\\u7b26\\u53f7\",\"ruble sign\":\"\\u5362\\u5e03\\u7b26\\u53f7\",\"yen character\":\"\\u65e5\\u5143\\u5b57\\u6837\",\"yuan character\":\"\\u4eba\\u6c11\\u5e01\\u5143\\u5b57\\u6837\",\"yuan character, in hong kong and taiwan\":\"\\u5143\\u5b57\\u6837\\uff08\\u6e2f\\u53f0\\u5730\\u533a\\uff09\",\"yen/yuan character variant one\":\"\\u5143\\u5b57\\u6837\\uff08\\u5927\\u5199\\uff09\",\"Emojis\":\"Emojis\",\"Emojis...\":\"Emojis...\",\"Loading emojis...\":\"\\u6b63\\u5728\\u52a0\\u8f7dEmojis...\",\"Could not load emojis\":\"\\u65e0\\u6cd5\\u52a0\\u8f7dEmojis\",\"People\":\"\\u4eba\\u7c7b\",\"Animals and Nature\":\"\\u52a8\\u7269\\u548c\\u81ea\\u7136\",\"Food and Drink\":\"\\u98df\\u7269\\u548c\\u996e\\u54c1\",\"Activity\":\"\\u6d3b\\u52a8\",\"Travel and Places\":\"\\u65c5\\u6e38\\u548c\\u5730\\u70b9\",\"Objects\":\"\\u7269\\u4ef6\",\"Flags\":\"\\u65d7\\u5e1c\",\"Characters\":\"\\u5b57\\u7b26\",\"Characters (no spaces)\":\"\\u5b57\\u7b26(\\u65e0\\u7a7a\\u683c)\",\"{0} characters\":\"{0} \\u4e2a\\u5b57\\u7b26\",\"Error: Form submit field collision.\":\"\\u9519\\u8bef: \\u8868\\u5355\\u63d0\\u4ea4\\u5b57\\u6bb5\\u51b2\\u7a81\\u3002\",\"Error: No form element found.\":\"\\u9519\\u8bef: \\u6ca1\\u6709\\u8868\\u5355\\u63a7\\u4ef6\\u3002\",\"Color swatch\":\"\\u989c\\u8272\\u6837\\u672c\",\"Color Picker\":\"\\u9009\\u8272\\u5668\",\"Invalid hex color code: {0}\":\"\\u5341\\u516d\\u8fdb\\u5236\\u989c\\u8272\\u4ee3\\u7801\\u65e0\\u6548\\uff1a {0}\",\"Invalid input\":\"\\u65e0\\u6548\\u8f93\\u5165\",\"R\":\"R\",\"Red component\":\"\\u7ea2\\u8272\\u90e8\\u5206\",\"G\":\"G\",\"Green component\":\"\\u7eff\\u8272\\u90e8\\u5206\",\"B\":\"B\",\"Blue component\":\"\\u767d\\u8272\\u90e8\\u5206\",\"#\":\"#\",\"Hex color code\":\"\\u5341\\u516d\\u8fdb\\u5236\\u989c\\u8272\\u4ee3\\u7801\",\"Range 0 to 255\":\"\\u8303\\u56f40\\u81f3255\",\"Turquoise\":\"\\u9752\\u7eff\\u8272\",\"Green\":\"\\u7eff\\u8272\",\"Blue\":\"\\u84dd\\u8272\",\"Purple\":\"\\u7d2b\\u8272\",\"Navy Blue\":\"\\u6d77\\u519b\\u84dd\",\"Dark Turquoise\":\"\\u6df1\\u84dd\\u7eff\\u8272\",\"Dark Green\":\"\\u6df1\\u7eff\\u8272\",\"Medium Blue\":\"\\u4e2d\\u84dd\\u8272\",\"Medium Purple\":\"\\u4e2d\\u7d2b\\u8272\",\"Midnight Blue\":\"\\u6df1\\u84dd\\u8272\",\"Yellow\":\"\\u9ec4\\u8272\",\"Orange\":\"\\u6a59\\u8272\",\"Red\":\"\\u7ea2\\u8272\",\"Light Gray\":\"\\u6d45\\u7070\\u8272\",\"Gray\":\"\\u7070\\u8272\",\"Dark Yellow\":\"\\u6697\\u9ec4\\u8272\",\"Dark Orange\":\"\\u6df1\\u6a59\\u8272\",\"Dark Red\":\"\\u6df1\\u7ea2\\u8272\",\"Medium Gray\":\"\\u4e2d\\u7070\\u8272\",\"Dark Gray\":\"\\u6df1\\u7070\\u8272\",\"Light Green\":\"\\u6d45\\u7eff\\u8272\",\"Light Yellow\":\"\\u6d45\\u9ec4\\u8272\",\"Light Red\":\"\\u6d45\\u7ea2\\u8272\",\"Light Purple\":\"\\u6d45\\u7d2b\\u8272\",\"Light Blue\":\"\\u6d45\\u84dd\\u8272\",\"Dark Purple\":\"\\u6df1\\u7d2b\\u8272\",\"Dark Blue\":\"\\u6df1\\u84dd\\u8272\",\"Black\":\"\\u9ed1\\u8272\",\"White\":\"\\u767d\\u8272\",\"Switch to or from fullscreen mode\":\"\\u5207\\u6362\\u5168\\u5c4f\\u6a21\\u5f0f\",\"Open help dialog\":\"\\u6253\\u5f00\\u5e2e\\u52a9\\u5bf9\\u8bdd\\u6846\",\"history\":\"\\u5386\\u53f2\",\"styles\":\"\\u6837\\u5f0f\",\"formatting\":\"\\u683c\\u5f0f\\u5316\",\"alignment\":\"\\u5bf9\\u9f50\",\"indentation\":\"\\u7f29\\u8fdb\",\"Font\":\"\\u5b57\\u4f53\",\"Size\":\"\\u5b57\\u53f7\",\"More...\":\"\\u66f4\\u591a...\",\"Select...\":\"\\u9009\\u62e9...\",\"Preferences\":\"\\u9996\\u9009\\u9879\",\"Yes\":\"\\u662f\",\"No\":\"\\u5426\",\"Keyboard Navigation\":\"\\u952e\\u76d8\\u6307\\u5f15\",\"Version\":\"\\u7248\\u672c\",\"Code view\":\"\\u4ee3\\u7801\\u89c6\\u56fe\",\"Open popup menu for split buttons\":\"\\u6253\\u5f00\\u5f39\\u51fa\\u5f0f\\u83dc\\u5355\\uff0c\\u7528\\u4e8e\\u62c6\\u5206\\u6309\\u94ae\",\"List Properties\":\"\\u5217\\u8868\\u5c5e\\u6027\",\"List properties...\":\"\\u6807\\u9898\\u5b57\\u4f53\\u5c5e\\u6027\",\"Start list at number\":\"\\u4ee5\\u6570\\u5b57\\u5f00\\u59cb\\u5217\\u8868\",\"Line height\":\"\\u884c\\u9ad8\",\"Dropped file type is not supported\":\"\\u6b64\\u6587\\u4ef6\\u7c7b\\u578b\\u4e0d\\u652f\\u6301\\u62d6\\u653e\",\"Loading...\":\"\\u52a0\\u8f7d\\u4e2d...\",\"ImageProxy HTTP error: Rejected request\":\"\\u56fe\\u7247\\u4ee3\\u7406\\u8bf7\\u6c42\\u9519\\u8bef\\uff1a\\u8bf7\\u6c42\\u88ab\\u62d2\\u7edd\",\"ImageProxy HTTP error: Could not find Image Proxy\":\"\\u56fe\\u7247\\u4ee3\\u7406\\u8bf7\\u6c42\\u9519\\u8bef\\uff1a\\u65e0\\u6cd5\\u627e\\u5230\\u56fe\\u7247\\u4ee3\\u7406\",\"ImageProxy HTTP error: Incorrect Image Proxy URL\":\"\\u56fe\\u7247\\u4ee3\\u7406\\u8bf7\\u6c42\\u9519\\u8bef\\uff1a\\u56fe\\u7247\\u4ee3\\u7406\\u5730\\u5740\\u9519\\u8bef\",\"ImageProxy HTTP error: Unknown ImageProxy error\":\"\\u56fe\\u7247\\u4ee3\\u7406\\u8bf7\\u6c42\\u9519\\u8bef\\uff1a\\u672a\\u77e5\\u7684\\u56fe\\u7247\\u4ee3\\u7406\\u9519\\u8bef\"});"
  },
  {
    "path": "minimalist-vue3/public/tinymce/skins/ui/oxide/content.css",
    "content": ".mce-content-body .mce-item-anchor {\n  background: transparent url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'8'%20height%3D'12'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20d%3D'M0%200L8%200%208%2012%204.09117821%209%200%2012z'%2F%3E%3C%2Fsvg%3E%0A\") no-repeat center;\n}\n.mce-content-body .mce-item-anchor:empty {\n  cursor: default;\n  display: inline-block;\n  height: 12px !important;\n  padding: 0 2px;\n  -webkit-user-modify: read-only;\n  -moz-user-modify: read-only;\n  -webkit-user-select: all;\n  -moz-user-select: all;\n  user-select: all;\n  width: 8px !important;\n}\n.mce-content-body .mce-item-anchor:not(:empty) {\n  background-position-x: 2px;\n  display: inline-block;\n  padding-left: 12px;\n}\n.mce-content-body .mce-item-anchor[data-mce-selected] {\n  outline-offset: 1px;\n}\n.tox-comments-visible .tox-comment[contenteditable=\"false\"]:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment img:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment > audio:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment > video:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment span.mce-preview-object:not([data-mce-selected]) {\n  outline: 3px solid #ffe89d;\n}\n.tox-comments-visible .tox-comment[contenteditable=\"false\"][data-mce-annotation-active=\"true\"]:not([data-mce-selected]) {\n  outline: 3px solid #fed635;\n}\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"] img:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"] > audio:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"] > video:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"] span.mce-preview-object:not([data-mce-selected]) {\n  outline: 3px solid #fed635;\n}\n.tox-comments-visible span.tox-comment:not([data-mce-selected]) {\n  background-color: #ffe89d;\n  outline: none;\n}\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"]:not([data-mce-selected=\"inline-boundary\"]) {\n  background-color: #fed635;\n}\n.tox-checklist > li:not(.tox-checklist--hidden) {\n  list-style: none;\n  margin: 0.25em 0;\n}\n.tox-checklist > li:not(.tox-checklist--hidden)::before {\n  content: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-unchecked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2215%22%20height%3D%2215%22%20x%3D%22.5%22%20y%3D%22.5%22%20fill-rule%3D%22nonzero%22%20stroke%3D%22%234C4C4C%22%20rx%3D%222%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A\");\n  cursor: pointer;\n  height: 1em;\n  margin-left: -1.5em;\n  margin-top: 0.125em;\n  position: absolute;\n  width: 1em;\n}\n.tox-checklist li:not(.tox-checklist--hidden).tox-checklist--checked::before {\n  content: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-checked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22%234099FF%22%20fill-rule%3D%22nonzero%22%20rx%3D%222%22%2F%3E%3Cpath%20id%3D%22Path%22%20fill%3D%22%23FFF%22%20fill-rule%3D%22nonzero%22%20d%3D%22M11.5703186%2C3.14417309%20C11.8516238%2C2.73724603%2012.4164781%2C2.62829933%2012.83558%2C2.89774797%20C13.260121%2C3.17069355%2013.3759736%2C3.72932262%2013.0909105%2C4.14168582%20L7.7580587%2C11.8560195%20C7.43776896%2C12.3193404%206.76483983%2C12.3852142%206.35607322%2C11.9948725%20L3.02491697%2C8.8138662%20C2.66090143%2C8.46625845%202.65798871%2C7.89594698%203.01850234%2C7.54483354%20C3.373942%2C7.19866177%203.94940006%2C7.19592841%204.30829608%2C7.5386474%20L6.85276923%2C9.9684299%20L11.5703186%2C3.14417309%20Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A\");\n}\n[dir=rtl] .tox-checklist > li:not(.tox-checklist--hidden)::before {\n  margin-left: 0;\n  margin-right: -1.5em;\n}\n/* stylelint-disable */\n/* http://prismjs.com/ */\n/**\n * prism.js default theme for JavaScript, CSS and HTML\n * Based on dabblet (http://dabblet.com)\n * @author Lea Verou\n */\ncode[class*=\"language-\"],\npre[class*=\"language-\"] {\n  color: black;\n  background: none;\n  text-shadow: 0 1px white;\n  font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;\n  font-size: 1em;\n  text-align: left;\n  white-space: pre;\n  word-spacing: normal;\n  word-break: normal;\n  word-wrap: normal;\n  line-height: 1.5;\n  -moz-tab-size: 4;\n  tab-size: 4;\n  -webkit-hyphens: none;\n  hyphens: none;\n}\npre[class*=\"language-\"]::-moz-selection,\npre[class*=\"language-\"] ::-moz-selection,\ncode[class*=\"language-\"]::-moz-selection,\ncode[class*=\"language-\"] ::-moz-selection {\n  text-shadow: none;\n  background: #b3d4fc;\n}\npre[class*=\"language-\"]::selection,\npre[class*=\"language-\"] ::selection,\ncode[class*=\"language-\"]::selection,\ncode[class*=\"language-\"] ::selection {\n  text-shadow: none;\n  background: #b3d4fc;\n}\n@media print {\n  code[class*=\"language-\"],\n  pre[class*=\"language-\"] {\n    text-shadow: none;\n  }\n}\n/* Code blocks */\npre[class*=\"language-\"] {\n  padding: 1em;\n  margin: 0.5em 0;\n  overflow: auto;\n}\n:not(pre) > code[class*=\"language-\"],\npre[class*=\"language-\"] {\n  background: #f5f2f0;\n}\n/* Inline code */\n:not(pre) > code[class*=\"language-\"] {\n  padding: 0.1em;\n  border-radius: 0.3em;\n  white-space: normal;\n}\n.token.comment,\n.token.prolog,\n.token.doctype,\n.token.cdata {\n  color: slategray;\n}\n.token.punctuation {\n  color: #999;\n}\n.token.namespace {\n  opacity: 0.7;\n}\n.token.property,\n.token.tag,\n.token.boolean,\n.token.number,\n.token.constant,\n.token.symbol,\n.token.deleted {\n  color: #905;\n}\n.token.selector,\n.token.attr-name,\n.token.string,\n.token.char,\n.token.builtin,\n.token.inserted {\n  color: #690;\n}\n.token.operator,\n.token.entity,\n.token.url,\n.language-css .token.string,\n.style .token.string {\n  color: #9a6e3a;\n  /* This background color was intended by the author of this theme. */\n  background: hsla(0, 0%, 100%, 0.5);\n}\n.token.atrule,\n.token.attr-value,\n.token.keyword {\n  color: #07a;\n}\n.token.function,\n.token.class-name {\n  color: #DD4A68;\n}\n.token.regex,\n.token.important,\n.token.variable {\n  color: #e90;\n}\n.token.important,\n.token.bold {\n  font-weight: bold;\n}\n.token.italic {\n  font-style: italic;\n}\n.token.entity {\n  cursor: help;\n}\n/* stylelint-enable */\n.mce-content-body {\n  overflow-wrap: break-word;\n  word-wrap: break-word;\n}\n.mce-content-body .mce-visual-caret {\n  background-color: black;\n  background-color: currentColor;\n  position: absolute;\n}\n.mce-content-body .mce-visual-caret-hidden {\n  display: none;\n}\n.mce-content-body *[data-mce-caret] {\n  left: -1000px;\n  margin: 0;\n  padding: 0;\n  position: absolute;\n  right: auto;\n  top: 0;\n}\n.mce-content-body .mce-offscreen-selection {\n  left: -2000000px;\n  max-width: 1000000px;\n  position: absolute;\n}\n.mce-content-body *[contentEditable=false] {\n  cursor: default;\n}\n.mce-content-body *[contentEditable=true] {\n  cursor: text;\n}\n.tox-cursor-format-painter {\n  cursor: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A\"), default;\n}\ndiv.mce-footnotes hr {\n  margin-inline-end: auto;\n  margin-inline-start: 0;\n  width: 25%;\n}\ndiv.mce-footnotes li > a.mce-footnotes-backlink {\n  text-decoration: none;\n}\n@media print {\n  sup.mce-footnote a {\n    color: black;\n    text-decoration: none;\n  }\n  div.mce-footnotes {\n    break-inside: avoid;\n    width: 100%;\n  }\n  div.mce-footnotes li > a.mce-footnotes-backlink {\n    display: none;\n  }\n}\n.mce-content-body figure.align-left {\n  float: left;\n}\n.mce-content-body figure.align-right {\n  float: right;\n}\n.mce-content-body figure.image.align-center {\n  display: table;\n  margin-left: auto;\n  margin-right: auto;\n}\n.mce-preview-object {\n  border: 1px solid gray;\n  display: inline-block;\n  line-height: 0;\n  margin: 0 2px 0 2px;\n  position: relative;\n}\n.mce-preview-object .mce-shim {\n  background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);\n  height: 100%;\n  left: 0;\n  position: absolute;\n  top: 0;\n  width: 100%;\n}\n.mce-preview-object[data-mce-selected=\"2\"] .mce-shim {\n  display: none;\n}\n.mce-content-body .mce-mergetag {\n  cursor: default !important;\n  -webkit-user-select: none;\n     -moz-user-select: none;\n          user-select: none;\n}\n.mce-content-body .mce-mergetag:hover {\n  background-color: rgba(0, 108, 231, 0.1);\n}\n.mce-content-body .mce-mergetag-affix {\n  background-color: rgba(0, 108, 231, 0.1);\n  color: #006ce7;\n}\n.mce-object {\n  background: transparent url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%2F%3E%3C%2Fsvg%3E%0A\") no-repeat center;\n  border: 1px dashed #aaa;\n}\n.mce-pagebreak {\n  border: 1px dashed #aaa;\n  cursor: default;\n  display: block;\n  height: 5px;\n  margin-top: 15px;\n  page-break-before: always;\n  width: 100%;\n}\n@media print {\n  .mce-pagebreak {\n    border: 0;\n  }\n}\n.tiny-pageembed .mce-shim {\n  background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);\n  height: 100%;\n  left: 0;\n  position: absolute;\n  top: 0;\n  width: 100%;\n}\n.tiny-pageembed[data-mce-selected=\"2\"] .mce-shim {\n  display: none;\n}\n.tiny-pageembed {\n  display: inline-block;\n  position: relative;\n}\n.tiny-pageembed--21by9,\n.tiny-pageembed--16by9,\n.tiny-pageembed--4by3,\n.tiny-pageembed--1by1 {\n  display: block;\n  overflow: hidden;\n  padding: 0;\n  position: relative;\n  width: 100%;\n}\n.tiny-pageembed--21by9 {\n  padding-top: 42.857143%;\n}\n.tiny-pageembed--16by9 {\n  padding-top: 56.25%;\n}\n.tiny-pageembed--4by3 {\n  padding-top: 75%;\n}\n.tiny-pageembed--1by1 {\n  padding-top: 100%;\n}\n.tiny-pageembed--21by9 iframe,\n.tiny-pageembed--16by9 iframe,\n.tiny-pageembed--4by3 iframe,\n.tiny-pageembed--1by1 iframe {\n  border: 0;\n  height: 100%;\n  left: 0;\n  position: absolute;\n  top: 0;\n  width: 100%;\n}\n.mce-content-body[data-mce-placeholder] {\n  position: relative;\n}\n.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before {\n  color: rgba(34, 47, 62, 0.7);\n  content: attr(data-mce-placeholder);\n  position: absolute;\n}\n.mce-content-body:not([dir=rtl])[data-mce-placeholder]:not(.mce-visualblocks)::before {\n  left: 1px;\n}\n.mce-content-body[dir=rtl][data-mce-placeholder]:not(.mce-visualblocks)::before {\n  right: 1px;\n}\n.mce-content-body div.mce-resizehandle {\n  background-color: #4099ff;\n  border-color: #4099ff;\n  border-style: solid;\n  border-width: 1px;\n  box-sizing: border-box;\n  height: 10px;\n  position: absolute;\n  width: 10px;\n  z-index: 1298;\n}\n.mce-content-body div.mce-resizehandle:hover {\n  background-color: #4099ff;\n}\n.mce-content-body div.mce-resizehandle:nth-of-type(1) {\n  cursor: nwse-resize;\n}\n.mce-content-body div.mce-resizehandle:nth-of-type(2) {\n  cursor: nesw-resize;\n}\n.mce-content-body div.mce-resizehandle:nth-of-type(3) {\n  cursor: nwse-resize;\n}\n.mce-content-body div.mce-resizehandle:nth-of-type(4) {\n  cursor: nesw-resize;\n}\n.mce-content-body .mce-resize-backdrop {\n  z-index: 10000;\n}\n.mce-content-body .mce-clonedresizable {\n  cursor: default;\n  opacity: 0.5;\n  outline: 1px dashed black;\n  position: absolute;\n  z-index: 10001;\n}\n.mce-content-body .mce-clonedresizable.mce-resizetable-columns th,\n.mce-content-body .mce-clonedresizable.mce-resizetable-columns td {\n  border: 0;\n}\n.mce-content-body .mce-resize-helper {\n  background: #555;\n  background: rgba(0, 0, 0, 0.75);\n  border: 1px;\n  border-radius: 3px;\n  color: white;\n  display: none;\n  font-family: sans-serif;\n  font-size: 12px;\n  line-height: 14px;\n  margin: 5px 10px;\n  padding: 5px;\n  position: absolute;\n  white-space: nowrap;\n  z-index: 10002;\n}\n.tox-rtc-user-selection {\n  position: relative;\n}\n.tox-rtc-user-cursor {\n  bottom: 0;\n  cursor: default;\n  position: absolute;\n  top: 0;\n  width: 2px;\n}\n.tox-rtc-user-cursor::before {\n  background-color: inherit;\n  border-radius: 50%;\n  content: '';\n  display: block;\n  height: 8px;\n  position: absolute;\n  right: -3px;\n  top: -3px;\n  width: 8px;\n}\n.tox-rtc-user-cursor:hover::after {\n  background-color: inherit;\n  border-radius: 100px;\n  box-sizing: border-box;\n  color: #fff;\n  content: attr(data-user);\n  display: block;\n  font-size: 12px;\n  font-weight: bold;\n  left: -5px;\n  min-height: 8px;\n  min-width: 8px;\n  padding: 0 12px;\n  position: absolute;\n  top: -11px;\n  white-space: nowrap;\n  z-index: 1000;\n}\n.tox-rtc-user-selection--1 .tox-rtc-user-cursor {\n  background-color: #2dc26b;\n}\n.tox-rtc-user-selection--2 .tox-rtc-user-cursor {\n  background-color: #e03e2d;\n}\n.tox-rtc-user-selection--3 .tox-rtc-user-cursor {\n  background-color: #f1c40f;\n}\n.tox-rtc-user-selection--4 .tox-rtc-user-cursor {\n  background-color: #3598db;\n}\n.tox-rtc-user-selection--5 .tox-rtc-user-cursor {\n  background-color: #b96ad9;\n}\n.tox-rtc-user-selection--6 .tox-rtc-user-cursor {\n  background-color: #e67e23;\n}\n.tox-rtc-user-selection--7 .tox-rtc-user-cursor {\n  background-color: #aaa69d;\n}\n.tox-rtc-user-selection--8 .tox-rtc-user-cursor {\n  background-color: #f368e0;\n}\n.tox-rtc-remote-image {\n  background: #eaeaea url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%2236%22%20height%3D%2212%22%20viewBox%3D%220%200%2036%2012%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%3Ccircle%20cx%3D%226%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2218%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.33s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2230%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.66s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%3C%2Fsvg%3E%0A\") no-repeat center center;\n  border: 1px solid #ccc;\n  min-height: 240px;\n  min-width: 320px;\n}\n.mce-match-marker {\n  background: #aaa;\n  color: #fff;\n}\n.mce-match-marker-selected {\n  background: #39f;\n  color: #fff;\n}\n.mce-match-marker-selected::-moz-selection {\n  background: #39f;\n  color: #fff;\n}\n.mce-match-marker-selected::selection {\n  background: #39f;\n  color: #fff;\n}\n.mce-content-body img[data-mce-selected],\n.mce-content-body video[data-mce-selected],\n.mce-content-body audio[data-mce-selected],\n.mce-content-body object[data-mce-selected],\n.mce-content-body embed[data-mce-selected],\n.mce-content-body table[data-mce-selected],\n.mce-content-body details[data-mce-selected] {\n  outline: 3px solid #b4d7ff;\n}\n.mce-content-body hr[data-mce-selected] {\n  outline: 3px solid #b4d7ff;\n  outline-offset: 1px;\n}\n.mce-content-body *[contentEditable=false] *[contentEditable=true]:focus {\n  outline: 3px solid #b4d7ff;\n}\n.mce-content-body *[contentEditable=false] *[contentEditable=true]:hover {\n  outline: 3px solid #b4d7ff;\n}\n.mce-content-body *[contentEditable=false][data-mce-selected] {\n  cursor: not-allowed;\n  outline: 3px solid #b4d7ff;\n}\n.mce-content-body.mce-content-readonly *[contentEditable=true]:focus,\n.mce-content-body.mce-content-readonly *[contentEditable=true]:hover {\n  outline: none;\n}\n.mce-content-body *[data-mce-selected=\"inline-boundary\"] {\n  background-color: #b4d7ff;\n}\n.mce-content-body .mce-edit-focus {\n  outline: 3px solid #b4d7ff;\n}\n.mce-content-body td[data-mce-selected],\n.mce-content-body th[data-mce-selected] {\n  position: relative;\n}\n.mce-content-body td[data-mce-selected]::-moz-selection,\n.mce-content-body th[data-mce-selected]::-moz-selection {\n  background: none;\n}\n.mce-content-body td[data-mce-selected]::selection,\n.mce-content-body th[data-mce-selected]::selection {\n  background: none;\n}\n.mce-content-body td[data-mce-selected] *,\n.mce-content-body th[data-mce-selected] * {\n  outline: none;\n  -webkit-touch-callout: none;\n  -webkit-user-select: none;\n     -moz-user-select: none;\n          user-select: none;\n}\n.mce-content-body td[data-mce-selected]::after,\n.mce-content-body th[data-mce-selected]::after {\n  background-color: rgba(180, 215, 255, 0.7);\n  border: 1px solid rgba(180, 215, 255, 0.7);\n  bottom: -1px;\n  content: '';\n  left: -1px;\n  mix-blend-mode: multiply;\n  position: absolute;\n  right: -1px;\n  top: -1px;\n}\n@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {\n  .mce-content-body td[data-mce-selected]::after,\n  .mce-content-body th[data-mce-selected]::after {\n    border-color: rgba(0, 84, 180, 0.7);\n  }\n}\n.mce-content-body img[data-mce-selected]::-moz-selection {\n  background: none;\n}\n.mce-content-body img[data-mce-selected]::selection {\n  background: none;\n}\n.ephox-snooker-resizer-bar {\n  background-color: #b4d7ff;\n  opacity: 0;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  user-select: none;\n}\n.ephox-snooker-resizer-cols {\n  cursor: col-resize;\n}\n.ephox-snooker-resizer-rows {\n  cursor: row-resize;\n}\n.ephox-snooker-resizer-bar.ephox-snooker-resizer-bar-dragging {\n  opacity: 1;\n}\n.mce-spellchecker-word {\n  background-image: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%23ff0000'%20fill%3D'none'%20stroke-linecap%3D'round'%20stroke-opacity%3D'.75'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A\");\n  background-position: 0 calc(100% + 1px);\n  background-repeat: repeat-x;\n  background-size: auto 6px;\n  cursor: default;\n  height: 2rem;\n}\n.mce-spellchecker-grammar {\n  background-image: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%2300A835'%20fill%3D'none'%20stroke-linecap%3D'round'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A\");\n  background-position: 0 calc(100% + 1px);\n  background-repeat: repeat-x;\n  background-size: auto 6px;\n  cursor: default;\n}\n.mce-toc {\n  border: 1px solid gray;\n}\n.mce-toc h2 {\n  margin: 4px;\n}\n.mce-toc li {\n  list-style-type: none;\n}\n[data-mce-block] {\n  display: block;\n}\ntable[style*=\"border-width: 0px\"],\n.mce-item-table:not([border]),\n.mce-item-table[border=\"0\"],\ntable[style*=\"border-width: 0px\"] td,\n.mce-item-table:not([border]) td,\n.mce-item-table[border=\"0\"] td,\ntable[style*=\"border-width: 0px\"] th,\n.mce-item-table:not([border]) th,\n.mce-item-table[border=\"0\"] th,\ntable[style*=\"border-width: 0px\"] caption,\n.mce-item-table:not([border]) caption,\n.mce-item-table[border=\"0\"] caption {\n  border: 1px dashed #bbb;\n}\n.mce-visualblocks p,\n.mce-visualblocks h1,\n.mce-visualblocks h2,\n.mce-visualblocks h3,\n.mce-visualblocks h4,\n.mce-visualblocks h5,\n.mce-visualblocks h6,\n.mce-visualblocks div:not([data-mce-bogus]),\n.mce-visualblocks section,\n.mce-visualblocks article,\n.mce-visualblocks blockquote,\n.mce-visualblocks address,\n.mce-visualblocks pre,\n.mce-visualblocks figure,\n.mce-visualblocks figcaption,\n.mce-visualblocks hgroup,\n.mce-visualblocks aside,\n.mce-visualblocks ul,\n.mce-visualblocks ol,\n.mce-visualblocks dl {\n  background-repeat: no-repeat;\n  border: 1px dashed #bbb;\n  margin-left: 3px;\n  padding-top: 10px;\n}\n.mce-visualblocks p {\n  background-image: url(data:image/gif;base64,R0lGODlhCQAJAJEAAAAAAP///7u7u////yH5BAEAAAMALAAAAAAJAAkAAAIQnG+CqCN/mlyvsRUpThG6AgA7);\n}\n.mce-visualblocks h1 {\n  background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGu1JuxHoAfRNRW3TWXyF2YiRUAOw==);\n}\n.mce-visualblocks h2 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8Hybbx4oOuqgTynJd6bGlWg3DkJzoaUAAAOw==);\n}\n.mce-visualblocks h3 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIZjI8Hybbx4oOuqgTynJf2Ln2NOHpQpmhAAQA7);\n}\n.mce-visualblocks h4 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxInR0zqeAdhtJlXwV1oCll2HaWgAAOw==);\n}\n.mce-visualblocks h5 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjane4iq5GlW05GgIkIZUAAAOw==);\n}\n.mce-visualblocks h6 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjan04jep1iZ1XRlAo5bVgAAOw==);\n}\n.mce-visualblocks div:not([data-mce-bogus]) {\n  background-image: url(data:image/gif;base64,R0lGODlhEgAKAIABALu7u////yH5BAEAAAEALAAAAAASAAoAAAIfjI9poI0cgDywrhuxfbrzDEbQM2Ei5aRjmoySW4pAAQA7);\n}\n.mce-visualblocks section {\n  background-image: url(data:image/gif;base64,R0lGODlhKAAKAIABALu7u////yH5BAEAAAEALAAAAAAoAAoAAAI5jI+pywcNY3sBWHdNrplytD2ellDeSVbp+GmWqaDqDMepc8t17Y4vBsK5hDyJMcI6KkuYU+jpjLoKADs=);\n}\n.mce-visualblocks article {\n  background-image: url(data:image/gif;base64,R0lGODlhKgAKAIABALu7u////yH5BAEAAAEALAAAAAAqAAoAAAI6jI+pywkNY3wG0GBvrsd2tXGYSGnfiF7ikpXemTpOiJScasYoDJJrjsG9gkCJ0ag6KhmaIe3pjDYBBQA7);\n}\n.mce-visualblocks blockquote {\n  background-image: url(data:image/gif;base64,R0lGODlhPgAKAIABALu7u////yH5BAEAAAEALAAAAAA+AAoAAAJPjI+py+0Knpz0xQDyuUhvfoGgIX5iSKZYgq5uNL5q69asZ8s5rrf0yZmpNkJZzFesBTu8TOlDVAabUyatguVhWduud3EyiUk45xhTTgMBBQA7);\n}\n.mce-visualblocks address {\n  background-image: url(data:image/gif;base64,R0lGODlhLQAKAIABALu7u////yH5BAEAAAEALAAAAAAtAAoAAAI/jI+pywwNozSP1gDyyZcjb3UaRpXkWaXmZW4OqKLhBmLs+K263DkJK7OJeifh7FicKD9A1/IpGdKkyFpNmCkAADs=);\n}\n.mce-visualblocks pre {\n  background-image: url(data:image/gif;base64,R0lGODlhFQAKAIABALu7uwAAACH5BAEAAAEALAAAAAAVAAoAAAIjjI+ZoN0cgDwSmnpz1NCueYERhnibZVKLNnbOq8IvKpJtVQAAOw==);\n}\n.mce-visualblocks figure {\n  background-image: url(data:image/gif;base64,R0lGODlhJAAKAIAAALu7u////yH5BAEAAAEALAAAAAAkAAoAAAI0jI+py+2fwAHUSFvD3RlvG4HIp4nX5JFSpnZUJ6LlrM52OE7uSWosBHScgkSZj7dDKnWAAgA7);\n}\n.mce-visualblocks figcaption {\n  border: 1px dashed #bbb;\n}\n.mce-visualblocks hgroup {\n  background-image: url(data:image/gif;base64,R0lGODlhJwAKAIABALu7uwAAACH5BAEAAAEALAAAAAAnAAoAAAI3jI+pywYNI3uB0gpsRtt5fFnfNZaVSYJil4Wo03Hv6Z62uOCgiXH1kZIIJ8NiIxRrAZNMZAtQAAA7);\n}\n.mce-visualblocks aside {\n  background-image: url(data:image/gif;base64,R0lGODlhHgAKAIABAKqqqv///yH5BAEAAAEALAAAAAAeAAoAAAItjI+pG8APjZOTzgtqy7I3f1yehmQcFY4WKZbqByutmW4aHUd6vfcVbgudgpYCADs=);\n}\n.mce-visualblocks ul {\n  background-image: url(data:image/gif;base64,R0lGODlhDQAKAIAAALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGuYnqUVSjvw26DzzXiqIDlVwAAOw==);\n}\n.mce-visualblocks ol {\n  background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybH6HHt0qourxC6CvzXieHyeWQAAOw==);\n}\n.mce-visualblocks dl {\n  background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybEOnmOvUoWznTqeuEjNSCqeGRUAOw==);\n}\n.mce-visualblocks:not([dir=rtl]) p,\n.mce-visualblocks:not([dir=rtl]) h1,\n.mce-visualblocks:not([dir=rtl]) h2,\n.mce-visualblocks:not([dir=rtl]) h3,\n.mce-visualblocks:not([dir=rtl]) h4,\n.mce-visualblocks:not([dir=rtl]) h5,\n.mce-visualblocks:not([dir=rtl]) h6,\n.mce-visualblocks:not([dir=rtl]) div:not([data-mce-bogus]),\n.mce-visualblocks:not([dir=rtl]) section,\n.mce-visualblocks:not([dir=rtl]) article,\n.mce-visualblocks:not([dir=rtl]) blockquote,\n.mce-visualblocks:not([dir=rtl]) address,\n.mce-visualblocks:not([dir=rtl]) pre,\n.mce-visualblocks:not([dir=rtl]) figure,\n.mce-visualblocks:not([dir=rtl]) figcaption,\n.mce-visualblocks:not([dir=rtl]) hgroup,\n.mce-visualblocks:not([dir=rtl]) aside,\n.mce-visualblocks:not([dir=rtl]) ul,\n.mce-visualblocks:not([dir=rtl]) ol,\n.mce-visualblocks:not([dir=rtl]) dl {\n  margin-left: 3px;\n}\n.mce-visualblocks[dir=rtl] p,\n.mce-visualblocks[dir=rtl] h1,\n.mce-visualblocks[dir=rtl] h2,\n.mce-visualblocks[dir=rtl] h3,\n.mce-visualblocks[dir=rtl] h4,\n.mce-visualblocks[dir=rtl] h5,\n.mce-visualblocks[dir=rtl] h6,\n.mce-visualblocks[dir=rtl] div:not([data-mce-bogus]),\n.mce-visualblocks[dir=rtl] section,\n.mce-visualblocks[dir=rtl] article,\n.mce-visualblocks[dir=rtl] blockquote,\n.mce-visualblocks[dir=rtl] address,\n.mce-visualblocks[dir=rtl] pre,\n.mce-visualblocks[dir=rtl] figure,\n.mce-visualblocks[dir=rtl] figcaption,\n.mce-visualblocks[dir=rtl] hgroup,\n.mce-visualblocks[dir=rtl] aside,\n.mce-visualblocks[dir=rtl] ul,\n.mce-visualblocks[dir=rtl] ol,\n.mce-visualblocks[dir=rtl] dl {\n  background-position-x: right;\n  margin-right: 3px;\n}\n.mce-nbsp,\n.mce-shy {\n  background: #aaa;\n}\n.mce-shy::after {\n  content: '-';\n}\nbody {\n  font-family: sans-serif;\n}\ntable {\n  border-collapse: collapse;\n}\n"
  },
  {
    "path": "minimalist-vue3/public/tinymce/skins/ui/oxide/content.inline.css",
    "content": ".mce-content-body .mce-item-anchor {\n  background: transparent url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'8'%20height%3D'12'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20d%3D'M0%200L8%200%208%2012%204.09117821%209%200%2012z'%2F%3E%3C%2Fsvg%3E%0A\") no-repeat center;\n}\n.mce-content-body .mce-item-anchor:empty {\n  cursor: default;\n  display: inline-block;\n  height: 12px !important;\n  padding: 0 2px;\n  -webkit-user-modify: read-only;\n  -moz-user-modify: read-only;\n  -webkit-user-select: all;\n  -moz-user-select: all;\n  user-select: all;\n  width: 8px !important;\n}\n.mce-content-body .mce-item-anchor:not(:empty) {\n  background-position-x: 2px;\n  display: inline-block;\n  padding-left: 12px;\n}\n.mce-content-body .mce-item-anchor[data-mce-selected] {\n  outline-offset: 1px;\n}\n.tox-comments-visible .tox-comment[contenteditable=\"false\"]:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment img:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment > audio:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment > video:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment span.mce-preview-object:not([data-mce-selected]) {\n  outline: 3px solid #ffe89d;\n}\n.tox-comments-visible .tox-comment[contenteditable=\"false\"][data-mce-annotation-active=\"true\"]:not([data-mce-selected]) {\n  outline: 3px solid #fed635;\n}\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"] img:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"] > audio:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"] > video:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"] span.mce-preview-object:not([data-mce-selected]) {\n  outline: 3px solid #fed635;\n}\n.tox-comments-visible span.tox-comment:not([data-mce-selected]) {\n  background-color: #ffe89d;\n  outline: none;\n}\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"]:not([data-mce-selected=\"inline-boundary\"]) {\n  background-color: #fed635;\n}\n.tox-checklist > li:not(.tox-checklist--hidden) {\n  list-style: none;\n  margin: 0.25em 0;\n}\n.tox-checklist > li:not(.tox-checklist--hidden)::before {\n  content: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-unchecked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2215%22%20height%3D%2215%22%20x%3D%22.5%22%20y%3D%22.5%22%20fill-rule%3D%22nonzero%22%20stroke%3D%22%234C4C4C%22%20rx%3D%222%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A\");\n  cursor: pointer;\n  height: 1em;\n  margin-left: -1.5em;\n  margin-top: 0.125em;\n  position: absolute;\n  width: 1em;\n}\n.tox-checklist li:not(.tox-checklist--hidden).tox-checklist--checked::before {\n  content: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-checked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22%234099FF%22%20fill-rule%3D%22nonzero%22%20rx%3D%222%22%2F%3E%3Cpath%20id%3D%22Path%22%20fill%3D%22%23FFF%22%20fill-rule%3D%22nonzero%22%20d%3D%22M11.5703186%2C3.14417309%20C11.8516238%2C2.73724603%2012.4164781%2C2.62829933%2012.83558%2C2.89774797%20C13.260121%2C3.17069355%2013.3759736%2C3.72932262%2013.0909105%2C4.14168582%20L7.7580587%2C11.8560195%20C7.43776896%2C12.3193404%206.76483983%2C12.3852142%206.35607322%2C11.9948725%20L3.02491697%2C8.8138662%20C2.66090143%2C8.46625845%202.65798871%2C7.89594698%203.01850234%2C7.54483354%20C3.373942%2C7.19866177%203.94940006%2C7.19592841%204.30829608%2C7.5386474%20L6.85276923%2C9.9684299%20L11.5703186%2C3.14417309%20Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A\");\n}\n[dir=rtl] .tox-checklist > li:not(.tox-checklist--hidden)::before {\n  margin-left: 0;\n  margin-right: -1.5em;\n}\n/* stylelint-disable */\n/* http://prismjs.com/ */\n/**\n * prism.js default theme for JavaScript, CSS and HTML\n * Based on dabblet (http://dabblet.com)\n * @author Lea Verou\n */\ncode[class*=\"language-\"],\npre[class*=\"language-\"] {\n  color: black;\n  background: none;\n  text-shadow: 0 1px white;\n  font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;\n  font-size: 1em;\n  text-align: left;\n  white-space: pre;\n  word-spacing: normal;\n  word-break: normal;\n  word-wrap: normal;\n  line-height: 1.5;\n  -moz-tab-size: 4;\n  tab-size: 4;\n  -webkit-hyphens: none;\n  hyphens: none;\n}\npre[class*=\"language-\"]::-moz-selection,\npre[class*=\"language-\"] ::-moz-selection,\ncode[class*=\"language-\"]::-moz-selection,\ncode[class*=\"language-\"] ::-moz-selection {\n  text-shadow: none;\n  background: #b3d4fc;\n}\npre[class*=\"language-\"]::selection,\npre[class*=\"language-\"] ::selection,\ncode[class*=\"language-\"]::selection,\ncode[class*=\"language-\"] ::selection {\n  text-shadow: none;\n  background: #b3d4fc;\n}\n@media print {\n  code[class*=\"language-\"],\n  pre[class*=\"language-\"] {\n    text-shadow: none;\n  }\n}\n/* Code blocks */\npre[class*=\"language-\"] {\n  padding: 1em;\n  margin: 0.5em 0;\n  overflow: auto;\n}\n:not(pre) > code[class*=\"language-\"],\npre[class*=\"language-\"] {\n  background: #f5f2f0;\n}\n/* Inline code */\n:not(pre) > code[class*=\"language-\"] {\n  padding: 0.1em;\n  border-radius: 0.3em;\n  white-space: normal;\n}\n.token.comment,\n.token.prolog,\n.token.doctype,\n.token.cdata {\n  color: slategray;\n}\n.token.punctuation {\n  color: #999;\n}\n.token.namespace {\n  opacity: 0.7;\n}\n.token.property,\n.token.tag,\n.token.boolean,\n.token.number,\n.token.constant,\n.token.symbol,\n.token.deleted {\n  color: #905;\n}\n.token.selector,\n.token.attr-name,\n.token.string,\n.token.char,\n.token.builtin,\n.token.inserted {\n  color: #690;\n}\n.token.operator,\n.token.entity,\n.token.url,\n.language-css .token.string,\n.style .token.string {\n  color: #9a6e3a;\n  /* This background color was intended by the author of this theme. */\n  background: hsla(0, 0%, 100%, 0.5);\n}\n.token.atrule,\n.token.attr-value,\n.token.keyword {\n  color: #07a;\n}\n.token.function,\n.token.class-name {\n  color: #DD4A68;\n}\n.token.regex,\n.token.important,\n.token.variable {\n  color: #e90;\n}\n.token.important,\n.token.bold {\n  font-weight: bold;\n}\n.token.italic {\n  font-style: italic;\n}\n.token.entity {\n  cursor: help;\n}\n/* stylelint-enable */\n.mce-content-body {\n  overflow-wrap: break-word;\n  word-wrap: break-word;\n}\n.mce-content-body .mce-visual-caret {\n  background-color: black;\n  background-color: currentColor;\n  position: absolute;\n}\n.mce-content-body .mce-visual-caret-hidden {\n  display: none;\n}\n.mce-content-body *[data-mce-caret] {\n  left: -1000px;\n  margin: 0;\n  padding: 0;\n  position: absolute;\n  right: auto;\n  top: 0;\n}\n.mce-content-body .mce-offscreen-selection {\n  left: -2000000px;\n  max-width: 1000000px;\n  position: absolute;\n}\n.mce-content-body *[contentEditable=false] {\n  cursor: default;\n}\n.mce-content-body *[contentEditable=true] {\n  cursor: text;\n}\n.tox-cursor-format-painter {\n  cursor: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A\"), default;\n}\ndiv.mce-footnotes hr {\n  margin-inline-end: auto;\n  margin-inline-start: 0;\n  width: 25%;\n}\ndiv.mce-footnotes li > a.mce-footnotes-backlink {\n  text-decoration: none;\n}\n@media print {\n  sup.mce-footnote a {\n    color: black;\n    text-decoration: none;\n  }\n  div.mce-footnotes {\n    break-inside: avoid;\n    width: 100%;\n  }\n  div.mce-footnotes li > a.mce-footnotes-backlink {\n    display: none;\n  }\n}\n.mce-content-body figure.align-left {\n  float: left;\n}\n.mce-content-body figure.align-right {\n  float: right;\n}\n.mce-content-body figure.image.align-center {\n  display: table;\n  margin-left: auto;\n  margin-right: auto;\n}\n.mce-preview-object {\n  border: 1px solid gray;\n  display: inline-block;\n  line-height: 0;\n  margin: 0 2px 0 2px;\n  position: relative;\n}\n.mce-preview-object .mce-shim {\n  background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);\n  height: 100%;\n  left: 0;\n  position: absolute;\n  top: 0;\n  width: 100%;\n}\n.mce-preview-object[data-mce-selected=\"2\"] .mce-shim {\n  display: none;\n}\n.mce-content-body .mce-mergetag {\n  cursor: default !important;\n  -webkit-user-select: none;\n     -moz-user-select: none;\n          user-select: none;\n}\n.mce-content-body .mce-mergetag:hover {\n  background-color: rgba(0, 108, 231, 0.1);\n}\n.mce-content-body .mce-mergetag-affix {\n  background-color: rgba(0, 108, 231, 0.1);\n  color: #006ce7;\n}\n.mce-object {\n  background: transparent url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%2F%3E%3C%2Fsvg%3E%0A\") no-repeat center;\n  border: 1px dashed #aaa;\n}\n.mce-pagebreak {\n  border: 1px dashed #aaa;\n  cursor: default;\n  display: block;\n  height: 5px;\n  margin-top: 15px;\n  page-break-before: always;\n  width: 100%;\n}\n@media print {\n  .mce-pagebreak {\n    border: 0;\n  }\n}\n.tiny-pageembed .mce-shim {\n  background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);\n  height: 100%;\n  left: 0;\n  position: absolute;\n  top: 0;\n  width: 100%;\n}\n.tiny-pageembed[data-mce-selected=\"2\"] .mce-shim {\n  display: none;\n}\n.tiny-pageembed {\n  display: inline-block;\n  position: relative;\n}\n.tiny-pageembed--21by9,\n.tiny-pageembed--16by9,\n.tiny-pageembed--4by3,\n.tiny-pageembed--1by1 {\n  display: block;\n  overflow: hidden;\n  padding: 0;\n  position: relative;\n  width: 100%;\n}\n.tiny-pageembed--21by9 {\n  padding-top: 42.857143%;\n}\n.tiny-pageembed--16by9 {\n  padding-top: 56.25%;\n}\n.tiny-pageembed--4by3 {\n  padding-top: 75%;\n}\n.tiny-pageembed--1by1 {\n  padding-top: 100%;\n}\n.tiny-pageembed--21by9 iframe,\n.tiny-pageembed--16by9 iframe,\n.tiny-pageembed--4by3 iframe,\n.tiny-pageembed--1by1 iframe {\n  border: 0;\n  height: 100%;\n  left: 0;\n  position: absolute;\n  top: 0;\n  width: 100%;\n}\n.mce-content-body[data-mce-placeholder] {\n  position: relative;\n}\n.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before {\n  color: rgba(34, 47, 62, 0.7);\n  content: attr(data-mce-placeholder);\n  position: absolute;\n}\n.mce-content-body:not([dir=rtl])[data-mce-placeholder]:not(.mce-visualblocks)::before {\n  left: 1px;\n}\n.mce-content-body[dir=rtl][data-mce-placeholder]:not(.mce-visualblocks)::before {\n  right: 1px;\n}\n.mce-content-body div.mce-resizehandle {\n  background-color: #4099ff;\n  border-color: #4099ff;\n  border-style: solid;\n  border-width: 1px;\n  box-sizing: border-box;\n  height: 10px;\n  position: absolute;\n  width: 10px;\n  z-index: 1298;\n}\n.mce-content-body div.mce-resizehandle:hover {\n  background-color: #4099ff;\n}\n.mce-content-body div.mce-resizehandle:nth-of-type(1) {\n  cursor: nwse-resize;\n}\n.mce-content-body div.mce-resizehandle:nth-of-type(2) {\n  cursor: nesw-resize;\n}\n.mce-content-body div.mce-resizehandle:nth-of-type(3) {\n  cursor: nwse-resize;\n}\n.mce-content-body div.mce-resizehandle:nth-of-type(4) {\n  cursor: nesw-resize;\n}\n.mce-content-body .mce-resize-backdrop {\n  z-index: 10000;\n}\n.mce-content-body .mce-clonedresizable {\n  cursor: default;\n  opacity: 0.5;\n  outline: 1px dashed black;\n  position: absolute;\n  z-index: 10001;\n}\n.mce-content-body .mce-clonedresizable.mce-resizetable-columns th,\n.mce-content-body .mce-clonedresizable.mce-resizetable-columns td {\n  border: 0;\n}\n.mce-content-body .mce-resize-helper {\n  background: #555;\n  background: rgba(0, 0, 0, 0.75);\n  border: 1px;\n  border-radius: 3px;\n  color: white;\n  display: none;\n  font-family: sans-serif;\n  font-size: 12px;\n  line-height: 14px;\n  margin: 5px 10px;\n  padding: 5px;\n  position: absolute;\n  white-space: nowrap;\n  z-index: 10002;\n}\n.tox-rtc-user-selection {\n  position: relative;\n}\n.tox-rtc-user-cursor {\n  bottom: 0;\n  cursor: default;\n  position: absolute;\n  top: 0;\n  width: 2px;\n}\n.tox-rtc-user-cursor::before {\n  background-color: inherit;\n  border-radius: 50%;\n  content: '';\n  display: block;\n  height: 8px;\n  position: absolute;\n  right: -3px;\n  top: -3px;\n  width: 8px;\n}\n.tox-rtc-user-cursor:hover::after {\n  background-color: inherit;\n  border-radius: 100px;\n  box-sizing: border-box;\n  color: #fff;\n  content: attr(data-user);\n  display: block;\n  font-size: 12px;\n  font-weight: bold;\n  left: -5px;\n  min-height: 8px;\n  min-width: 8px;\n  padding: 0 12px;\n  position: absolute;\n  top: -11px;\n  white-space: nowrap;\n  z-index: 1000;\n}\n.tox-rtc-user-selection--1 .tox-rtc-user-cursor {\n  background-color: #2dc26b;\n}\n.tox-rtc-user-selection--2 .tox-rtc-user-cursor {\n  background-color: #e03e2d;\n}\n.tox-rtc-user-selection--3 .tox-rtc-user-cursor {\n  background-color: #f1c40f;\n}\n.tox-rtc-user-selection--4 .tox-rtc-user-cursor {\n  background-color: #3598db;\n}\n.tox-rtc-user-selection--5 .tox-rtc-user-cursor {\n  background-color: #b96ad9;\n}\n.tox-rtc-user-selection--6 .tox-rtc-user-cursor {\n  background-color: #e67e23;\n}\n.tox-rtc-user-selection--7 .tox-rtc-user-cursor {\n  background-color: #aaa69d;\n}\n.tox-rtc-user-selection--8 .tox-rtc-user-cursor {\n  background-color: #f368e0;\n}\n.tox-rtc-remote-image {\n  background: #eaeaea url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%2236%22%20height%3D%2212%22%20viewBox%3D%220%200%2036%2012%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%3Ccircle%20cx%3D%226%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2218%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.33s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2230%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.66s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%3C%2Fsvg%3E%0A\") no-repeat center center;\n  border: 1px solid #ccc;\n  min-height: 240px;\n  min-width: 320px;\n}\n.mce-match-marker {\n  background: #aaa;\n  color: #fff;\n}\n.mce-match-marker-selected {\n  background: #39f;\n  color: #fff;\n}\n.mce-match-marker-selected::-moz-selection {\n  background: #39f;\n  color: #fff;\n}\n.mce-match-marker-selected::selection {\n  background: #39f;\n  color: #fff;\n}\n.mce-content-body img[data-mce-selected],\n.mce-content-body video[data-mce-selected],\n.mce-content-body audio[data-mce-selected],\n.mce-content-body object[data-mce-selected],\n.mce-content-body embed[data-mce-selected],\n.mce-content-body table[data-mce-selected],\n.mce-content-body details[data-mce-selected] {\n  outline: 3px solid #b4d7ff;\n}\n.mce-content-body hr[data-mce-selected] {\n  outline: 3px solid #b4d7ff;\n  outline-offset: 1px;\n}\n.mce-content-body *[contentEditable=false] *[contentEditable=true]:focus {\n  outline: 3px solid #b4d7ff;\n}\n.mce-content-body *[contentEditable=false] *[contentEditable=true]:hover {\n  outline: 3px solid #b4d7ff;\n}\n.mce-content-body *[contentEditable=false][data-mce-selected] {\n  cursor: not-allowed;\n  outline: 3px solid #b4d7ff;\n}\n.mce-content-body.mce-content-readonly *[contentEditable=true]:focus,\n.mce-content-body.mce-content-readonly *[contentEditable=true]:hover {\n  outline: none;\n}\n.mce-content-body *[data-mce-selected=\"inline-boundary\"] {\n  background-color: #b4d7ff;\n}\n.mce-content-body .mce-edit-focus {\n  outline: 3px solid #b4d7ff;\n}\n.mce-content-body td[data-mce-selected],\n.mce-content-body th[data-mce-selected] {\n  position: relative;\n}\n.mce-content-body td[data-mce-selected]::-moz-selection,\n.mce-content-body th[data-mce-selected]::-moz-selection {\n  background: none;\n}\n.mce-content-body td[data-mce-selected]::selection,\n.mce-content-body th[data-mce-selected]::selection {\n  background: none;\n}\n.mce-content-body td[data-mce-selected] *,\n.mce-content-body th[data-mce-selected] * {\n  outline: none;\n  -webkit-touch-callout: none;\n  -webkit-user-select: none;\n     -moz-user-select: none;\n          user-select: none;\n}\n.mce-content-body td[data-mce-selected]::after,\n.mce-content-body th[data-mce-selected]::after {\n  background-color: rgba(180, 215, 255, 0.7);\n  border: 1px solid rgba(180, 215, 255, 0.7);\n  bottom: -1px;\n  content: '';\n  left: -1px;\n  mix-blend-mode: multiply;\n  position: absolute;\n  right: -1px;\n  top: -1px;\n}\n@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {\n  .mce-content-body td[data-mce-selected]::after,\n  .mce-content-body th[data-mce-selected]::after {\n    border-color: rgba(0, 84, 180, 0.7);\n  }\n}\n.mce-content-body img[data-mce-selected]::-moz-selection {\n  background: none;\n}\n.mce-content-body img[data-mce-selected]::selection {\n  background: none;\n}\n.ephox-snooker-resizer-bar {\n  background-color: #b4d7ff;\n  opacity: 0;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  user-select: none;\n}\n.ephox-snooker-resizer-cols {\n  cursor: col-resize;\n}\n.ephox-snooker-resizer-rows {\n  cursor: row-resize;\n}\n.ephox-snooker-resizer-bar.ephox-snooker-resizer-bar-dragging {\n  opacity: 1;\n}\n.mce-spellchecker-word {\n  background-image: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%23ff0000'%20fill%3D'none'%20stroke-linecap%3D'round'%20stroke-opacity%3D'.75'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A\");\n  background-position: 0 calc(100% + 1px);\n  background-repeat: repeat-x;\n  background-size: auto 6px;\n  cursor: default;\n  height: 2rem;\n}\n.mce-spellchecker-grammar {\n  background-image: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%2300A835'%20fill%3D'none'%20stroke-linecap%3D'round'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A\");\n  background-position: 0 calc(100% + 1px);\n  background-repeat: repeat-x;\n  background-size: auto 6px;\n  cursor: default;\n}\n.mce-toc {\n  border: 1px solid gray;\n}\n.mce-toc h2 {\n  margin: 4px;\n}\n.mce-toc li {\n  list-style-type: none;\n}\n[data-mce-block] {\n  display: block;\n}\ntable[style*=\"border-width: 0px\"],\n.mce-item-table:not([border]),\n.mce-item-table[border=\"0\"],\ntable[style*=\"border-width: 0px\"] td,\n.mce-item-table:not([border]) td,\n.mce-item-table[border=\"0\"] td,\ntable[style*=\"border-width: 0px\"] th,\n.mce-item-table:not([border]) th,\n.mce-item-table[border=\"0\"] th,\ntable[style*=\"border-width: 0px\"] caption,\n.mce-item-table:not([border]) caption,\n.mce-item-table[border=\"0\"] caption {\n  border: 1px dashed #bbb;\n}\n.mce-visualblocks p,\n.mce-visualblocks h1,\n.mce-visualblocks h2,\n.mce-visualblocks h3,\n.mce-visualblocks h4,\n.mce-visualblocks h5,\n.mce-visualblocks h6,\n.mce-visualblocks div:not([data-mce-bogus]),\n.mce-visualblocks section,\n.mce-visualblocks article,\n.mce-visualblocks blockquote,\n.mce-visualblocks address,\n.mce-visualblocks pre,\n.mce-visualblocks figure,\n.mce-visualblocks figcaption,\n.mce-visualblocks hgroup,\n.mce-visualblocks aside,\n.mce-visualblocks ul,\n.mce-visualblocks ol,\n.mce-visualblocks dl {\n  background-repeat: no-repeat;\n  border: 1px dashed #bbb;\n  margin-left: 3px;\n  padding-top: 10px;\n}\n.mce-visualblocks p {\n  background-image: url(data:image/gif;base64,R0lGODlhCQAJAJEAAAAAAP///7u7u////yH5BAEAAAMALAAAAAAJAAkAAAIQnG+CqCN/mlyvsRUpThG6AgA7);\n}\n.mce-visualblocks h1 {\n  background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGu1JuxHoAfRNRW3TWXyF2YiRUAOw==);\n}\n.mce-visualblocks h2 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8Hybbx4oOuqgTynJd6bGlWg3DkJzoaUAAAOw==);\n}\n.mce-visualblocks h3 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIZjI8Hybbx4oOuqgTynJf2Ln2NOHpQpmhAAQA7);\n}\n.mce-visualblocks h4 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxInR0zqeAdhtJlXwV1oCll2HaWgAAOw==);\n}\n.mce-visualblocks h5 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjane4iq5GlW05GgIkIZUAAAOw==);\n}\n.mce-visualblocks h6 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjan04jep1iZ1XRlAo5bVgAAOw==);\n}\n.mce-visualblocks div:not([data-mce-bogus]) {\n  background-image: url(data:image/gif;base64,R0lGODlhEgAKAIABALu7u////yH5BAEAAAEALAAAAAASAAoAAAIfjI9poI0cgDywrhuxfbrzDEbQM2Ei5aRjmoySW4pAAQA7);\n}\n.mce-visualblocks section {\n  background-image: url(data:image/gif;base64,R0lGODlhKAAKAIABALu7u////yH5BAEAAAEALAAAAAAoAAoAAAI5jI+pywcNY3sBWHdNrplytD2ellDeSVbp+GmWqaDqDMepc8t17Y4vBsK5hDyJMcI6KkuYU+jpjLoKADs=);\n}\n.mce-visualblocks article {\n  background-image: url(data:image/gif;base64,R0lGODlhKgAKAIABALu7u////yH5BAEAAAEALAAAAAAqAAoAAAI6jI+pywkNY3wG0GBvrsd2tXGYSGnfiF7ikpXemTpOiJScasYoDJJrjsG9gkCJ0ag6KhmaIe3pjDYBBQA7);\n}\n.mce-visualblocks blockquote {\n  background-image: url(data:image/gif;base64,R0lGODlhPgAKAIABALu7u////yH5BAEAAAEALAAAAAA+AAoAAAJPjI+py+0Knpz0xQDyuUhvfoGgIX5iSKZYgq5uNL5q69asZ8s5rrf0yZmpNkJZzFesBTu8TOlDVAabUyatguVhWduud3EyiUk45xhTTgMBBQA7);\n}\n.mce-visualblocks address {\n  background-image: url(data:image/gif;base64,R0lGODlhLQAKAIABALu7u////yH5BAEAAAEALAAAAAAtAAoAAAI/jI+pywwNozSP1gDyyZcjb3UaRpXkWaXmZW4OqKLhBmLs+K263DkJK7OJeifh7FicKD9A1/IpGdKkyFpNmCkAADs=);\n}\n.mce-visualblocks pre {\n  background-image: url(data:image/gif;base64,R0lGODlhFQAKAIABALu7uwAAACH5BAEAAAEALAAAAAAVAAoAAAIjjI+ZoN0cgDwSmnpz1NCueYERhnibZVKLNnbOq8IvKpJtVQAAOw==);\n}\n.mce-visualblocks figure {\n  background-image: url(data:image/gif;base64,R0lGODlhJAAKAIAAALu7u////yH5BAEAAAEALAAAAAAkAAoAAAI0jI+py+2fwAHUSFvD3RlvG4HIp4nX5JFSpnZUJ6LlrM52OE7uSWosBHScgkSZj7dDKnWAAgA7);\n}\n.mce-visualblocks figcaption {\n  border: 1px dashed #bbb;\n}\n.mce-visualblocks hgroup {\n  background-image: url(data:image/gif;base64,R0lGODlhJwAKAIABALu7uwAAACH5BAEAAAEALAAAAAAnAAoAAAI3jI+pywYNI3uB0gpsRtt5fFnfNZaVSYJil4Wo03Hv6Z62uOCgiXH1kZIIJ8NiIxRrAZNMZAtQAAA7);\n}\n.mce-visualblocks aside {\n  background-image: url(data:image/gif;base64,R0lGODlhHgAKAIABAKqqqv///yH5BAEAAAEALAAAAAAeAAoAAAItjI+pG8APjZOTzgtqy7I3f1yehmQcFY4WKZbqByutmW4aHUd6vfcVbgudgpYCADs=);\n}\n.mce-visualblocks ul {\n  background-image: url(data:image/gif;base64,R0lGODlhDQAKAIAAALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGuYnqUVSjvw26DzzXiqIDlVwAAOw==);\n}\n.mce-visualblocks ol {\n  background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybH6HHt0qourxC6CvzXieHyeWQAAOw==);\n}\n.mce-visualblocks dl {\n  background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybEOnmOvUoWznTqeuEjNSCqeGRUAOw==);\n}\n.mce-visualblocks:not([dir=rtl]) p,\n.mce-visualblocks:not([dir=rtl]) h1,\n.mce-visualblocks:not([dir=rtl]) h2,\n.mce-visualblocks:not([dir=rtl]) h3,\n.mce-visualblocks:not([dir=rtl]) h4,\n.mce-visualblocks:not([dir=rtl]) h5,\n.mce-visualblocks:not([dir=rtl]) h6,\n.mce-visualblocks:not([dir=rtl]) div:not([data-mce-bogus]),\n.mce-visualblocks:not([dir=rtl]) section,\n.mce-visualblocks:not([dir=rtl]) article,\n.mce-visualblocks:not([dir=rtl]) blockquote,\n.mce-visualblocks:not([dir=rtl]) address,\n.mce-visualblocks:not([dir=rtl]) pre,\n.mce-visualblocks:not([dir=rtl]) figure,\n.mce-visualblocks:not([dir=rtl]) figcaption,\n.mce-visualblocks:not([dir=rtl]) hgroup,\n.mce-visualblocks:not([dir=rtl]) aside,\n.mce-visualblocks:not([dir=rtl]) ul,\n.mce-visualblocks:not([dir=rtl]) ol,\n.mce-visualblocks:not([dir=rtl]) dl {\n  margin-left: 3px;\n}\n.mce-visualblocks[dir=rtl] p,\n.mce-visualblocks[dir=rtl] h1,\n.mce-visualblocks[dir=rtl] h2,\n.mce-visualblocks[dir=rtl] h3,\n.mce-visualblocks[dir=rtl] h4,\n.mce-visualblocks[dir=rtl] h5,\n.mce-visualblocks[dir=rtl] h6,\n.mce-visualblocks[dir=rtl] div:not([data-mce-bogus]),\n.mce-visualblocks[dir=rtl] section,\n.mce-visualblocks[dir=rtl] article,\n.mce-visualblocks[dir=rtl] blockquote,\n.mce-visualblocks[dir=rtl] address,\n.mce-visualblocks[dir=rtl] pre,\n.mce-visualblocks[dir=rtl] figure,\n.mce-visualblocks[dir=rtl] figcaption,\n.mce-visualblocks[dir=rtl] hgroup,\n.mce-visualblocks[dir=rtl] aside,\n.mce-visualblocks[dir=rtl] ul,\n.mce-visualblocks[dir=rtl] ol,\n.mce-visualblocks[dir=rtl] dl {\n  background-position-x: right;\n  margin-right: 3px;\n}\n.mce-nbsp,\n.mce-shy {\n  background: #aaa;\n}\n.mce-shy::after {\n  content: '-';\n}\n"
  },
  {
    "path": "minimalist-vue3/public/tinymce/skins/ui/oxide/skin.css",
    "content": ".tox {\n  box-shadow: none;\n  box-sizing: content-box;\n  color: #222f3e;\n  cursor: auto;\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n  font-size: 16px;\n  font-style: normal;\n  font-weight: normal;\n  line-height: normal;\n  -webkit-tap-highlight-color: transparent;\n  text-decoration: none;\n  text-shadow: none;\n  text-transform: none;\n  vertical-align: initial;\n  white-space: normal;\n}\n.tox *:not(svg):not(rect) {\n  box-sizing: inherit;\n  color: inherit;\n  cursor: inherit;\n  direction: inherit;\n  font-family: inherit;\n  font-size: inherit;\n  font-style: inherit;\n  font-weight: inherit;\n  line-height: inherit;\n  -webkit-tap-highlight-color: inherit;\n  text-align: inherit;\n  text-decoration: inherit;\n  text-shadow: inherit;\n  text-transform: inherit;\n  vertical-align: inherit;\n  white-space: inherit;\n}\n.tox *:not(svg):not(rect) {\n  /* stylelint-disable-line no-duplicate-selectors */\n  background: transparent;\n  border: 0;\n  box-shadow: none;\n  float: none;\n  height: auto;\n  margin: 0;\n  max-width: none;\n  outline: 0;\n  padding: 0;\n  position: static;\n  width: auto;\n}\n.tox:not([dir=rtl]) {\n  direction: ltr;\n  text-align: left;\n}\n.tox[dir=rtl] {\n  direction: rtl;\n  text-align: right;\n}\n.tox-tinymce {\n  border: 2px solid #eeeeee;\n  border-radius: 10px;\n  box-shadow: none;\n  box-sizing: border-box;\n  display: flex;\n  flex-direction: column;\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n  overflow: hidden;\n  position: relative;\n  visibility: inherit !important;\n}\n.tox.tox-tinymce-inline {\n  border: none;\n  box-shadow: none;\n  overflow: initial;\n}\n.tox.tox-tinymce-inline .tox-editor-container {\n  overflow: initial;\n}\n.tox.tox-tinymce-inline .tox-editor-header {\n  background-color: #fff;\n  border: 2px solid #eeeeee;\n  border-radius: 10px;\n  box-shadow: none;\n  overflow: hidden;\n}\n.tox-tinymce-aux {\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n  z-index: 1300;\n}\n.tox-tinymce *:focus,\n.tox-tinymce-aux *:focus {\n  outline: none;\n}\nbutton::-moz-focus-inner {\n  border: 0;\n}\n.tox[dir=rtl] .tox-icon--flip svg {\n  transform: rotateY(180deg);\n}\n.tox .accessibility-issue__header {\n  align-items: center;\n  display: flex;\n  margin-bottom: 4px;\n}\n.tox .accessibility-issue__description {\n  align-items: stretch;\n  border-radius: 6px;\n  display: flex;\n  justify-content: space-between;\n}\n.tox .accessibility-issue__description > div {\n  padding-bottom: 4px;\n}\n.tox .accessibility-issue__description > div > div {\n  align-items: center;\n  display: flex;\n  margin-bottom: 4px;\n}\n.tox .accessibility-issue__description > div > div .tox-icon svg {\n  display: block;\n}\n.tox .accessibility-issue__repair {\n  margin-top: 16px;\n}\n.tox .tox-dialog__body-content .accessibility-issue--info .accessibility-issue__description {\n  background-color: rgba(0, 101, 216, 0.1);\n  color: #222f3e;\n}\n.tox .tox-dialog__body-content .accessibility-issue--info .tox-form__group h2 {\n  color: #006ce7;\n}\n.tox .tox-dialog__body-content .accessibility-issue--info .tox-icon svg {\n  fill: #006ce7;\n}\n.tox .tox-dialog__body-content .accessibility-issue--info a.tox-button--naked.tox-button--icon {\n  background-color: #006ce7;\n  color: #fff;\n}\n.tox .tox-dialog__body-content .accessibility-issue--info a.tox-button--naked.tox-button--icon:hover,\n.tox .tox-dialog__body-content .accessibility-issue--info a.tox-button--naked.tox-button--icon:focus {\n  background-color: #0060ce;\n}\n.tox .tox-dialog__body-content .accessibility-issue--info a.tox-button--naked.tox-button--icon:active {\n  background-color: #0054b4;\n}\n.tox .tox-dialog__body-content .accessibility-issue--warn {\n  /* stylelint-disable-next-line no-descending-specificity */\n}\n.tox .tox-dialog__body-content .accessibility-issue--warn .accessibility-issue__description {\n  background-color: rgba(255, 165, 0, 0.08);\n  color: #222f3e;\n}\n.tox .tox-dialog__body-content .accessibility-issue--warn .tox-form__group h2 {\n  color: #8f5d00;\n}\n.tox .tox-dialog__body-content .accessibility-issue--warn .tox-icon svg {\n  fill: #8f5d00;\n}\n.tox .tox-dialog__body-content .accessibility-issue--warn a.tox-button--naked.tox-button--icon {\n  background-color: #FFE89D;\n  color: #222f3e;\n}\n.tox .tox-dialog__body-content .accessibility-issue--warn a.tox-button--naked.tox-button--icon:hover,\n.tox .tox-dialog__body-content .accessibility-issue--warn a.tox-button--naked.tox-button--icon:focus {\n  background-color: #F2D574;\n  color: #222f3e;\n}\n.tox .tox-dialog__body-content .accessibility-issue--warn a.tox-button--naked.tox-button--icon:active {\n  background-color: #E8C657;\n  color: #222f3e;\n}\n.tox .tox-dialog__body-content .accessibility-issue--error {\n  /* stylelint-disable-next-line no-descending-specificity */\n}\n.tox .tox-dialog__body-content .accessibility-issue--error .accessibility-issue__description {\n  background-color: rgba(204, 0, 0, 0.1);\n  color: #222f3e;\n}\n.tox .tox-dialog__body-content .accessibility-issue--error .tox-form__group h2 {\n  color: #c00;\n}\n.tox .tox-dialog__body-content .accessibility-issue--error .tox-icon svg {\n  fill: #c00;\n}\n.tox .tox-dialog__body-content .accessibility-issue--error a.tox-button--naked.tox-button--icon {\n  background-color: #F2BFBF;\n  color: #222f3e;\n}\n.tox .tox-dialog__body-content .accessibility-issue--error a.tox-button--naked.tox-button--icon:hover,\n.tox .tox-dialog__body-content .accessibility-issue--error a.tox-button--naked.tox-button--icon:focus {\n  background-color: #E9A4A4;\n  color: #222f3e;\n}\n.tox .tox-dialog__body-content .accessibility-issue--error a.tox-button--naked.tox-button--icon:active {\n  background-color: #EE9494;\n  color: #222f3e;\n}\n.tox .tox-dialog__body-content .accessibility-issue--success .accessibility-issue__description {\n  background-color: rgba(120, 171, 70, 0.1);\n  color: #222f3e;\n}\n.tox .tox-dialog__body-content .accessibility-issue--success .accessibility-issue__description > *:last-child {\n  display: none;\n}\n.tox .tox-dialog__body-content .accessibility-issue--success .tox-form__group h2 {\n  color: #527530;\n}\n.tox .tox-dialog__body-content .accessibility-issue--success .tox-icon svg {\n  fill: #527530;\n}\n.tox .tox-dialog__body-content .accessibility-issue__header .tox-form__group h1,\n.tox .tox-dialog__body-content .tox-form__group .accessibility-issue__description h2 {\n  font-size: 14px;\n  margin-top: 0;\n}\n.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__header .tox-button {\n  margin-left: 4px;\n}\n.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__header > *:nth-last-child(2) {\n  margin-left: auto;\n}\n.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__description {\n  padding: 4px 4px 4px 8px;\n}\n.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__header .tox-button {\n  margin-right: 4px;\n}\n.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__header > *:nth-last-child(2) {\n  margin-right: auto;\n}\n.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__description {\n  padding: 4px 8px 4px 4px;\n}\n.tox .tox-advtemplate .tox-form__grid {\n  flex: 1;\n}\n.tox .tox-advtemplate .tox-form__grid > div:first-child {\n  display: flex;\n  flex-direction: column;\n  width: 30%;\n}\n.tox .tox-advtemplate .tox-form__grid > div:first-child > div:nth-child(2) {\n  flex-basis: 0;\n  flex-grow: 1;\n  overflow: auto;\n}\n@media only screen and (max-width: 767px ) {\n  body:not(.tox-force-desktop) .tox .tox-advtemplate .tox-form__grid > div:first-child {\n    width: 100%;\n  }\n}\n.tox .tox-advtemplate iframe {\n  border-color: #eeeeee;\n  border-radius: 10px;\n  border-style: solid;\n  border-width: 1px;\n  margin: 0 10px;\n}\n.tox .tox-anchorbar {\n  display: flex;\n  flex: 0 0 auto;\n}\n.tox .tox-bottom-anchorbar {\n  display: flex;\n  flex: 0 0 auto;\n}\n.tox .tox-bar {\n  display: flex;\n  flex: 0 0 auto;\n}\n.tox .tox-button {\n  background-color: #006ce7;\n  background-image: none;\n  background-position: 0 0;\n  background-repeat: repeat;\n  border-color: #006ce7;\n  border-radius: 6px;\n  border-style: solid;\n  border-width: 1px;\n  box-shadow: none;\n  box-sizing: border-box;\n  color: #fff;\n  cursor: pointer;\n  display: inline-block;\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: bold;\n  letter-spacing: normal;\n  line-height: 24px;\n  margin: 0;\n  outline: none;\n  padding: 4px 16px;\n  position: relative;\n  text-align: center;\n  text-decoration: none;\n  text-transform: none;\n  white-space: nowrap;\n}\n.tox .tox-button::before {\n  border-radius: 6px;\n  bottom: -1px;\n  box-shadow: inset 0 0 0 2px #fff, 0 0 0 1px #006ce7, 0 0 0 3px rgba(0, 108, 231, 0.25);\n  content: '';\n  left: -1px;\n  opacity: 0;\n  pointer-events: none;\n  position: absolute;\n  right: -1px;\n  top: -1px;\n}\n.tox .tox-button[disabled] {\n  background-color: #006ce7;\n  background-image: none;\n  border-color: #006ce7;\n  box-shadow: none;\n  color: rgba(255, 255, 255, 0.5);\n  cursor: not-allowed;\n}\n.tox .tox-button:focus:not(:disabled) {\n  background-color: #0060ce;\n  background-image: none;\n  border-color: #0060ce;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button:focus-visible:not(:disabled)::before {\n  opacity: 1;\n}\n.tox .tox-button:hover:not(:disabled) {\n  background-color: #0060ce;\n  background-image: none;\n  border-color: #0060ce;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button:active:not(:disabled) {\n  background-color: #0054b4;\n  background-image: none;\n  border-color: #0054b4;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button.tox-button--enabled {\n  background-color: #0054b4;\n  background-image: none;\n  border-color: #0054b4;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button.tox-button--enabled[disabled] {\n  background-color: #0054b4;\n  background-image: none;\n  border-color: #0054b4;\n  box-shadow: none;\n  color: rgba(255, 255, 255, 0.5);\n  cursor: not-allowed;\n}\n.tox .tox-button.tox-button--enabled:focus:not(:disabled) {\n  background-color: #00489b;\n  background-image: none;\n  border-color: #00489b;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button.tox-button--enabled:hover:not(:disabled) {\n  background-color: #00489b;\n  background-image: none;\n  border-color: #00489b;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button.tox-button--enabled:active:not(:disabled) {\n  background-color: #003c81;\n  background-image: none;\n  border-color: #003c81;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button--icon-and-text,\n.tox .tox-button.tox-button--icon-and-text,\n.tox .tox-button.tox-button--secondary.tox-button--icon-and-text {\n  display: flex;\n  padding: 5px 4px;\n}\n.tox .tox-button--icon-and-text .tox-icon svg,\n.tox .tox-button.tox-button--icon-and-text .tox-icon svg,\n.tox .tox-button.tox-button--secondary.tox-button--icon-and-text .tox-icon svg {\n  display: block;\n  fill: currentColor;\n}\n.tox .tox-button--secondary {\n  background-color: #f0f0f0;\n  background-image: none;\n  background-position: 0 0;\n  background-repeat: repeat;\n  border-color: #f0f0f0;\n  border-radius: 6px;\n  border-style: solid;\n  border-width: 1px;\n  box-shadow: none;\n  color: #222f3e;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: bold;\n  letter-spacing: normal;\n  outline: none;\n  padding: 4px 16px;\n  text-decoration: none;\n  text-transform: none;\n}\n.tox .tox-button--secondary[disabled] {\n  background-color: #f0f0f0;\n  background-image: none;\n  border-color: #f0f0f0;\n  box-shadow: none;\n  color: rgba(34, 47, 62, 0.5);\n}\n.tox .tox-button--secondary:focus:not(:disabled) {\n  background-color: #e3e3e3;\n  background-image: none;\n  border-color: #e3e3e3;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-button--secondary:hover:not(:disabled) {\n  background-color: #e3e3e3;\n  background-image: none;\n  border-color: #e3e3e3;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-button--secondary:active:not(:disabled) {\n  background-color: #d6d6d6;\n  background-image: none;\n  border-color: #d6d6d6;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-button--secondary.tox-button--enabled {\n  background-color: #a8c8ed;\n  background-image: none;\n  border-color: #a8c8ed;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-button--secondary.tox-button--enabled[disabled] {\n  background-color: #a8c8ed;\n  background-image: none;\n  border-color: #a8c8ed;\n  box-shadow: none;\n  color: rgba(34, 47, 62, 0.5);\n}\n.tox .tox-button--secondary.tox-button--enabled:focus:not(:disabled) {\n  background-color: #93bbe9;\n  background-image: none;\n  border-color: #93bbe9;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-button--secondary.tox-button--enabled:hover:not(:disabled) {\n  background-color: #93bbe9;\n  background-image: none;\n  border-color: #93bbe9;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-button--secondary.tox-button--enabled:active:not(:disabled) {\n  background-color: #7daee4;\n  background-image: none;\n  border-color: #7daee4;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-button--icon,\n.tox .tox-button.tox-button--icon,\n.tox .tox-button.tox-button--secondary.tox-button--icon {\n  padding: 4px;\n}\n.tox .tox-button--icon .tox-icon svg,\n.tox .tox-button.tox-button--icon .tox-icon svg,\n.tox .tox-button.tox-button--secondary.tox-button--icon .tox-icon svg {\n  display: block;\n  fill: currentColor;\n}\n.tox .tox-button-link {\n  background: 0;\n  border: none;\n  box-sizing: border-box;\n  cursor: pointer;\n  display: inline-block;\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n  font-size: 16px;\n  font-weight: normal;\n  line-height: 1.3;\n  margin: 0;\n  padding: 0;\n  white-space: nowrap;\n}\n.tox .tox-button-link--sm {\n  font-size: 14px;\n}\n.tox .tox-button--naked {\n  background-color: transparent;\n  border-color: transparent;\n  box-shadow: unset;\n  color: #222f3e;\n}\n.tox .tox-button--naked[disabled] {\n  background-color: rgba(34, 47, 62, 0.12);\n  border-color: transparent;\n  box-shadow: unset;\n  color: rgba(34, 47, 62, 0.5);\n}\n.tox .tox-button--naked:hover:not(:disabled) {\n  background-color: rgba(34, 47, 62, 0.12);\n  border-color: transparent;\n  box-shadow: unset;\n  color: #222f3e;\n}\n.tox .tox-button--naked:focus:not(:disabled) {\n  background-color: rgba(34, 47, 62, 0.12);\n  border-color: transparent;\n  box-shadow: unset;\n  color: #222f3e;\n}\n.tox .tox-button--naked:active:not(:disabled) {\n  background-color: rgba(34, 47, 62, 0.18);\n  border-color: transparent;\n  box-shadow: unset;\n  color: #222f3e;\n}\n.tox .tox-button--naked .tox-icon svg {\n  fill: currentColor;\n}\n.tox .tox-button--naked.tox-button--icon:hover:not(:disabled) {\n  color: #222f3e;\n}\n.tox .tox-checkbox {\n  align-items: center;\n  border-radius: 6px;\n  cursor: pointer;\n  display: flex;\n  height: 36px;\n  min-width: 36px;\n}\n.tox .tox-checkbox__input {\n  /* Hide from view but visible to screen readers */\n  height: 1px;\n  overflow: hidden;\n  position: absolute;\n  top: auto;\n  width: 1px;\n}\n.tox .tox-checkbox__icons {\n  align-items: center;\n  border-radius: 6px;\n  box-shadow: 0 0 0 2px transparent;\n  box-sizing: content-box;\n  display: flex;\n  height: 24px;\n  justify-content: center;\n  padding: calc(4px - 1px);\n  width: 24px;\n}\n.tox .tox-checkbox__icons .tox-checkbox-icon__unchecked svg {\n  display: block;\n  fill: rgba(34, 47, 62, 0.3);\n}\n.tox .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg {\n  display: none;\n  fill: #006ce7;\n}\n.tox .tox-checkbox__icons .tox-checkbox-icon__checked svg {\n  display: none;\n  fill: #006ce7;\n}\n.tox .tox-checkbox--disabled {\n  color: rgba(34, 47, 62, 0.5);\n  cursor: not-allowed;\n}\n.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__checked svg {\n  fill: rgba(34, 47, 62, 0.5);\n}\n.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__unchecked svg {\n  fill: rgba(34, 47, 62, 0.5);\n}\n.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg {\n  fill: rgba(34, 47, 62, 0.5);\n}\n.tox input.tox-checkbox__input:checked + .tox-checkbox__icons .tox-checkbox-icon__unchecked svg {\n  display: none;\n}\n.tox input.tox-checkbox__input:checked + .tox-checkbox__icons .tox-checkbox-icon__checked svg {\n  display: block;\n}\n.tox input.tox-checkbox__input:indeterminate + .tox-checkbox__icons .tox-checkbox-icon__unchecked svg {\n  display: none;\n}\n.tox input.tox-checkbox__input:indeterminate + .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg {\n  display: block;\n}\n.tox input.tox-checkbox__input:focus + .tox-checkbox__icons {\n  border-radius: 6px;\n  box-shadow: inset 0 0 0 1px #006ce7;\n  padding: calc(4px - 1px);\n}\n.tox:not([dir=rtl]) .tox-checkbox__label {\n  margin-left: 4px;\n}\n.tox:not([dir=rtl]) .tox-checkbox__input {\n  left: -10000px;\n}\n.tox:not([dir=rtl]) .tox-bar .tox-checkbox {\n  margin-left: 4px;\n}\n.tox[dir=rtl] .tox-checkbox__label {\n  margin-right: 4px;\n}\n.tox[dir=rtl] .tox-checkbox__input {\n  right: -10000px;\n}\n.tox[dir=rtl] .tox-bar .tox-checkbox {\n  margin-right: 4px;\n}\n.tox {\n  /* stylelint-disable-next-line no-descending-specificity */\n}\n.tox .tox-collection--toolbar .tox-collection__group {\n  display: flex;\n  padding: 0;\n}\n.tox .tox-collection--grid .tox-collection__group {\n  display: flex;\n  flex-wrap: wrap;\n  max-height: 208px;\n  overflow-x: hidden;\n  overflow-y: auto;\n  padding: 0;\n}\n.tox .tox-collection--list .tox-collection__group {\n  border-bottom-width: 0;\n  border-color: #e3e3e3;\n  border-left-width: 0;\n  border-right-width: 0;\n  border-style: solid;\n  border-top-width: 1px;\n  padding: 4px 0;\n}\n.tox .tox-collection--list .tox-collection__group:first-child {\n  border-top-width: 0;\n}\n.tox .tox-collection__group-heading {\n  background-color: #fcfcfc;\n  color: rgba(34, 47, 62, 0.7);\n  cursor: default;\n  font-size: 12px;\n  font-style: normal;\n  font-weight: normal;\n  margin-bottom: 4px;\n  margin-top: -4px;\n  padding: 4px 8px;\n  text-transform: none;\n  -webkit-touch-callout: none;\n  -webkit-user-select: none;\n     -moz-user-select: none;\n          user-select: none;\n}\n.tox .tox-collection__item {\n  align-items: center;\n  border-radius: 3px;\n  color: #222f3e;\n  display: flex;\n  -webkit-touch-callout: none;\n  -webkit-user-select: none;\n     -moz-user-select: none;\n          user-select: none;\n}\n.tox .tox-collection--list .tox-collection__item {\n  padding: 4px 8px;\n}\n.tox .tox-collection--toolbar .tox-collection__item {\n  border-radius: 3px;\n  padding: 4px;\n}\n.tox .tox-collection--grid .tox-collection__item {\n  border-radius: 3px;\n  padding: 4px;\n}\n.tox .tox-collection--list .tox-collection__item--enabled {\n  background-color: #fff;\n  color: #222f3e;\n}\n.tox .tox-collection--list .tox-collection__item--active {\n  background-color: #cce2fa;\n}\n.tox .tox-collection--toolbar .tox-collection__item--enabled {\n  background-color: #a6ccf7;\n  color: #222f3e;\n}\n.tox .tox-collection--toolbar .tox-collection__item--active {\n  background-color: #cce2fa;\n}\n.tox .tox-collection--grid .tox-collection__item--enabled {\n  background-color: #a6ccf7;\n  color: #222f3e;\n}\n.tox .tox-collection--grid .tox-collection__item--active:not(.tox-collection__item--state-disabled) {\n  background-color: #cce2fa;\n  color: #222f3e;\n}\n.tox .tox-collection--list .tox-collection__item--active:not(.tox-collection__item--state-disabled) {\n  color: #222f3e;\n}\n.tox .tox-collection--toolbar .tox-collection__item--active:not(.tox-collection__item--state-disabled) {\n  color: #222f3e;\n}\n.tox .tox-collection__item-icon,\n.tox .tox-collection__item-checkmark {\n  align-items: center;\n  display: flex;\n  height: 24px;\n  justify-content: center;\n  width: 24px;\n}\n.tox .tox-collection__item-icon svg,\n.tox .tox-collection__item-checkmark svg {\n  fill: currentColor;\n}\n.tox .tox-collection--toolbar-lg .tox-collection__item-icon {\n  height: 48px;\n  width: 48px;\n}\n.tox .tox-collection__item-label {\n  color: currentColor;\n  display: inline-block;\n  flex: 1;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: normal;\n  line-height: 24px;\n  text-transform: none;\n  word-break: break-all;\n}\n.tox .tox-collection__item-accessory {\n  color: rgba(34, 47, 62, 0.7);\n  display: inline-block;\n  font-size: 14px;\n  height: 24px;\n  line-height: 24px;\n  text-transform: none;\n}\n.tox .tox-collection__item-caret {\n  align-items: center;\n  display: flex;\n  min-height: 24px;\n}\n.tox .tox-collection__item-caret::after {\n  content: '';\n  font-size: 0;\n  min-height: inherit;\n}\n.tox .tox-collection__item-caret svg {\n  fill: #222f3e;\n}\n.tox .tox-collection__item--state-disabled {\n  background-color: transparent;\n  color: rgba(34, 47, 62, 0.5);\n  cursor: not-allowed;\n}\n.tox .tox-collection__item--state-disabled .tox-collection__item-caret svg {\n  fill: rgba(34, 47, 62, 0.5);\n}\n.tox .tox-collection--list .tox-collection__item:not(.tox-collection__item--enabled) .tox-collection__item-checkmark svg {\n  display: none;\n}\n.tox .tox-collection--list .tox-collection__item:not(.tox-collection__item--enabled) .tox-collection__item-accessory + .tox-collection__item-checkmark {\n  display: none;\n}\n.tox .tox-collection--horizontal {\n  background-color: #fff;\n  border: 1px solid #e3e3e3;\n  border-radius: 6px;\n  box-shadow: 0 0 2px 0 rgba(34, 47, 62, 0.2), 0 4px 8px 0 rgba(34, 47, 62, 0.15);\n  display: flex;\n  flex: 0 0 auto;\n  flex-shrink: 0;\n  flex-wrap: nowrap;\n  margin-bottom: 0;\n  overflow-x: auto;\n  padding: 0;\n}\n.tox .tox-collection--horizontal .tox-collection__group {\n  align-items: center;\n  display: flex;\n  flex-wrap: nowrap;\n  margin: 0;\n  padding: 0 4px;\n}\n.tox .tox-collection--horizontal .tox-collection__item {\n  height: 28px;\n  margin: 6px 1px 5px 0;\n  padding: 0 4px;\n}\n.tox .tox-collection--horizontal .tox-collection__item-label {\n  white-space: nowrap;\n}\n.tox .tox-collection--horizontal .tox-collection__item-caret {\n  margin-left: 4px;\n}\n.tox .tox-collection__item-container {\n  display: flex;\n}\n.tox .tox-collection__item-container--row {\n  align-items: center;\n  flex: 1 1 auto;\n  flex-direction: row;\n}\n.tox .tox-collection__item-container--row.tox-collection__item-container--align-left {\n  margin-right: auto;\n}\n.tox .tox-collection__item-container--row.tox-collection__item-container--align-right {\n  justify-content: flex-end;\n  margin-left: auto;\n}\n.tox .tox-collection__item-container--row.tox-collection__item-container--valign-top {\n  align-items: flex-start;\n  margin-bottom: auto;\n}\n.tox .tox-collection__item-container--row.tox-collection__item-container--valign-middle {\n  align-items: center;\n}\n.tox .tox-collection__item-container--row.tox-collection__item-container--valign-bottom {\n  align-items: flex-end;\n  margin-top: auto;\n}\n.tox .tox-collection__item-container--column {\n  align-self: center;\n  flex: 1 1 auto;\n  flex-direction: column;\n}\n.tox .tox-collection__item-container--column.tox-collection__item-container--align-left {\n  align-items: flex-start;\n}\n.tox .tox-collection__item-container--column.tox-collection__item-container--align-right {\n  align-items: flex-end;\n}\n.tox .tox-collection__item-container--column.tox-collection__item-container--valign-top {\n  align-self: flex-start;\n}\n.tox .tox-collection__item-container--column.tox-collection__item-container--valign-middle {\n  align-self: center;\n}\n.tox .tox-collection__item-container--column.tox-collection__item-container--valign-bottom {\n  align-self: flex-end;\n}\n.tox:not([dir=rtl]) .tox-collection--horizontal .tox-collection__group:not(:last-of-type) {\n  border-right: 1px solid transparent;\n}\n.tox:not([dir=rtl]) .tox-collection--list .tox-collection__item > *:not(:first-child) {\n  margin-left: 8px;\n}\n.tox:not([dir=rtl]) .tox-collection--list .tox-collection__item > .tox-collection__item-label:first-child {\n  margin-left: 4px;\n}\n.tox:not([dir=rtl]) .tox-collection__item-accessory {\n  margin-left: 16px;\n  text-align: right;\n}\n.tox:not([dir=rtl]) .tox-collection .tox-collection__item-caret {\n  margin-left: 16px;\n}\n.tox[dir=rtl] .tox-collection--horizontal .tox-collection__group:not(:last-of-type) {\n  border-left: 1px solid transparent;\n}\n.tox[dir=rtl] .tox-collection--list .tox-collection__item > *:not(:first-child) {\n  margin-right: 8px;\n}\n.tox[dir=rtl] .tox-collection--list .tox-collection__item > .tox-collection__item-label:first-child {\n  margin-right: 4px;\n}\n.tox[dir=rtl] .tox-collection__item-accessory {\n  margin-right: 16px;\n  text-align: left;\n}\n.tox[dir=rtl] .tox-collection .tox-collection__item-caret {\n  margin-right: 16px;\n  transform: rotateY(180deg);\n}\n.tox[dir=rtl] .tox-collection--horizontal .tox-collection__item-caret {\n  margin-right: 4px;\n}\n.tox .tox-color-picker-container {\n  display: flex;\n  flex-direction: row;\n  height: 225px;\n  margin: 0;\n}\n.tox .tox-sv-palette {\n  box-sizing: border-box;\n  display: flex;\n  height: 100%;\n}\n.tox .tox-sv-palette-spectrum {\n  height: 100%;\n}\n.tox .tox-sv-palette,\n.tox .tox-sv-palette-spectrum {\n  width: 225px;\n}\n.tox .tox-sv-palette-thumb {\n  background: none;\n  border: 1px solid black;\n  border-radius: 50%;\n  box-sizing: content-box;\n  height: 12px;\n  position: absolute;\n  width: 12px;\n}\n.tox .tox-sv-palette-inner-thumb {\n  border: 1px solid white;\n  border-radius: 50%;\n  height: 10px;\n  position: absolute;\n  width: 10px;\n}\n.tox .tox-hue-slider {\n  box-sizing: border-box;\n  height: 100%;\n  width: 25px;\n}\n.tox .tox-hue-slider-spectrum {\n  background: linear-gradient(to bottom, #f00, #ff0080, #f0f, #8000ff, #00f, #0080ff, #0ff, #00ff80, #0f0, #80ff00, #ff0, #ff8000, #f00);\n  height: 100%;\n  width: 100%;\n}\n.tox .tox-hue-slider,\n.tox .tox-hue-slider-spectrum {\n  width: 20px;\n}\n.tox .tox-hue-slider-thumb {\n  background: white;\n  border: 1px solid black;\n  box-sizing: content-box;\n  height: 4px;\n  width: 100%;\n}\n.tox .tox-rgb-form {\n  display: flex;\n  flex-direction: column;\n  justify-content: space-between;\n}\n.tox .tox-rgb-form div {\n  align-items: center;\n  display: flex;\n  justify-content: space-between;\n  margin-bottom: 5px;\n  width: inherit;\n}\n.tox .tox-rgb-form input {\n  width: 6em;\n}\n.tox .tox-rgb-form input.tox-invalid {\n  /* Need !important to override Chrome's focus styling unfortunately */\n  border: 1px solid red !important;\n}\n.tox .tox-rgb-form .tox-rgba-preview {\n  border: 1px solid black;\n  flex-grow: 2;\n  margin-bottom: 0;\n}\n.tox:not([dir=rtl]) .tox-sv-palette {\n  margin-right: 15px;\n}\n.tox:not([dir=rtl]) .tox-hue-slider {\n  margin-right: 15px;\n}\n.tox:not([dir=rtl]) .tox-hue-slider-thumb {\n  margin-left: -1px;\n}\n.tox:not([dir=rtl]) .tox-rgb-form label {\n  margin-right: 0.5em;\n}\n.tox[dir=rtl] .tox-sv-palette {\n  margin-left: 15px;\n}\n.tox[dir=rtl] .tox-hue-slider {\n  margin-left: 15px;\n}\n.tox[dir=rtl] .tox-hue-slider-thumb {\n  margin-right: -1px;\n}\n.tox[dir=rtl] .tox-rgb-form label {\n  margin-left: 0.5em;\n}\n.tox .tox-toolbar .tox-swatches,\n.tox .tox-toolbar__primary .tox-swatches,\n.tox .tox-toolbar__overflow .tox-swatches {\n  margin: 5px 0 6px 11px;\n}\n.tox .tox-collection--list .tox-collection__group .tox-swatches-menu {\n  border: 0;\n  margin: -4px -4px;\n}\n.tox .tox-swatches__row {\n  display: flex;\n}\n.tox .tox-swatch {\n  height: 30px;\n  transition: transform 0.15s, box-shadow 0.15s;\n  width: 30px;\n}\n.tox .tox-swatch:hover,\n.tox .tox-swatch:focus {\n  box-shadow: 0 0 0 1px rgba(127, 127, 127, 0.3) inset;\n  transform: scale(0.8);\n}\n.tox .tox-swatch--remove {\n  align-items: center;\n  display: flex;\n  justify-content: center;\n}\n.tox .tox-swatch--remove svg path {\n  stroke: #e74c3c;\n}\n.tox .tox-swatches__picker-btn {\n  align-items: center;\n  background-color: transparent;\n  border: 0;\n  cursor: pointer;\n  display: flex;\n  height: 30px;\n  justify-content: center;\n  outline: none;\n  padding: 0;\n  width: 30px;\n}\n.tox .tox-swatches__picker-btn svg {\n  fill: #222f3e;\n  height: 24px;\n  width: 24px;\n}\n.tox .tox-swatches__picker-btn:hover {\n  background: #cce2fa;\n}\n.tox div.tox-swatch:not(.tox-swatch--remove) svg {\n  display: none;\n  fill: #222f3e;\n  height: 24px;\n  margin: calc((30px - 24px) / 2) calc((30px - 24px) / 2);\n  width: 24px;\n}\n.tox div.tox-swatch:not(.tox-swatch--remove) svg path {\n  fill: #fff;\n  paint-order: stroke;\n  stroke: #222f3e;\n  stroke-width: 2px;\n}\n.tox div.tox-swatch:not(.tox-swatch--remove).tox-collection__item--enabled svg {\n  display: block;\n}\n.tox:not([dir=rtl]) .tox-swatches__picker-btn {\n  margin-left: auto;\n}\n.tox[dir=rtl] .tox-swatches__picker-btn {\n  margin-right: auto;\n}\n.tox .tox-comment-thread {\n  background: #fff;\n  position: relative;\n}\n.tox .tox-comment-thread > *:not(:first-child) {\n  margin-top: 8px;\n}\n.tox .tox-comment {\n  background: #fff;\n  border: 1px solid #eeeeee;\n  border-radius: 6px;\n  box-shadow: 0 4px 8px 0 rgba(34, 47, 62, 0.1);\n  padding: 8px 8px 16px 8px;\n  position: relative;\n}\n.tox .tox-comment__header {\n  align-items: center;\n  color: #222f3e;\n  display: flex;\n  justify-content: space-between;\n}\n.tox .tox-comment__date {\n  color: #222f3e;\n  font-size: 12px;\n  line-height: 18px;\n}\n.tox .tox-comment__body {\n  color: #222f3e;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: normal;\n  line-height: 1.3;\n  margin-top: 8px;\n  position: relative;\n  text-transform: initial;\n}\n.tox .tox-comment__body textarea {\n  resize: none;\n  white-space: normal;\n  width: 100%;\n}\n.tox .tox-comment__expander {\n  padding-top: 8px;\n}\n.tox .tox-comment__expander p {\n  color: rgba(34, 47, 62, 0.7);\n  font-size: 14px;\n  font-style: normal;\n}\n.tox .tox-comment__body p {\n  margin: 0;\n}\n.tox .tox-comment__buttonspacing {\n  padding-top: 16px;\n  text-align: center;\n}\n.tox .tox-comment-thread__overlay::after {\n  background: #fff;\n  bottom: 0;\n  content: \"\";\n  display: flex;\n  left: 0;\n  opacity: 0.9;\n  position: absolute;\n  right: 0;\n  top: 0;\n  z-index: 5;\n}\n.tox .tox-comment__reply {\n  display: flex;\n  flex-shrink: 0;\n  flex-wrap: wrap;\n  justify-content: flex-end;\n  margin-top: 8px;\n}\n.tox .tox-comment__reply > *:first-child {\n  margin-bottom: 8px;\n  width: 100%;\n}\n.tox .tox-comment__edit {\n  display: flex;\n  flex-wrap: wrap;\n  justify-content: flex-end;\n  margin-top: 16px;\n}\n.tox .tox-comment__gradient::after {\n  background: linear-gradient(rgba(255, 255, 255, 0), #fff);\n  bottom: 0;\n  content: \"\";\n  display: block;\n  height: 5em;\n  margin-top: -40px;\n  position: absolute;\n  width: 100%;\n}\n.tox .tox-comment__overlay {\n  background: #fff;\n  bottom: 0;\n  display: flex;\n  flex-direction: column;\n  flex-grow: 1;\n  left: 0;\n  opacity: 0.9;\n  position: absolute;\n  right: 0;\n  text-align: center;\n  top: 0;\n  z-index: 5;\n}\n.tox .tox-comment__loading-text {\n  align-items: center;\n  color: #222f3e;\n  display: flex;\n  flex-direction: column;\n  position: relative;\n}\n.tox .tox-comment__loading-text > div {\n  padding-bottom: 16px;\n}\n.tox .tox-comment__overlaytext {\n  bottom: 0;\n  flex-direction: column;\n  font-size: 14px;\n  left: 0;\n  padding: 1em;\n  position: absolute;\n  right: 0;\n  top: 0;\n  z-index: 10;\n}\n.tox .tox-comment__overlaytext p {\n  background-color: #fff;\n  box-shadow: 0 0 8px 8px #fff;\n  color: #222f3e;\n  text-align: center;\n}\n.tox .tox-comment__overlaytext div:nth-of-type(2) {\n  font-size: 0.8em;\n}\n.tox .tox-comment__busy-spinner {\n  align-items: center;\n  background-color: #fff;\n  bottom: 0;\n  display: flex;\n  justify-content: center;\n  left: 0;\n  position: absolute;\n  right: 0;\n  top: 0;\n  z-index: 20;\n}\n.tox .tox-comment__scroll {\n  display: flex;\n  flex-direction: column;\n  flex-shrink: 1;\n  overflow: auto;\n}\n.tox .tox-conversations {\n  margin: 8px;\n}\n.tox:not([dir=rtl]) .tox-comment__edit {\n  margin-left: 8px;\n}\n.tox:not([dir=rtl]) .tox-comment__buttonspacing > *:last-child,\n.tox:not([dir=rtl]) .tox-comment__edit > *:last-child,\n.tox:not([dir=rtl]) .tox-comment__reply > *:last-child {\n  margin-left: 8px;\n}\n.tox[dir=rtl] .tox-comment__edit {\n  margin-right: 8px;\n}\n.tox[dir=rtl] .tox-comment__buttonspacing > *:last-child,\n.tox[dir=rtl] .tox-comment__edit > *:last-child,\n.tox[dir=rtl] .tox-comment__reply > *:last-child {\n  margin-right: 8px;\n}\n.tox .tox-user {\n  align-items: center;\n  display: flex;\n}\n.tox .tox-user__avatar svg {\n  fill: rgba(34, 47, 62, 0.7);\n}\n.tox .tox-user__avatar img {\n  border-radius: 50%;\n  height: 36px;\n  object-fit: cover;\n  vertical-align: middle;\n  width: 36px;\n}\n.tox .tox-user__name {\n  color: #222f3e;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: bold;\n  line-height: 18px;\n  text-transform: none;\n}\n.tox:not([dir=rtl]) .tox-user__avatar svg,\n.tox:not([dir=rtl]) .tox-user__avatar img {\n  margin-right: 8px;\n}\n.tox:not([dir=rtl]) .tox-user__avatar + .tox-user__name {\n  margin-left: 8px;\n}\n.tox[dir=rtl] .tox-user__avatar svg,\n.tox[dir=rtl] .tox-user__avatar img {\n  margin-left: 8px;\n}\n.tox[dir=rtl] .tox-user__avatar + .tox-user__name {\n  margin-right: 8px;\n}\n.tox .tox-dialog-wrap {\n  align-items: center;\n  bottom: 0;\n  display: flex;\n  justify-content: center;\n  left: 0;\n  position: fixed;\n  right: 0;\n  top: 0;\n  z-index: 1100;\n}\n.tox .tox-dialog-wrap__backdrop {\n  background-color: rgba(255, 255, 255, 0.75);\n  bottom: 0;\n  left: 0;\n  position: absolute;\n  right: 0;\n  top: 0;\n  z-index: 1;\n}\n.tox .tox-dialog-wrap__backdrop--opaque {\n  background-color: #fff;\n}\n.tox .tox-dialog {\n  background-color: #fff;\n  border-color: #eeeeee;\n  border-radius: 10px;\n  border-style: solid;\n  border-width: 0px;\n  box-shadow: 0 16px 16px -10px rgba(34, 47, 62, 0.15), 0 0 40px 1px rgba(34, 47, 62, 0.15);\n  display: flex;\n  flex-direction: column;\n  max-height: 100%;\n  max-width: 480px;\n  overflow: hidden;\n  position: relative;\n  width: 95vw;\n  z-index: 2;\n}\n@media only screen and (max-width: 767px ) {\n  body:not(.tox-force-desktop) .tox .tox-dialog {\n    align-self: flex-start;\n    margin: 8px auto;\n    max-height: calc(100vh - 8px * 2);\n    width: calc(100vw - 16px);\n  }\n}\n.tox .tox-dialog-inline {\n  z-index: 1100;\n}\n.tox .tox-dialog__header {\n  align-items: center;\n  background-color: #fff;\n  border-bottom: none;\n  color: #222f3e;\n  display: flex;\n  font-size: 16px;\n  justify-content: space-between;\n  padding: 8px 16px 0 16px;\n  position: relative;\n}\n.tox .tox-dialog__header .tox-button {\n  z-index: 1;\n}\n.tox .tox-dialog__draghandle {\n  cursor: grab;\n  height: 100%;\n  left: 0;\n  position: absolute;\n  top: 0;\n  width: 100%;\n}\n.tox .tox-dialog__draghandle:active {\n  cursor: grabbing;\n}\n.tox .tox-dialog__dismiss {\n  margin-left: auto;\n}\n.tox .tox-dialog__title {\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n  font-size: 20px;\n  font-style: normal;\n  font-weight: normal;\n  line-height: 1.3;\n  margin: 0;\n  text-transform: none;\n}\n.tox .tox-dialog__body {\n  color: #222f3e;\n  display: flex;\n  flex: 1;\n  font-size: 16px;\n  font-style: normal;\n  font-weight: normal;\n  line-height: 1.3;\n  min-width: 0;\n  text-align: left;\n  text-transform: none;\n}\n@media only screen and (max-width: 767px ) {\n  body:not(.tox-force-desktop) .tox .tox-dialog__body {\n    flex-direction: column;\n  }\n}\n.tox .tox-dialog__body-nav {\n  align-items: flex-start;\n  display: flex;\n  flex-direction: column;\n  flex-shrink: 0;\n  padding: 16px 16px;\n}\n@media only screen and (min-width: 768px ) {\n  .tox .tox-dialog__body-nav {\n    max-width: 11em;\n  }\n}\n@media only screen and (max-width: 767px ) {\n  body:not(.tox-force-desktop) .tox .tox-dialog__body-nav {\n    flex-direction: row;\n    -webkit-overflow-scrolling: touch;\n    overflow-x: auto;\n    padding-bottom: 0;\n  }\n}\n.tox .tox-dialog__body-nav-item {\n  border-bottom: 2px solid transparent;\n  color: rgba(34, 47, 62, 0.7);\n  display: inline-block;\n  flex-shrink: 0;\n  font-size: 14px;\n  line-height: 1.3;\n  margin-bottom: 8px;\n  max-width: 13em;\n  text-decoration: none;\n}\n.tox .tox-dialog__body-nav-item:focus {\n  background-color: rgba(0, 108, 231, 0.1);\n}\n.tox .tox-dialog__body-nav-item--active {\n  border-bottom: 2px solid #006ce7;\n  color: #006ce7;\n}\n.tox .tox-dialog__body-content {\n  box-sizing: border-box;\n  display: flex;\n  flex: 1;\n  flex-direction: column;\n  max-height: min(650px, calc(100vh - 110px));\n  overflow: auto;\n  -webkit-overflow-scrolling: touch;\n  padding: 16px 16px;\n}\n.tox .tox-dialog__body-content > * {\n  margin-bottom: 0;\n  margin-top: 16px;\n}\n.tox .tox-dialog__body-content > *:first-child {\n  margin-top: 0;\n}\n.tox .tox-dialog__body-content > *:last-child {\n  margin-bottom: 0;\n}\n.tox .tox-dialog__body-content > *:only-child {\n  margin-bottom: 0;\n  margin-top: 0;\n}\n.tox .tox-dialog__body-content a {\n  color: #006ce7;\n  cursor: pointer;\n  text-decoration: none;\n}\n.tox .tox-dialog__body-content a:hover,\n.tox .tox-dialog__body-content a:focus {\n  color: #0054b4;\n  text-decoration: none;\n}\n.tox .tox-dialog__body-content a:active {\n  color: #0054b4;\n  text-decoration: none;\n}\n.tox .tox-dialog__body-content svg {\n  fill: #222f3e;\n}\n.tox .tox-dialog__body-content strong {\n  font-weight: bold;\n}\n.tox .tox-dialog__body-content ul {\n  list-style-type: disc;\n}\n.tox .tox-dialog__body-content ul,\n.tox .tox-dialog__body-content ol,\n.tox .tox-dialog__body-content dd {\n  padding-inline-start: 2.5rem;\n}\n.tox .tox-dialog__body-content ul,\n.tox .tox-dialog__body-content ol,\n.tox .tox-dialog__body-content dl {\n  margin-bottom: 16px;\n}\n.tox .tox-dialog__body-content ul,\n.tox .tox-dialog__body-content ol,\n.tox .tox-dialog__body-content dl,\n.tox .tox-dialog__body-content dd,\n.tox .tox-dialog__body-content dt {\n  display: block;\n  margin-inline-end: 0;\n  margin-inline-start: 0;\n}\n.tox .tox-dialog__body-content .tox-form__group h1 {\n  color: #222f3e;\n  font-size: 20px;\n  font-style: normal;\n  font-weight: bold;\n  letter-spacing: normal;\n  margin-bottom: 16px;\n  margin-top: 2rem;\n  text-transform: none;\n}\n.tox .tox-dialog__body-content .tox-form__group h2 {\n  color: #222f3e;\n  font-size: 16px;\n  font-style: normal;\n  font-weight: bold;\n  letter-spacing: normal;\n  margin-bottom: 16px;\n  margin-top: 2rem;\n  text-transform: none;\n}\n.tox .tox-dialog__body-content .tox-form__group p {\n  margin-bottom: 16px;\n}\n.tox .tox-dialog__body-content .tox-form__group h1:first-child,\n.tox .tox-dialog__body-content .tox-form__group h2:first-child,\n.tox .tox-dialog__body-content .tox-form__group p:first-child {\n  margin-top: 0;\n}\n.tox .tox-dialog__body-content .tox-form__group h1:last-child,\n.tox .tox-dialog__body-content .tox-form__group h2:last-child,\n.tox .tox-dialog__body-content .tox-form__group p:last-child {\n  margin-bottom: 0;\n}\n.tox .tox-dialog__body-content .tox-form__group h1:only-child,\n.tox .tox-dialog__body-content .tox-form__group h2:only-child,\n.tox .tox-dialog__body-content .tox-form__group p:only-child {\n  margin-bottom: 0;\n  margin-top: 0;\n}\n.tox .tox-dialog__body-content .tox-form__group .tox-label.tox-label--center {\n  text-align: center;\n}\n.tox .tox-dialog__body-content .tox-form__group .tox-label.tox-label--end {\n  text-align: end;\n}\n.tox .tox-dialog--width-lg {\n  height: 650px;\n  max-width: 1200px;\n}\n.tox .tox-dialog--fullscreen {\n  height: 100%;\n  max-width: 100%;\n}\n.tox .tox-dialog--fullscreen .tox-dialog__body-content {\n  max-height: 100%;\n}\n.tox .tox-dialog--width-md {\n  max-width: 800px;\n}\n.tox .tox-dialog--width-md .tox-dialog__body-content {\n  overflow: auto;\n}\n.tox .tox-dialog__body-content--centered {\n  text-align: center;\n}\n.tox .tox-dialog__footer {\n  align-items: center;\n  background-color: #fff;\n  border-top: none;\n  display: flex;\n  justify-content: space-between;\n  padding: 8px 16px;\n}\n.tox .tox-dialog__footer-start,\n.tox .tox-dialog__footer-end {\n  display: flex;\n}\n.tox .tox-dialog__busy-spinner {\n  align-items: center;\n  background-color: rgba(255, 255, 255, 0.75);\n  bottom: 0;\n  display: flex;\n  justify-content: center;\n  left: 0;\n  position: absolute;\n  right: 0;\n  top: 0;\n  z-index: 3;\n}\n.tox .tox-dialog__table {\n  border-collapse: collapse;\n  width: 100%;\n}\n.tox .tox-dialog__table thead th {\n  font-weight: bold;\n  padding-bottom: 8px;\n}\n.tox .tox-dialog__table thead th:first-child {\n  padding-right: 8px;\n}\n.tox .tox-dialog__table tbody tr {\n  border-bottom: 1px solid #626262;\n}\n.tox .tox-dialog__table tbody tr:last-child {\n  border-bottom: none;\n}\n.tox .tox-dialog__table td {\n  padding-bottom: 8px;\n  padding-top: 8px;\n}\n.tox .tox-dialog__table td:first-child {\n  padding-right: 8px;\n}\n.tox .tox-dialog__iframe {\n  min-height: 200px;\n}\n.tox .tox-dialog__iframe.tox-dialog__iframe--opaque {\n  background: #fff;\n}\n.tox .tox-dialog__iframe.tox-dialog__iframe--bordered {\n  border: 1px solid #eeeeee;\n  border-radius: 6px;\n}\n.tox .tox-dialog__popups {\n  position: absolute;\n  width: 100%;\n  z-index: 1100;\n}\n.tox .tox-dialog__body-iframe {\n  display: flex;\n  flex: 1;\n  flex-direction: column;\n}\n.tox .tox-dialog__body-iframe .tox-navobj {\n  display: flex;\n  flex: 1;\n}\n.tox .tox-dialog__body-iframe .tox-navobj :nth-child(2) {\n  flex: 1;\n  height: 100%;\n}\n.tox .tox-dialog-dock-fadeout {\n  opacity: 0;\n  visibility: hidden;\n}\n.tox .tox-dialog-dock-fadein {\n  opacity: 1;\n  visibility: visible;\n}\n.tox .tox-dialog-dock-transition {\n  transition: visibility 0s linear 0.3s, opacity 0.3s ease;\n}\n.tox .tox-dialog-dock-transition.tox-dialog-dock-fadein {\n  transition-delay: 0s;\n}\n@media only screen and (max-width: 767px ) {\n  body:not(.tox-force-desktop) .tox:not([dir=rtl]) .tox-dialog__body-nav {\n    margin-right: 0;\n  }\n}\n@media only screen and (max-width: 767px ) {\n  body:not(.tox-force-desktop) .tox:not([dir=rtl]) .tox-dialog__body-nav-item:not(:first-child) {\n    margin-left: 8px;\n  }\n}\n.tox:not([dir=rtl]) .tox-dialog__footer .tox-dialog__footer-start > *,\n.tox:not([dir=rtl]) .tox-dialog__footer .tox-dialog__footer-end > * {\n  margin-left: 8px;\n}\n.tox[dir=rtl] .tox-dialog__body {\n  text-align: right;\n}\n@media only screen and (max-width: 767px ) {\n  body:not(.tox-force-desktop) .tox[dir=rtl] .tox-dialog__body-nav {\n    margin-left: 0;\n  }\n}\n@media only screen and (max-width: 767px ) {\n  body:not(.tox-force-desktop) .tox[dir=rtl] .tox-dialog__body-nav-item:not(:first-child) {\n    margin-right: 8px;\n  }\n}\n.tox[dir=rtl] .tox-dialog__footer .tox-dialog__footer-start > *,\n.tox[dir=rtl] .tox-dialog__footer .tox-dialog__footer-end > * {\n  margin-right: 8px;\n}\nbody.tox-dialog__disable-scroll {\n  overflow: hidden;\n}\n.tox .tox-dropzone-container {\n  display: flex;\n  flex: 1;\n}\n.tox .tox-dropzone {\n  align-items: center;\n  background: #fff;\n  border: 2px dashed #eeeeee;\n  box-sizing: border-box;\n  display: flex;\n  flex-direction: column;\n  flex-grow: 1;\n  justify-content: center;\n  min-height: 100px;\n  padding: 10px;\n}\n.tox .tox-dropzone p {\n  color: rgba(34, 47, 62, 0.7);\n  margin: 0 0 16px 0;\n}\n.tox .tox-edit-area {\n  display: flex;\n  flex: 1;\n  overflow: hidden;\n  position: relative;\n}\n.tox .tox-edit-area::before {\n  border: 2px solid #2D6ADF;\n  border-radius: 4px;\n  content: '';\n  inset: 0;\n  opacity: 0;\n  pointer-events: none;\n  position: absolute;\n  transition: opacity 0.15s;\n  z-index: 1;\n}\n.tox .tox-edit-area__iframe {\n  background-color: #fff;\n  border: 0;\n  box-sizing: border-box;\n  flex: 1;\n  height: 100%;\n  position: absolute;\n  width: 100%;\n}\n.tox.tox-edit-focus .tox-edit-area::before {\n  opacity: 1;\n}\n.tox.tox-inline-edit-area {\n  border: 1px dotted #eeeeee;\n}\n.tox .tox-editor-container {\n  display: flex;\n  flex: 1 1 auto;\n  flex-direction: column;\n  overflow: hidden;\n}\n.tox .tox-editor-header {\n  display: grid;\n  grid-template-columns: 1fr min-content;\n  z-index: 2;\n}\n.tox:not(.tox-tinymce-inline) .tox-editor-header {\n  background-color: #fff;\n  border-bottom: none;\n  box-shadow: 0 2px 2px -2px rgba(34, 47, 62, 0.1), 0 8px 8px -4px rgba(34, 47, 62, 0.07);\n  padding: 4px 0;\n}\n.tox:not(.tox-tinymce-inline) .tox-editor-header:not(.tox-editor-dock-transition) {\n  transition: box-shadow 0.5s;\n}\n.tox:not(.tox-tinymce-inline).tox-tinymce--toolbar-bottom .tox-editor-header {\n  border-top: 1px solid #e3e3e3;\n  box-shadow: none;\n}\n.tox:not(.tox-tinymce-inline).tox-tinymce--toolbar-sticky-on .tox-editor-header {\n  background-color: #fff;\n  box-shadow: 0 2px 2px -2px rgba(34, 47, 62, 0.2), 0 8px 8px -4px rgba(34, 47, 62, 0.15);\n  padding: 4px 0;\n}\n.tox:not(.tox-tinymce-inline).tox-tinymce--toolbar-sticky-on.tox-tinymce--toolbar-bottom .tox-editor-header {\n  box-shadow: 0 2px 2px -2px rgba(34, 47, 62, 0.2), 0 8px 8px -4px rgba(34, 47, 62, 0.15);\n}\n.tox.tox:not(.tox-tinymce-inline) .tox-editor-header.tox-editor-header--empty {\n  background: none;\n  border: none;\n  box-shadow: none;\n  padding: 0;\n}\n.tox-editor-dock-fadeout {\n  opacity: 0;\n  visibility: hidden;\n}\n.tox-editor-dock-fadein {\n  opacity: 1;\n  visibility: visible;\n}\n.tox-editor-dock-transition {\n  transition: visibility 0s linear 0.25s, opacity 0.25s ease;\n}\n.tox-editor-dock-transition.tox-editor-dock-fadein {\n  transition-delay: 0s;\n}\n.tox .tox-control-wrap {\n  flex: 1;\n  position: relative;\n}\n.tox .tox-control-wrap:not(.tox-control-wrap--status-invalid) .tox-control-wrap__status-icon-invalid,\n.tox .tox-control-wrap:not(.tox-control-wrap--status-unknown) .tox-control-wrap__status-icon-unknown,\n.tox .tox-control-wrap:not(.tox-control-wrap--status-valid) .tox-control-wrap__status-icon-valid {\n  display: none;\n}\n.tox .tox-control-wrap svg {\n  display: block;\n}\n.tox .tox-control-wrap__status-icon-wrap {\n  position: absolute;\n  top: 50%;\n  transform: translateY(-50%);\n}\n.tox .tox-control-wrap__status-icon-invalid svg {\n  fill: #c00;\n}\n.tox .tox-control-wrap__status-icon-unknown svg {\n  fill: orange;\n}\n.tox .tox-control-wrap__status-icon-valid svg {\n  fill: green;\n}\n.tox:not([dir=rtl]) .tox-control-wrap--status-invalid .tox-textfield,\n.tox:not([dir=rtl]) .tox-control-wrap--status-unknown .tox-textfield,\n.tox:not([dir=rtl]) .tox-control-wrap--status-valid .tox-textfield {\n  padding-right: 32px;\n}\n.tox:not([dir=rtl]) .tox-control-wrap__status-icon-wrap {\n  right: 4px;\n}\n.tox[dir=rtl] .tox-control-wrap--status-invalid .tox-textfield,\n.tox[dir=rtl] .tox-control-wrap--status-unknown .tox-textfield,\n.tox[dir=rtl] .tox-control-wrap--status-valid .tox-textfield {\n  padding-left: 32px;\n}\n.tox[dir=rtl] .tox-control-wrap__status-icon-wrap {\n  left: 4px;\n}\n.tox .tox-autocompleter {\n  max-width: 25em;\n}\n.tox .tox-autocompleter .tox-menu {\n  box-sizing: border-box;\n  max-width: 25em;\n}\n.tox .tox-autocompleter .tox-autocompleter-highlight {\n  font-weight: bold;\n}\n.tox .tox-color-input {\n  display: flex;\n  position: relative;\n  z-index: 1;\n}\n.tox .tox-color-input .tox-textfield {\n  z-index: -1;\n}\n.tox .tox-color-input span {\n  border-color: rgba(34, 47, 62, 0.2);\n  border-radius: 6px;\n  border-style: solid;\n  border-width: 1px;\n  box-shadow: none;\n  box-sizing: border-box;\n  height: 24px;\n  position: absolute;\n  top: 6px;\n  width: 24px;\n}\n.tox .tox-color-input span:hover:not([aria-disabled=true]),\n.tox .tox-color-input span:focus:not([aria-disabled=true]) {\n  border-color: #006ce7;\n  cursor: pointer;\n}\n.tox .tox-color-input span::before {\n  background-image: linear-gradient(45deg, rgba(0, 0, 0, 0.25) 25%, transparent 25%), linear-gradient(-45deg, rgba(0, 0, 0, 0.25) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, rgba(0, 0, 0, 0.25) 75%), linear-gradient(-45deg, transparent 75%, rgba(0, 0, 0, 0.25) 75%);\n  background-position: 0 0, 0 6px, 6px -6px, -6px 0;\n  background-size: 12px 12px;\n  border: 1px solid #fff;\n  border-radius: 6px;\n  box-sizing: border-box;\n  content: '';\n  height: 24px;\n  left: -1px;\n  position: absolute;\n  top: -1px;\n  width: 24px;\n  z-index: -1;\n}\n.tox .tox-color-input span[aria-disabled=true] {\n  cursor: not-allowed;\n}\n.tox:not([dir=rtl]) .tox-color-input {\n  /* stylelint-disable-next-line no-descending-specificity */\n}\n.tox:not([dir=rtl]) .tox-color-input .tox-textfield {\n  padding-left: 36px;\n}\n.tox:not([dir=rtl]) .tox-color-input span {\n  left: 6px;\n}\n.tox[dir=\"rtl\"] .tox-color-input {\n  /* stylelint-disable-next-line no-descending-specificity */\n}\n.tox[dir=\"rtl\"] .tox-color-input .tox-textfield {\n  padding-right: 36px;\n}\n.tox[dir=\"rtl\"] .tox-color-input span {\n  right: 6px;\n}\n.tox .tox-label,\n.tox .tox-toolbar-label {\n  color: rgba(34, 47, 62, 0.7);\n  display: block;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: normal;\n  line-height: 1.3;\n  padding: 0 8px 0 0;\n  text-transform: none;\n  white-space: nowrap;\n}\n.tox .tox-toolbar-label {\n  padding: 0 8px;\n}\n.tox[dir=rtl] .tox-label {\n  padding: 0 0 0 8px;\n}\n.tox .tox-form {\n  display: flex;\n  flex: 1;\n  flex-direction: column;\n}\n.tox .tox-form__group {\n  box-sizing: border-box;\n  margin-bottom: 4px;\n}\n.tox .tox-form-group--maximize {\n  flex: 1;\n}\n.tox .tox-form__group--error {\n  color: #c00;\n}\n.tox .tox-form__group--collection {\n  display: flex;\n}\n.tox .tox-form__grid {\n  display: flex;\n  flex-direction: row;\n  flex-wrap: wrap;\n  justify-content: space-between;\n}\n.tox .tox-form__grid--2col > .tox-form__group {\n  width: calc(50% - (8px / 2));\n}\n.tox .tox-form__grid--3col > .tox-form__group {\n  width: calc(100% / 3 - (8px / 2));\n}\n.tox .tox-form__grid--4col > .tox-form__group {\n  width: calc(25% - (8px / 2));\n}\n.tox .tox-form__controls-h-stack {\n  align-items: center;\n  display: flex;\n}\n.tox .tox-form__group--inline {\n  align-items: center;\n  display: flex;\n}\n.tox .tox-form__group--stretched {\n  display: flex;\n  flex: 1;\n  flex-direction: column;\n}\n.tox .tox-form__group--stretched .tox-textarea {\n  flex: 1;\n}\n.tox .tox-form__group--stretched .tox-navobj {\n  display: flex;\n  flex: 1;\n}\n.tox .tox-form__group--stretched .tox-navobj :nth-child(2) {\n  flex: 1;\n  height: 100%;\n}\n.tox:not([dir=rtl]) .tox-form__controls-h-stack > *:not(:first-child) {\n  margin-left: 4px;\n}\n.tox[dir=rtl] .tox-form__controls-h-stack > *:not(:first-child) {\n  margin-right: 4px;\n}\n.tox .tox-lock.tox-locked .tox-lock-icon__unlock,\n.tox .tox-lock:not(.tox-locked) .tox-lock-icon__lock {\n  display: none;\n}\n.tox .tox-textfield,\n.tox .tox-toolbar-textfield,\n.tox .tox-listboxfield .tox-listbox--select,\n.tox .tox-textarea,\n.tox .tox-textarea-wrap .tox-textarea:focus {\n  -webkit-appearance: none;\n     -moz-appearance: none;\n          appearance: none;\n  background-color: #fff;\n  border-color: #eeeeee;\n  border-radius: 6px;\n  border-style: solid;\n  border-width: 1px;\n  box-shadow: none;\n  box-sizing: border-box;\n  color: #222f3e;\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n  font-size: 16px;\n  line-height: 24px;\n  margin: 0;\n  min-height: 34px;\n  outline: none;\n  padding: 5px 5.5px;\n  resize: none;\n  width: 100%;\n}\n.tox .tox-textfield[disabled],\n.tox .tox-textarea[disabled] {\n  background-color: #f2f2f2;\n  color: rgba(34, 47, 62, 0.85);\n  cursor: not-allowed;\n}\n.tox .tox-textfield:focus,\n.tox .tox-dialog__iframe.tox-dialog__iframe--bordered:focus-within,\n.tox .tox-listboxfield .tox-listbox--select:focus,\n.tox .tox-textarea-wrap:focus-within,\n.tox .tox-textarea:focus,\n.tox .tox-custom-editor:focus-within {\n  background-color: #fff;\n  border-color: #006ce7;\n  box-shadow: 0 0 0 2px rgba(0, 108, 231, 0.25);\n  outline: none;\n}\n.tox .tox-toolbar-textfield {\n  border-width: 0;\n  margin-bottom: 3px;\n  margin-top: 2px;\n  max-width: 250px;\n}\n.tox .tox-naked-btn {\n  background-color: transparent;\n  border: 0;\n  border-color: transparent;\n  box-shadow: unset;\n  color: #006ce7;\n  cursor: pointer;\n  display: block;\n  margin: 0;\n  padding: 0;\n}\n.tox .tox-naked-btn svg {\n  display: block;\n  fill: #222f3e;\n}\n.tox:not([dir=rtl]) .tox-toolbar-textfield + * {\n  margin-left: 4px;\n}\n.tox[dir=rtl] .tox-toolbar-textfield + * {\n  margin-right: 4px;\n}\n.tox .tox-listboxfield {\n  cursor: pointer;\n  position: relative;\n}\n.tox .tox-listboxfield .tox-listbox--select[disabled] {\n  background-color: #f2f2f2;\n  color: rgba(34, 47, 62, 0.85);\n  cursor: not-allowed;\n}\n.tox .tox-listbox__select-label {\n  cursor: default;\n  flex: 1;\n  margin: 0 4px;\n}\n.tox .tox-listbox__select-chevron {\n  align-items: center;\n  display: flex;\n  justify-content: center;\n  width: 16px;\n}\n.tox .tox-listbox__select-chevron svg {\n  fill: #222f3e;\n}\n.tox .tox-listboxfield .tox-listbox--select {\n  align-items: center;\n  display: flex;\n}\n.tox:not([dir=rtl]) .tox-listboxfield svg {\n  right: 8px;\n}\n.tox[dir=rtl] .tox-listboxfield svg {\n  left: 8px;\n}\n.tox .tox-selectfield {\n  cursor: pointer;\n  position: relative;\n}\n.tox .tox-selectfield select {\n  -webkit-appearance: none;\n     -moz-appearance: none;\n          appearance: none;\n  background-color: #fff;\n  border-color: #eeeeee;\n  border-radius: 6px;\n  border-style: solid;\n  border-width: 1px;\n  box-shadow: none;\n  box-sizing: border-box;\n  color: #222f3e;\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n  font-size: 16px;\n  line-height: 24px;\n  margin: 0;\n  min-height: 34px;\n  outline: none;\n  padding: 5px 5.5px;\n  resize: none;\n  width: 100%;\n}\n.tox .tox-selectfield select[disabled] {\n  background-color: #f2f2f2;\n  color: rgba(34, 47, 62, 0.85);\n  cursor: not-allowed;\n}\n.tox .tox-selectfield select::-ms-expand {\n  display: none;\n}\n.tox .tox-selectfield select:focus {\n  background-color: #fff;\n  border-color: #006ce7;\n  box-shadow: 0 0 0 2px rgba(0, 108, 231, 0.25);\n  outline: none;\n}\n.tox .tox-selectfield svg {\n  pointer-events: none;\n  position: absolute;\n  top: 50%;\n  transform: translateY(-50%);\n}\n.tox:not([dir=rtl]) .tox-selectfield select[size=\"0\"],\n.tox:not([dir=rtl]) .tox-selectfield select[size=\"1\"] {\n  padding-right: 24px;\n}\n.tox:not([dir=rtl]) .tox-selectfield svg {\n  right: 8px;\n}\n.tox[dir=rtl] .tox-selectfield select[size=\"0\"],\n.tox[dir=rtl] .tox-selectfield select[size=\"1\"] {\n  padding-left: 24px;\n}\n.tox[dir=rtl] .tox-selectfield svg {\n  left: 8px;\n}\n.tox .tox-textarea-wrap {\n  border-color: #eeeeee;\n  border-radius: 6px;\n  border-style: solid;\n  border-width: 1px;\n  display: flex;\n  flex: 1;\n  overflow: hidden;\n}\n.tox .tox-textarea {\n  -webkit-appearance: textarea;\n     -moz-appearance: textarea;\n          appearance: textarea;\n  white-space: pre-wrap;\n}\n.tox .tox-textarea-wrap .tox-textarea {\n  border: none;\n}\n.tox .tox-textarea-wrap .tox-textarea:focus {\n  border: none;\n}\n.tox-fullscreen {\n  border: 0;\n  height: 100%;\n  margin: 0;\n  overflow: hidden;\n  overscroll-behavior: none;\n  padding: 0;\n  touch-action: pinch-zoom;\n  width: 100%;\n}\n.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle {\n  display: none;\n}\n.tox.tox-tinymce.tox-fullscreen,\n.tox-shadowhost.tox-fullscreen {\n  left: 0;\n  position: fixed;\n  top: 0;\n  z-index: 1200;\n}\n.tox.tox-tinymce.tox-fullscreen {\n  background-color: transparent;\n}\n.tox-fullscreen .tox.tox-tinymce-aux,\n.tox-fullscreen ~ .tox.tox-tinymce-aux {\n  z-index: 1201;\n}\n.tox .tox-help__more-link {\n  list-style: none;\n  margin-top: 1em;\n}\n.tox .tox-imagepreview {\n  background-color: #666;\n  height: 380px;\n  overflow: hidden;\n  position: relative;\n  width: 100%;\n}\n.tox .tox-imagepreview.tox-imagepreview__loaded {\n  overflow: auto;\n}\n.tox .tox-imagepreview__container {\n  display: flex;\n  left: 100vw;\n  position: absolute;\n  top: 100vw;\n}\n.tox .tox-imagepreview__image {\n  background: url(data:image/gif;base64,R0lGODdhDAAMAIABAMzMzP///ywAAAAADAAMAAACFoQfqYeabNyDMkBQb81Uat85nxguUAEAOw==);\n}\n.tox .tox-image-tools .tox-spacer {\n  flex: 1;\n}\n.tox .tox-image-tools .tox-bar {\n  align-items: center;\n  display: flex;\n  height: 60px;\n  justify-content: center;\n}\n.tox .tox-image-tools .tox-imagepreview,\n.tox .tox-image-tools .tox-imagepreview + .tox-bar {\n  margin-top: 8px;\n}\n.tox .tox-image-tools .tox-croprect-block {\n  background: black;\n  filter: alpha(opacity=50);\n  opacity: 0.5;\n  position: absolute;\n  zoom: 1;\n}\n.tox .tox-image-tools .tox-croprect-handle {\n  border: 2px solid white;\n  height: 20px;\n  left: 0;\n  position: absolute;\n  top: 0;\n  width: 20px;\n}\n.tox .tox-image-tools .tox-croprect-handle-move {\n  border: 0;\n  cursor: move;\n  position: absolute;\n}\n.tox .tox-image-tools .tox-croprect-handle-nw {\n  border-width: 2px 0 0 2px;\n  cursor: nw-resize;\n  left: 100px;\n  margin: -2px 0 0 -2px;\n  top: 100px;\n}\n.tox .tox-image-tools .tox-croprect-handle-ne {\n  border-width: 2px 2px 0 0;\n  cursor: ne-resize;\n  left: 200px;\n  margin: -2px 0 0 -20px;\n  top: 100px;\n}\n.tox .tox-image-tools .tox-croprect-handle-sw {\n  border-width: 0 0 2px 2px;\n  cursor: sw-resize;\n  left: 100px;\n  margin: -20px 2px 0 -2px;\n  top: 200px;\n}\n.tox .tox-image-tools .tox-croprect-handle-se {\n  border-width: 0 2px 2px 0;\n  cursor: se-resize;\n  left: 200px;\n  margin: -20px 0 0 -20px;\n  top: 200px;\n}\n.tox .tox-insert-table-picker {\n  display: flex;\n  flex-wrap: wrap;\n  width: 170px;\n}\n.tox .tox-insert-table-picker > div {\n  border-color: #eeeeee;\n  border-style: solid;\n  border-width: 0 1px 1px 0;\n  box-sizing: border-box;\n  height: 17px;\n  width: 17px;\n}\n.tox .tox-collection--list .tox-collection__group .tox-insert-table-picker {\n  margin: -4px -4px;\n}\n.tox .tox-insert-table-picker .tox-insert-table-picker__selected {\n  background-color: rgba(0, 108, 231, 0.5);\n  border-color: rgba(0, 108, 231, 0.5);\n}\n.tox .tox-insert-table-picker__label {\n  color: rgba(34, 47, 62, 0.7);\n  display: block;\n  font-size: 14px;\n  padding: 4px;\n  text-align: center;\n  width: 100%;\n}\n.tox:not([dir=rtl]) {\n  /* stylelint-disable-next-line no-descending-specificity */\n}\n.tox:not([dir=rtl]) .tox-insert-table-picker > div:nth-child(10n) {\n  border-right: 0;\n}\n.tox[dir=rtl] {\n  /* stylelint-disable-next-line no-descending-specificity */\n}\n.tox[dir=rtl] .tox-insert-table-picker > div:nth-child(10n+1) {\n  border-right: 0;\n}\n.tox {\n  /* stylelint-disable */\n  /* stylelint-enable */\n}\n.tox .tox-menu {\n  background-color: #fff;\n  border: 1px solid transparent;\n  border-radius: 6px;\n  box-shadow: 0 0 2px 0 rgba(34, 47, 62, 0.2), 0 4px 8px 0 rgba(34, 47, 62, 0.15);\n  display: inline-block;\n  overflow: hidden;\n  vertical-align: top;\n  z-index: 1150;\n}\n.tox .tox-menu.tox-collection.tox-collection--list {\n  padding: 0 4px;\n}\n.tox .tox-menu.tox-collection.tox-collection--toolbar {\n  padding: 8px;\n}\n.tox .tox-menu.tox-collection.tox-collection--grid {\n  padding: 8px;\n}\n@media only screen and (min-width: 768px ) {\n  .tox .tox-menu .tox-collection__item-label {\n    overflow-wrap: break-word;\n    word-break: normal;\n  }\n}\n.tox .tox-menu__label h1,\n.tox .tox-menu__label h2,\n.tox .tox-menu__label h3,\n.tox .tox-menu__label h4,\n.tox .tox-menu__label h5,\n.tox .tox-menu__label h6,\n.tox .tox-menu__label p,\n.tox .tox-menu__label blockquote,\n.tox .tox-menu__label code {\n  margin: 0;\n}\n.tox .tox-menubar {\n  background: repeating-linear-gradient(transparent 0px 1px, transparent 1px 39px) center top 39px / 100% calc(100% - 39px) no-repeat;\n  background-color: #fff;\n  display: flex;\n  flex: 0 0 auto;\n  flex-shrink: 0;\n  flex-wrap: wrap;\n  grid-column: 1 / -1;\n  grid-row: 1;\n  padding: 0 11px 0 12px;\n}\n.tox .tox-promotion + .tox-menubar {\n  grid-column: 1;\n}\n.tox .tox-promotion {\n  background: repeating-linear-gradient(transparent 0px 1px, transparent 1px 39px) center top 39px / 100% calc(100% - 39px) no-repeat;\n  background-color: #fff;\n  grid-column: 2;\n  grid-row: 1;\n  padding-inline-end: 8px;\n  padding-inline-start: 4px;\n  padding-top: 5px;\n}\n.tox .tox-promotion-link {\n  align-items: unsafe center;\n  background-color: #E8F1F8;\n  border-radius: 5px;\n  color: #086BE6;\n  cursor: pointer;\n  display: flex;\n  font-size: 14px;\n  height: 26.6px;\n  padding: 4px 8px;\n  white-space: nowrap;\n}\n.tox .tox-promotion-link:hover {\n  background-color: #B4D7FF;\n}\n.tox .tox-promotion-link:focus {\n  background-color: #D9EDF7;\n}\n/* Deprecated. Remove in next major release */\n.tox .tox-mbtn {\n  align-items: center;\n  background: transparent;\n  border: 0;\n  border-radius: 3px;\n  box-shadow: none;\n  color: #222f3e;\n  display: flex;\n  flex: 0 0 auto;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: normal;\n  height: 28px;\n  justify-content: center;\n  margin: 5px 1px 6px 0;\n  outline: none;\n  overflow: hidden;\n  padding: 0 4px;\n  text-transform: none;\n  width: auto;\n}\n.tox .tox-mbtn[disabled] {\n  background-color: transparent;\n  border: 0;\n  box-shadow: none;\n  color: rgba(34, 47, 62, 0.5);\n  cursor: not-allowed;\n}\n.tox .tox-mbtn:focus:not(:disabled) {\n  background: #cce2fa;\n  border: 0;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-mbtn--active {\n  background: #a6ccf7;\n  border: 0;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-mbtn:hover:not(:disabled):not(.tox-mbtn--active) {\n  background: #cce2fa;\n  border: 0;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-mbtn__select-label {\n  cursor: default;\n  font-weight: normal;\n  margin: 0 4px;\n}\n.tox .tox-mbtn[disabled] .tox-mbtn__select-label {\n  cursor: not-allowed;\n}\n.tox .tox-mbtn__select-chevron {\n  align-items: center;\n  display: flex;\n  justify-content: center;\n  width: 16px;\n  display: none;\n}\n.tox .tox-notification {\n  border-radius: 6px;\n  border-style: solid;\n  border-width: 1px;\n  box-shadow: none;\n  box-sizing: border-box;\n  display: grid;\n  font-size: 14px;\n  font-weight: normal;\n  grid-template-columns: minmax(40px, 1fr) auto minmax(40px, 1fr);\n  margin-top: 4px;\n  opacity: 0;\n  padding: 4px;\n  transition: transform 100ms ease-in, opacity 150ms ease-in;\n}\n.tox .tox-notification p {\n  font-size: 14px;\n  font-weight: normal;\n}\n.tox .tox-notification a {\n  cursor: pointer;\n  text-decoration: underline;\n}\n.tox .tox-notification--in {\n  opacity: 1;\n}\n.tox .tox-notification--success {\n  background-color: #e4eeda;\n  border-color: #d7e6c8;\n  color: #222f3e;\n}\n.tox .tox-notification--success p {\n  color: #222f3e;\n}\n.tox .tox-notification--success a {\n  color: #517342;\n}\n.tox .tox-notification--success svg {\n  fill: #222f3e;\n}\n.tox .tox-notification--error {\n  background-color: #f5cccc;\n  border-color: #f0b3b3;\n  color: #222f3e;\n}\n.tox .tox-notification--error p {\n  color: #222f3e;\n}\n.tox .tox-notification--error a {\n  color: #77181f;\n}\n.tox .tox-notification--error svg {\n  fill: #222f3e;\n}\n.tox .tox-notification--warn,\n.tox .tox-notification--warning {\n  background-color: #fff5cc;\n  border-color: #fff0b3;\n  color: #222f3e;\n}\n.tox .tox-notification--warn p,\n.tox .tox-notification--warning p {\n  color: #222f3e;\n}\n.tox .tox-notification--warn a,\n.tox .tox-notification--warning a {\n  color: #7a6e25;\n}\n.tox .tox-notification--warn svg,\n.tox .tox-notification--warning svg {\n  fill: #222f3e;\n}\n.tox .tox-notification--info {\n  background-color: #d6e7fb;\n  border-color: #c1dbf9;\n  color: #222f3e;\n}\n.tox .tox-notification--info p {\n  color: #222f3e;\n}\n.tox .tox-notification--info a {\n  color: #2a64a6;\n}\n.tox .tox-notification--info svg {\n  fill: #222f3e;\n}\n.tox .tox-notification__body {\n  align-self: center;\n  color: #222f3e;\n  font-size: 14px;\n  grid-column-end: 3;\n  grid-column-start: 2;\n  grid-row-end: 2;\n  grid-row-start: 1;\n  text-align: center;\n  white-space: normal;\n  word-break: break-all;\n  word-break: break-word;\n}\n.tox .tox-notification__body > * {\n  margin: 0;\n}\n.tox .tox-notification__body > * + * {\n  margin-top: 1rem;\n}\n.tox .tox-notification__icon {\n  align-self: center;\n  grid-column-end: 2;\n  grid-column-start: 1;\n  grid-row-end: 2;\n  grid-row-start: 1;\n  justify-self: end;\n}\n.tox .tox-notification__icon svg {\n  display: block;\n}\n.tox .tox-notification__dismiss {\n  align-self: start;\n  grid-column-end: 4;\n  grid-column-start: 3;\n  grid-row-end: 2;\n  grid-row-start: 1;\n  justify-self: end;\n}\n.tox .tox-notification .tox-progress-bar {\n  grid-column-end: 4;\n  grid-column-start: 1;\n  grid-row-end: 3;\n  grid-row-start: 2;\n  justify-self: center;\n}\n.tox .tox-pop {\n  display: inline-block;\n  position: relative;\n}\n.tox .tox-pop--resizing {\n  transition: width 0.1s ease;\n}\n.tox .tox-pop--resizing .tox-toolbar,\n.tox .tox-pop--resizing .tox-toolbar__group {\n  flex-wrap: nowrap;\n}\n.tox .tox-pop--transition {\n  transition: 0.15s ease;\n  transition-property: left, right, top, bottom;\n}\n.tox .tox-pop--transition::before,\n.tox .tox-pop--transition::after {\n  transition: all 0.15s, visibility 0s, opacity 0.075s ease 0.075s;\n}\n.tox .tox-pop__dialog {\n  background-color: #fff;\n  border: 1px solid #eeeeee;\n  border-radius: 6px;\n  box-shadow: 0 0 2px 0 rgba(34, 47, 62, 0.2), 0 4px 8px 0 rgba(34, 47, 62, 0.15);\n  min-width: 0;\n  overflow: hidden;\n}\n.tox .tox-pop__dialog > *:not(.tox-toolbar) {\n  margin: 4px 4px 4px 8px;\n}\n.tox .tox-pop__dialog .tox-toolbar {\n  background-color: transparent;\n  margin-bottom: -1px;\n}\n.tox .tox-pop::before,\n.tox .tox-pop::after {\n  border-style: solid;\n  content: '';\n  display: block;\n  height: 0;\n  opacity: 1;\n  position: absolute;\n  width: 0;\n}\n.tox .tox-pop.tox-pop--inset::before,\n.tox .tox-pop.tox-pop--inset::after {\n  opacity: 0;\n  transition: all 0s 0.15s, visibility 0s, opacity 0.075s ease;\n}\n.tox .tox-pop.tox-pop--bottom::before,\n.tox .tox-pop.tox-pop--bottom::after {\n  left: 50%;\n  top: 100%;\n}\n.tox .tox-pop.tox-pop--bottom::after {\n  border-color: #fff transparent transparent transparent;\n  border-width: 8px;\n  margin-left: -8px;\n  margin-top: -1px;\n}\n.tox .tox-pop.tox-pop--bottom::before {\n  border-color: #eeeeee transparent transparent transparent;\n  border-width: 9px;\n  margin-left: -9px;\n}\n.tox .tox-pop.tox-pop--top::before,\n.tox .tox-pop.tox-pop--top::after {\n  left: 50%;\n  top: 0;\n  transform: translateY(-100%);\n}\n.tox .tox-pop.tox-pop--top::after {\n  border-color: transparent transparent #fff transparent;\n  border-width: 8px;\n  margin-left: -8px;\n  margin-top: 1px;\n}\n.tox .tox-pop.tox-pop--top::before {\n  border-color: transparent transparent #eeeeee transparent;\n  border-width: 9px;\n  margin-left: -9px;\n}\n.tox .tox-pop.tox-pop--left::before,\n.tox .tox-pop.tox-pop--left::after {\n  left: 0;\n  top: calc(50% - 1px);\n  transform: translateY(-50%);\n}\n.tox .tox-pop.tox-pop--left::after {\n  border-color: transparent #fff transparent transparent;\n  border-width: 8px;\n  margin-left: -15px;\n}\n.tox .tox-pop.tox-pop--left::before {\n  border-color: transparent #eeeeee transparent transparent;\n  border-width: 10px;\n  margin-left: -19px;\n}\n.tox .tox-pop.tox-pop--right::before,\n.tox .tox-pop.tox-pop--right::after {\n  left: 100%;\n  top: calc(50% + 1px);\n  transform: translateY(-50%);\n}\n.tox .tox-pop.tox-pop--right::after {\n  border-color: transparent transparent transparent #fff;\n  border-width: 8px;\n  margin-left: -1px;\n}\n.tox .tox-pop.tox-pop--right::before {\n  border-color: transparent transparent transparent #eeeeee;\n  border-width: 10px;\n  margin-left: -1px;\n}\n.tox .tox-pop.tox-pop--align-left::before,\n.tox .tox-pop.tox-pop--align-left::after {\n  left: 20px;\n}\n.tox .tox-pop.tox-pop--align-right::before,\n.tox .tox-pop.tox-pop--align-right::after {\n  left: calc(100% - 20px);\n}\n.tox .tox-sidebar-wrap {\n  display: flex;\n  flex-direction: row;\n  flex-grow: 1;\n  min-height: 0;\n}\n.tox .tox-sidebar {\n  background-color: #fff;\n  display: flex;\n  flex-direction: row;\n  justify-content: flex-end;\n}\n.tox .tox-sidebar__slider {\n  display: flex;\n  overflow: hidden;\n}\n.tox .tox-sidebar__pane-container {\n  display: flex;\n}\n.tox .tox-sidebar__pane {\n  display: flex;\n}\n.tox .tox-sidebar--sliding-closed {\n  opacity: 0;\n}\n.tox .tox-sidebar--sliding-open {\n  opacity: 1;\n}\n.tox .tox-sidebar--sliding-growing,\n.tox .tox-sidebar--sliding-shrinking {\n  transition: width 0.5s ease, opacity 0.5s ease;\n}\n.tox .tox-selector {\n  background-color: #4099ff;\n  border-color: #4099ff;\n  border-style: solid;\n  border-width: 1px;\n  box-sizing: border-box;\n  display: inline-block;\n  height: 10px;\n  position: absolute;\n  width: 10px;\n}\n.tox.tox-platform-touch .tox-selector {\n  height: 12px;\n  width: 12px;\n}\n.tox .tox-slider {\n  align-items: center;\n  display: flex;\n  flex: 1;\n  height: 24px;\n  justify-content: center;\n  position: relative;\n}\n.tox .tox-slider__rail {\n  background-color: transparent;\n  border: 1px solid #eeeeee;\n  border-radius: 6px;\n  height: 10px;\n  min-width: 120px;\n  width: 100%;\n}\n.tox .tox-slider__handle {\n  background-color: #006ce7;\n  border: 2px solid #0054b4;\n  border-radius: 6px;\n  box-shadow: none;\n  height: 24px;\n  left: 50%;\n  position: absolute;\n  top: 50%;\n  transform: translateX(-50%) translateY(-50%);\n  width: 14px;\n}\n.tox .tox-form__controls-h-stack > .tox-slider:not(:first-of-type) {\n  margin-inline-start: 8px;\n}\n.tox .tox-form__controls-h-stack > .tox-form__group + .tox-slider {\n  margin-inline-start: 32px;\n}\n.tox .tox-form__controls-h-stack > .tox-slider + .tox-form__group {\n  margin-inline-start: 32px;\n}\n.tox .tox-source-code {\n  overflow: auto;\n}\n.tox .tox-spinner {\n  display: flex;\n}\n.tox .tox-spinner > div {\n  animation: tam-bouncing-dots 1.5s ease-in-out 0s infinite both;\n  background-color: rgba(34, 47, 62, 0.7);\n  border-radius: 100%;\n  height: 8px;\n  width: 8px;\n}\n.tox .tox-spinner > div:nth-child(1) {\n  animation-delay: -0.32s;\n}\n.tox .tox-spinner > div:nth-child(2) {\n  animation-delay: -0.16s;\n}\n@keyframes tam-bouncing-dots {\n  0%,\n  80%,\n  100% {\n    transform: scale(0);\n  }\n  40% {\n    transform: scale(1);\n  }\n}\n.tox:not([dir=rtl]) .tox-spinner > div:not(:first-child) {\n  margin-left: 4px;\n}\n.tox[dir=rtl] .tox-spinner > div:not(:first-child) {\n  margin-right: 4px;\n}\n.tox .tox-statusbar {\n  align-items: center;\n  background-color: #fff;\n  border-top: 1px solid #e3e3e3;\n  color: rgba(34, 47, 62, 0.7);\n  display: flex;\n  flex: 0 0 auto;\n  font-size: 14px;\n  font-weight: normal;\n  height: 25px;\n  overflow: hidden;\n  padding: 0 8px;\n  position: relative;\n  text-transform: none;\n}\n.tox .tox-statusbar__text-container {\n  display: flex;\n  flex: 1 1 auto;\n  justify-content: flex-end;\n  overflow: hidden;\n}\n.tox .tox-statusbar__path {\n  display: flex;\n  flex: 1 1 auto;\n  margin-right: auto;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n.tox .tox-statusbar__path > * {\n  display: inline;\n  white-space: nowrap;\n}\n.tox .tox-statusbar__wordcount {\n  flex: 0 0 auto;\n  margin-left: 1ch;\n}\n.tox .tox-statusbar a,\n.tox .tox-statusbar__path-item,\n.tox .tox-statusbar__wordcount {\n  color: rgba(34, 47, 62, 0.7);\n  text-decoration: none;\n}\n.tox .tox-statusbar a:hover:not(:disabled):not([aria-disabled=true]),\n.tox .tox-statusbar__path-item:hover:not(:disabled):not([aria-disabled=true]),\n.tox .tox-statusbar__wordcount:hover:not(:disabled):not([aria-disabled=true]),\n.tox .tox-statusbar a:focus:not(:disabled):not([aria-disabled=true]),\n.tox .tox-statusbar__path-item:focus:not(:disabled):not([aria-disabled=true]),\n.tox .tox-statusbar__wordcount:focus:not(:disabled):not([aria-disabled=true]) {\n  color: #222f3e;\n  cursor: pointer;\n}\n.tox .tox-statusbar__branding svg {\n  fill: rgba(34, 47, 62, 0.8);\n  height: 1.14em;\n  vertical-align: -0.28em;\n  width: 3.6em;\n}\n.tox .tox-statusbar__branding a:hover:not(:disabled):not([aria-disabled=true]) svg,\n.tox .tox-statusbar__branding a:focus:not(:disabled):not([aria-disabled=true]) svg {\n  fill: #222f3e;\n}\n.tox .tox-statusbar__resize-handle {\n  align-items: flex-end;\n  align-self: stretch;\n  cursor: nwse-resize;\n  display: flex;\n  flex: 0 0 auto;\n  justify-content: flex-end;\n  margin-left: auto;\n  margin-right: -8px;\n  padding-bottom: 3px;\n  padding-left: 1ch;\n  padding-right: 3px;\n}\n.tox .tox-statusbar__resize-handle svg {\n  display: block;\n  fill: rgba(34, 47, 62, 0.5);\n}\n.tox .tox-statusbar__resize-handle:focus svg {\n  background-color: #dee0e2;\n  border-radius: 1px 1px 5px 1px;\n  box-shadow: 0 0 0 2px #dee0e2;\n}\n.tox:not([dir=rtl]) .tox-statusbar__path > * {\n  margin-right: 4px;\n}\n.tox:not([dir=rtl]) .tox-statusbar__branding {\n  margin-left: 2ch;\n}\n.tox[dir=rtl] .tox-statusbar {\n  flex-direction: row-reverse;\n}\n.tox[dir=rtl] .tox-statusbar__path > * {\n  margin-left: 4px;\n}\n.tox .tox-throbber {\n  z-index: 1299;\n}\n.tox .tox-throbber__busy-spinner {\n  align-items: center;\n  background-color: rgba(255, 255, 255, 0.6);\n  bottom: 0;\n  display: flex;\n  justify-content: center;\n  left: 0;\n  position: absolute;\n  right: 0;\n  top: 0;\n}\n.tox .tox-tbtn {\n  align-items: center;\n  background: transparent;\n  border: 0;\n  border-radius: 3px;\n  box-shadow: none;\n  color: #222f3e;\n  display: flex;\n  flex: 0 0 auto;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: normal;\n  height: 28px;\n  justify-content: center;\n  margin: 6px 1px 5px 0;\n  outline: none;\n  overflow: hidden;\n  padding: 0;\n  text-transform: none;\n  width: 34px;\n}\n.tox .tox-tbtn svg {\n  display: block;\n  fill: #222f3e;\n}\n.tox .tox-tbtn.tox-tbtn-more {\n  padding-left: 5px;\n  padding-right: 5px;\n  width: inherit;\n}\n.tox .tox-tbtn:focus {\n  background: #cce2fa;\n  border: 0;\n  box-shadow: none;\n}\n.tox .tox-tbtn:hover {\n  background: #cce2fa;\n  border: 0;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-tbtn:hover svg {\n  fill: #222f3e;\n}\n.tox .tox-tbtn:active {\n  background: #a6ccf7;\n  border: 0;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-tbtn:active svg {\n  fill: #222f3e;\n}\n.tox .tox-tbtn--disabled .tox-tbtn--enabled svg {\n  fill: rgba(34, 47, 62, 0.5);\n}\n.tox .tox-tbtn--disabled,\n.tox .tox-tbtn--disabled:hover,\n.tox .tox-tbtn:disabled,\n.tox .tox-tbtn:disabled:hover {\n  background: transparent;\n  border: 0;\n  box-shadow: none;\n  color: rgba(34, 47, 62, 0.5);\n  cursor: not-allowed;\n}\n.tox .tox-tbtn--disabled svg,\n.tox .tox-tbtn--disabled:hover svg,\n.tox .tox-tbtn:disabled svg,\n.tox .tox-tbtn:disabled:hover svg {\n  /* stylelint-disable-line no-descending-specificity */\n  fill: rgba(34, 47, 62, 0.5);\n}\n.tox .tox-tbtn--enabled,\n.tox .tox-tbtn--enabled:hover {\n  background: #a6ccf7;\n  border: 0;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-tbtn--enabled > *,\n.tox .tox-tbtn--enabled:hover > * {\n  transform: none;\n}\n.tox .tox-tbtn--enabled svg,\n.tox .tox-tbtn--enabled:hover svg {\n  /* stylelint-disable-line no-descending-specificity */\n  fill: #222f3e;\n}\n.tox .tox-tbtn--enabled.tox-tbtn--disabled svg,\n.tox .tox-tbtn--enabled:hover.tox-tbtn--disabled svg {\n  fill: rgba(34, 47, 62, 0.5);\n}\n.tox .tox-tbtn:focus:not(.tox-tbtn--disabled) {\n  color: #222f3e;\n}\n.tox .tox-tbtn:focus:not(.tox-tbtn--disabled) svg {\n  fill: #222f3e;\n}\n.tox .tox-tbtn:active > * {\n  transform: none;\n}\n.tox .tox-tbtn--md {\n  height: 42px;\n  width: 51px;\n}\n.tox .tox-tbtn--lg {\n  flex-direction: column;\n  height: 56px;\n  width: 68px;\n}\n.tox .tox-tbtn--return {\n  align-self: stretch;\n  height: unset;\n  width: 16px;\n}\n.tox .tox-tbtn--labeled {\n  padding: 0 4px;\n  width: unset;\n}\n.tox .tox-tbtn__vlabel {\n  display: block;\n  font-size: 10px;\n  font-weight: normal;\n  letter-spacing: -0.025em;\n  margin-bottom: 4px;\n  white-space: nowrap;\n}\n.tox .tox-number-input {\n  border-radius: 3px;\n  display: flex;\n  margin: 6px 1px 5px 0;\n  padding: 0 4px;\n  width: auto;\n}\n.tox .tox-number-input .tox-input-wrapper {\n  background: #f7f7f7;\n  display: flex;\n  pointer-events: none;\n  text-align: center;\n}\n.tox .tox-number-input .tox-input-wrapper:focus {\n  background: #cce2fa;\n}\n.tox .tox-number-input input {\n  border-radius: 3px;\n  color: #222f3e;\n  font-size: 14px;\n  margin: 2px 0;\n  pointer-events: all;\n  width: 60px;\n}\n.tox .tox-number-input input:hover {\n  background: #cce2fa;\n  color: #222f3e;\n}\n.tox .tox-number-input input:focus {\n  background: #fff;\n  color: #222f3e;\n}\n.tox .tox-number-input input:disabled {\n  background: transparent;\n  border: 0;\n  box-shadow: none;\n  color: rgba(34, 47, 62, 0.5);\n  cursor: not-allowed;\n}\n.tox .tox-number-input button {\n  background: #f7f7f7;\n  color: #222f3e;\n  height: 28px;\n  text-align: center;\n  width: 24px;\n}\n.tox .tox-number-input button svg {\n  display: block;\n  fill: #222f3e;\n  margin: 0 auto;\n  transform: scale(0.67);\n}\n.tox .tox-number-input button:focus {\n  background: #cce2fa;\n}\n.tox .tox-number-input button:hover {\n  background: #cce2fa;\n  border: 0;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-number-input button:hover svg {\n  fill: #222f3e;\n}\n.tox .tox-number-input button:active {\n  background: #a6ccf7;\n  border: 0;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-number-input button:active svg {\n  fill: #222f3e;\n}\n.tox .tox-number-input button:disabled {\n  background: transparent;\n  border: 0;\n  box-shadow: none;\n  color: rgba(34, 47, 62, 0.5);\n  cursor: not-allowed;\n}\n.tox .tox-number-input button:disabled svg {\n  fill: rgba(34, 47, 62, 0.5);\n}\n.tox .tox-number-input button.minus {\n  border-radius: 3px 0 0 3px;\n}\n.tox .tox-number-input button.plus {\n  border-radius: 0 3px 3px 0;\n}\n.tox .tox-number-input:focus:not(:active) > button,\n.tox .tox-number-input:focus:not(:active) > .tox-input-wrapper {\n  background: #cce2fa;\n}\n.tox .tox-tbtn--select {\n  margin: 6px 1px 5px 0;\n  padding: 0 4px;\n  width: auto;\n}\n.tox .tox-tbtn__select-label {\n  cursor: default;\n  font-weight: normal;\n  height: initial;\n  margin: 0 4px;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n.tox .tox-tbtn__select-chevron {\n  align-items: center;\n  display: flex;\n  justify-content: center;\n  width: 16px;\n}\n.tox .tox-tbtn__select-chevron svg {\n  fill: rgba(34, 47, 62, 0.5);\n}\n.tox .tox-tbtn--bespoke {\n  background: #f7f7f7;\n}\n.tox .tox-tbtn--bespoke + .tox-tbtn--bespoke {\n  margin-inline-start: 4px;\n}\n.tox .tox-tbtn--bespoke .tox-tbtn__select-label {\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  width: 7em;\n}\n.tox .tox-tbtn--disabled .tox-tbtn__select-label,\n.tox .tox-tbtn--select:disabled .tox-tbtn__select-label {\n  cursor: not-allowed;\n}\n.tox .tox-split-button {\n  border: 0;\n  border-radius: 3px;\n  box-sizing: border-box;\n  display: flex;\n  margin: 6px 1px 5px 0;\n  overflow: hidden;\n}\n.tox .tox-split-button:hover {\n  box-shadow: 0 0 0 1px #cce2fa inset;\n}\n.tox .tox-split-button:focus {\n  background: #cce2fa;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-split-button > * {\n  border-radius: 0;\n}\n.tox .tox-split-button__chevron {\n  width: 16px;\n}\n.tox .tox-split-button__chevron svg {\n  fill: rgba(34, 47, 62, 0.5);\n}\n.tox .tox-split-button .tox-tbtn {\n  margin: 0;\n}\n.tox .tox-split-button.tox-tbtn--disabled:hover,\n.tox .tox-split-button.tox-tbtn--disabled:focus,\n.tox .tox-split-button.tox-tbtn--disabled .tox-tbtn:hover,\n.tox .tox-split-button.tox-tbtn--disabled .tox-tbtn:focus {\n  background: transparent;\n  box-shadow: none;\n  color: rgba(34, 47, 62, 0.5);\n}\n.tox.tox-platform-touch .tox-split-button .tox-tbtn--select {\n  padding: 0 0px;\n}\n.tox.tox-platform-touch .tox-split-button .tox-tbtn:not(.tox-tbtn--select):first-child {\n  width: 30px;\n}\n.tox.tox-platform-touch .tox-split-button__chevron {\n  width: 20px;\n}\n.tox .tox-split-button.tox-tbtn--disabled svg #tox-icon-text-color__color,\n.tox .tox-split-button.tox-tbtn--disabled svg #tox-icon-highlight-bg-color__color {\n  opacity: 0.6;\n}\n.tox .tox-toolbar-overlord {\n  background-color: #fff;\n}\n.tox .tox-toolbar,\n.tox .tox-toolbar__primary,\n.tox .tox-toolbar__overflow {\n  background-attachment: local;\n  background-color: #fff;\n  background-image: repeating-linear-gradient(#e3e3e3 0px 1px, transparent 1px 39px);\n  background-position: center top 40px;\n  background-repeat: no-repeat;\n  background-size: calc(100% - 11px * 2) calc(100% - 41px);\n  display: flex;\n  flex: 0 0 auto;\n  flex-shrink: 0;\n  flex-wrap: wrap;\n  padding: 0 0px;\n  transform: perspective(1px);\n}\n.tox .tox-toolbar-overlord > .tox-toolbar,\n.tox .tox-toolbar-overlord > .tox-toolbar__primary,\n.tox .tox-toolbar-overlord > .tox-toolbar__overflow {\n  background-position: center top 0px;\n  background-size: calc(100% - 11px * 2) calc(100% - 0px);\n}\n.tox .tox-toolbar__overflow.tox-toolbar__overflow--closed {\n  height: 0;\n  opacity: 0;\n  padding-bottom: 0;\n  padding-top: 0;\n  visibility: hidden;\n}\n.tox .tox-toolbar__overflow--growing {\n  transition: height 0.3s ease, opacity 0.2s linear 0.1s;\n}\n.tox .tox-toolbar__overflow--shrinking {\n  transition: opacity 0.3s ease, height 0.2s linear 0.1s, visibility 0s linear 0.3s;\n}\n.tox .tox-toolbar-overlord,\n.tox .tox-anchorbar {\n  grid-column: 1 / -1;\n}\n.tox .tox-menubar + .tox-toolbar,\n.tox .tox-menubar + .tox-toolbar-overlord {\n  border-top: 1px solid transparent;\n  margin-top: -1px;\n  padding-bottom: 1px;\n  padding-top: 1px;\n}\n.tox .tox-toolbar--scrolling {\n  flex-wrap: nowrap;\n  overflow-x: auto;\n}\n.tox .tox-pop .tox-toolbar {\n  border-width: 0;\n}\n.tox .tox-toolbar--no-divider {\n  background-image: none;\n}\n.tox .tox-toolbar-overlord .tox-toolbar:not(.tox-toolbar--scrolling):first-child,\n.tox .tox-toolbar-overlord .tox-toolbar__primary {\n  background-position: center top 39px;\n}\n.tox .tox-editor-header > .tox-toolbar--scrolling,\n.tox .tox-toolbar-overlord .tox-toolbar--scrolling:first-child {\n  background-image: none;\n}\n.tox.tox-tinymce-aux .tox-toolbar__overflow {\n  background-color: #fff;\n  background-position: center top 43px;\n  background-size: calc(100% - 8px * 2) calc(100% - 51px);\n  border: none;\n  border-radius: 6px;\n  box-shadow: 0 0 2px 0 rgba(34, 47, 62, 0.2), 0 4px 8px 0 rgba(34, 47, 62, 0.15);\n  overscroll-behavior: none;\n  padding: 4px 0;\n}\n.tox-pop .tox-pop__dialog {\n  /* stylelint-disable-next-line no-descending-specificity */\n}\n.tox-pop .tox-pop__dialog .tox-toolbar {\n  background-position: center top 43px;\n  background-size: calc(100% - 11px * 2) calc(100% - 51px);\n  padding: 4px 0;\n}\n.tox .tox-toolbar__group {\n  align-items: center;\n  display: flex;\n  flex-wrap: wrap;\n  margin: 0 0;\n  padding: 0 11px 0 12px;\n}\n.tox .tox-toolbar__group--pull-right {\n  margin-left: auto;\n}\n.tox .tox-toolbar--scrolling .tox-toolbar__group {\n  flex-shrink: 0;\n  flex-wrap: nowrap;\n}\n.tox:not([dir=rtl]) .tox-toolbar__group:not(:last-of-type) {\n  border-right: 1px solid transparent;\n}\n.tox[dir=rtl] .tox-toolbar__group:not(:last-of-type) {\n  border-left: 1px solid transparent;\n}\n.tox .tox-tooltip {\n  display: inline-block;\n  padding: 8px;\n  position: relative;\n}\n.tox .tox-tooltip__body {\n  background-color: #222f3e;\n  border-radius: 6px;\n  box-shadow: 0 2px 4px rgba(34, 47, 62, 0.3);\n  color: rgba(255, 255, 255, 0.75);\n  font-size: 14px;\n  font-style: normal;\n  font-weight: normal;\n  padding: 4px 8px;\n  text-transform: none;\n}\n.tox .tox-tooltip__arrow {\n  position: absolute;\n}\n.tox .tox-tooltip--down .tox-tooltip__arrow {\n  border-left: 8px solid transparent;\n  border-right: 8px solid transparent;\n  border-top: 8px solid #222f3e;\n  bottom: 0;\n  left: 50%;\n  position: absolute;\n  transform: translateX(-50%);\n}\n.tox .tox-tooltip--up .tox-tooltip__arrow {\n  border-bottom: 8px solid #222f3e;\n  border-left: 8px solid transparent;\n  border-right: 8px solid transparent;\n  left: 50%;\n  position: absolute;\n  top: 0;\n  transform: translateX(-50%);\n}\n.tox .tox-tooltip--right .tox-tooltip__arrow {\n  border-bottom: 8px solid transparent;\n  border-left: 8px solid #222f3e;\n  border-top: 8px solid transparent;\n  position: absolute;\n  right: 0;\n  top: 50%;\n  transform: translateY(-50%);\n}\n.tox .tox-tooltip--left .tox-tooltip__arrow {\n  border-bottom: 8px solid transparent;\n  border-right: 8px solid #222f3e;\n  border-top: 8px solid transparent;\n  left: 0;\n  position: absolute;\n  top: 50%;\n  transform: translateY(-50%);\n}\n.tox .tox-tree {\n  display: flex;\n  flex-direction: column;\n}\n.tox .tox-tree .tox-trbtn {\n  align-items: center;\n  background: transparent;\n  border: 0;\n  border-radius: 4px;\n  box-shadow: none;\n  color: #222f3e;\n  display: flex;\n  flex: 0 0 auto;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: normal;\n  height: 28px;\n  margin-bottom: 4px;\n  margin-top: 4px;\n  outline: none;\n  overflow: hidden;\n  padding: 0;\n  padding-left: 8px;\n  text-transform: none;\n}\n.tox .tox-tree .tox-trbtn .tox-tree__label {\n  cursor: default;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n.tox .tox-tree .tox-trbtn svg {\n  display: block;\n  fill: #222f3e;\n}\n.tox .tox-tree .tox-trbtn:focus {\n  background: #cce2fa;\n  border: 0;\n  box-shadow: none;\n}\n.tox .tox-tree .tox-trbtn:hover {\n  background: #cce2fa;\n  border: 0;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-tree .tox-trbtn:hover svg {\n  fill: #222f3e;\n}\n.tox .tox-tree .tox-trbtn:active {\n  background: #a6ccf7;\n  border: 0;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-tree .tox-trbtn:active svg {\n  fill: #222f3e;\n}\n.tox .tox-tree .tox-trbtn--disabled,\n.tox .tox-tree .tox-trbtn--disabled:hover,\n.tox .tox-tree .tox-trbtn:disabled,\n.tox .tox-tree .tox-trbtn:disabled:hover {\n  background: transparent;\n  border: 0;\n  box-shadow: none;\n  color: rgba(34, 47, 62, 0.5);\n  cursor: not-allowed;\n}\n.tox .tox-tree .tox-trbtn--disabled svg,\n.tox .tox-tree .tox-trbtn--disabled:hover svg,\n.tox .tox-tree .tox-trbtn:disabled svg,\n.tox .tox-tree .tox-trbtn:disabled:hover svg {\n  /* stylelint-disable-line no-descending-specificity */\n  fill: rgba(34, 47, 62, 0.5);\n}\n.tox .tox-tree .tox-trbtn--enabled,\n.tox .tox-tree .tox-trbtn--enabled:hover {\n  background: #a6ccf7;\n  border: 0;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-tree .tox-trbtn--enabled > *,\n.tox .tox-tree .tox-trbtn--enabled:hover > * {\n  transform: none;\n}\n.tox .tox-tree .tox-trbtn--enabled svg,\n.tox .tox-tree .tox-trbtn--enabled:hover svg {\n  /* stylelint-disable-line no-descending-specificity */\n  fill: #222f3e;\n}\n.tox .tox-tree .tox-trbtn:focus:not(.tox-trbtn--disabled) {\n  color: #222f3e;\n}\n.tox .tox-tree .tox-trbtn:focus:not(.tox-trbtn--disabled) svg {\n  fill: #222f3e;\n}\n.tox .tox-tree .tox-trbtn:active > * {\n  transform: none;\n}\n.tox .tox-tree .tox-trbtn--return {\n  align-self: stretch;\n  height: unset;\n  width: 16px;\n}\n.tox .tox-tree .tox-trbtn--labeled {\n  padding: 0 4px;\n  width: unset;\n}\n.tox .tox-tree .tox-trbtn__vlabel {\n  display: block;\n  font-size: 10px;\n  font-weight: normal;\n  letter-spacing: -0.025em;\n  margin-bottom: 4px;\n  white-space: nowrap;\n}\n.tox .tox-tree .tox-tree--directory {\n  display: flex;\n  flex-direction: column;\n  /* stylelint-disable no-descending-specificity */\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label {\n  font-weight: bold;\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-mbtn {\n  margin-left: auto;\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-mbtn svg {\n  fill: transparent;\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-mbtn.tox-mbtn--active svg,\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-mbtn:focus svg {\n  fill: #222f3e;\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:hover .tox-mbtn svg,\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:focus .tox-mbtn svg {\n  fill: #222f3e;\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:hover:has(.tox-mbtn:hover) {\n  background-color: transparent;\n  color: #222f3e;\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:hover:has(.tox-mbtn:hover) .tox-chevron svg {\n  fill: #222f3e;\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-chevron {\n  margin-right: 6px;\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:has(+ .tox-tree--directory__children--growing) .tox-chevron,\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:has(+ .tox-tree--directory__children--shrinking) .tox-chevron {\n  transition: transform 0.5s ease-in-out;\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:has(+ .tox-tree--directory__children--growing) .tox-chevron,\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:has(+ .tox-tree--directory__children--open) .tox-chevron {\n  transform: rotate(90deg);\n}\n.tox .tox-tree .tox-tree--leaf__label {\n  font-weight: normal;\n}\n.tox .tox-tree .tox-tree--leaf__label .tox-mbtn {\n  margin-left: auto;\n}\n.tox .tox-tree .tox-tree--leaf__label .tox-mbtn svg {\n  fill: transparent;\n}\n.tox .tox-tree .tox-tree--leaf__label .tox-mbtn.tox-mbtn--active svg,\n.tox .tox-tree .tox-tree--leaf__label .tox-mbtn:focus svg {\n  fill: #222f3e;\n}\n.tox .tox-tree .tox-tree--leaf__label:hover .tox-mbtn svg {\n  fill: #222f3e;\n}\n.tox .tox-tree .tox-tree--leaf__label:hover:has(.tox-mbtn:hover) {\n  background-color: transparent;\n  color: #222f3e;\n}\n.tox .tox-tree .tox-tree--leaf__label:hover:has(.tox-mbtn:hover) .tox-chevron svg {\n  fill: #222f3e;\n}\n.tox .tox-tree .tox-tree--directory__children {\n  overflow: hidden;\n  padding-left: 16px;\n}\n.tox .tox-tree .tox-tree--directory__children.tox-tree--directory__children--growing,\n.tox .tox-tree .tox-tree--directory__children.tox-tree--directory__children--shrinking {\n  transition: height 0.5s ease-in-out;\n}\n.tox .tox-tree .tox-trbtn.tox-tree--leaf__label {\n  display: flex;\n  justify-content: space-between;\n}\n.tox .tox-view-wrap,\n.tox .tox-view-wrap__slot-container {\n  background-color: #fff;\n  display: flex;\n  flex: 1;\n  flex-direction: column;\n}\n.tox .tox-view {\n  display: flex;\n  flex: 1 1 auto;\n  flex-direction: column;\n  overflow: hidden;\n}\n.tox .tox-view__header {\n  align-items: center;\n  display: flex;\n  font-size: 16px;\n  justify-content: space-between;\n  padding: 8px 8px 0 8px;\n  position: relative;\n}\n.tox .tox-view--mobile.tox-view__header,\n.tox .tox-view--mobile.tox-view__toolbar {\n  padding: 8px;\n}\n.tox .tox-view--scrolling {\n  flex-wrap: nowrap;\n  overflow-x: auto;\n}\n.tox .tox-view__toolbar {\n  display: flex;\n  flex-direction: row;\n  gap: 8px;\n  justify-content: space-between;\n  padding: 8px 8px 0 8px;\n}\n.tox .tox-view__toolbar__group {\n  display: flex;\n  flex-direction: row;\n  gap: 12px;\n}\n.tox .tox-view__header-start,\n.tox .tox-view__header-end {\n  display: flex;\n}\n.tox .tox-view__pane {\n  height: 100%;\n  padding: 8px;\n  width: 100%;\n}\n.tox .tox-view__pane_panel {\n  border: 1px solid #eeeeee;\n  border-radius: 6px;\n}\n.tox:not([dir=rtl]) .tox-view__header .tox-view__header-start > *,\n.tox:not([dir=rtl]) .tox-view__header .tox-view__header-end > * {\n  margin-left: 8px;\n}\n.tox[dir=rtl] .tox-view__header .tox-view__header-start > *,\n.tox[dir=rtl] .tox-view__header .tox-view__header-end > * {\n  margin-right: 8px;\n}\n.tox .tox-well {\n  border: 1px solid #eeeeee;\n  border-radius: 6px;\n  padding: 8px;\n  width: 100%;\n}\n.tox .tox-well > *:first-child {\n  margin-top: 0;\n}\n.tox .tox-well > *:last-child {\n  margin-bottom: 0;\n}\n.tox .tox-well > *:only-child {\n  margin: 0;\n}\n.tox .tox-custom-editor {\n  border: 1px solid #eeeeee;\n  border-radius: 6px;\n  display: flex;\n  flex: 1;\n  overflow: hidden;\n  position: relative;\n}\n/* stylelint-disable */\n.tox {\n  /* stylelint-enable */\n}\n.tox .tox-dialog-loading::before {\n  background-color: rgba(0, 0, 0, 0.5);\n  content: \"\";\n  height: 100%;\n  position: absolute;\n  width: 100%;\n  z-index: 1000;\n}\n.tox .tox-tab {\n  cursor: pointer;\n}\n.tox .tox-dialog__content-js {\n  display: flex;\n  flex: 1;\n}\n.tox .tox-dialog__body-content .tox-collection {\n  display: flex;\n  flex: 1;\n}\n"
  },
  {
    "path": "minimalist-vue3/public/tinymce/skins/ui/oxide/skin.shadowdom.css",
    "content": "body.tox-dialog__disable-scroll {\n  overflow: hidden;\n}\n.tox-fullscreen {\n  border: 0;\n  height: 100%;\n  margin: 0;\n  overflow: hidden;\n  overscroll-behavior: none;\n  padding: 0;\n  touch-action: pinch-zoom;\n  width: 100%;\n}\n.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle {\n  display: none;\n}\n.tox.tox-tinymce.tox-fullscreen,\n.tox-shadowhost.tox-fullscreen {\n  left: 0;\n  position: fixed;\n  top: 0;\n  z-index: 1200;\n}\n.tox.tox-tinymce.tox-fullscreen {\n  background-color: transparent;\n}\n.tox-fullscreen .tox.tox-tinymce-aux,\n.tox-fullscreen ~ .tox.tox-tinymce-aux {\n  z-index: 1201;\n}\n"
  },
  {
    "path": "minimalist-vue3/public/tinymce/skins/ui/oxide-dark/content.css",
    "content": ".mce-content-body .mce-item-anchor {\n  background: transparent url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'8'%20height%3D'12'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20d%3D'M0%200L8%200%208%2012%204.09117821%209%200%2012z'%20fill%3D%22%23cccccc%22%2F%3E%3C%2Fsvg%3E%0A\") no-repeat center;\n}\n.mce-content-body .mce-item-anchor:empty {\n  cursor: default;\n  display: inline-block;\n  height: 12px !important;\n  padding: 0 2px;\n  -webkit-user-modify: read-only;\n  -moz-user-modify: read-only;\n  -webkit-user-select: all;\n  -moz-user-select: all;\n  user-select: all;\n  width: 8px !important;\n}\n.mce-content-body .mce-item-anchor:not(:empty) {\n  background-position-x: 2px;\n  display: inline-block;\n  padding-left: 12px;\n}\n.mce-content-body .mce-item-anchor[data-mce-selected] {\n  outline-offset: 1px;\n}\n.tox-comments-visible .tox-comment[contenteditable=\"false\"]:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment img:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment > audio:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment > video:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment span.mce-preview-object:not([data-mce-selected]) {\n  outline: 3px solid #ffe89d;\n}\n.tox-comments-visible .tox-comment[contenteditable=\"false\"][data-mce-annotation-active=\"true\"]:not([data-mce-selected]) {\n  outline: 3px solid #fed635;\n}\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"] img:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"] > audio:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"] > video:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"] span.mce-preview-object:not([data-mce-selected]) {\n  outline: 3px solid #fed635;\n}\n.tox-comments-visible span.tox-comment:not([data-mce-selected]) {\n  background-color: #ffe89d;\n  outline: none;\n}\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"]:not([data-mce-selected=\"inline-boundary\"]) {\n  background-color: #fed635;\n}\n.tox-checklist > li:not(.tox-checklist--hidden) {\n  list-style: none;\n  margin: 0.25em 0;\n}\n.tox-checklist > li:not(.tox-checklist--hidden)::before {\n  content: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-unchecked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2215%22%20height%3D%2215%22%20x%3D%22.5%22%20y%3D%22.5%22%20fill-rule%3D%22nonzero%22%20stroke%3D%22%236d737b%22%20rx%3D%222%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A\");\n  cursor: pointer;\n  height: 1em;\n  margin-left: -1.5em;\n  margin-top: 0.125em;\n  position: absolute;\n  width: 1em;\n}\n.tox-checklist li:not(.tox-checklist--hidden).tox-checklist--checked::before {\n  content: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-checked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22%234099FF%22%20fill-rule%3D%22nonzero%22%20rx%3D%222%22%2F%3E%3Cpath%20id%3D%22Path%22%20fill%3D%22%23FFF%22%20fill-rule%3D%22nonzero%22%20d%3D%22M11.5703186%2C3.14417309%20C11.8516238%2C2.73724603%2012.4164781%2C2.62829933%2012.83558%2C2.89774797%20C13.260121%2C3.17069355%2013.3759736%2C3.72932262%2013.0909105%2C4.14168582%20L7.7580587%2C11.8560195%20C7.43776896%2C12.3193404%206.76483983%2C12.3852142%206.35607322%2C11.9948725%20L3.02491697%2C8.8138662%20C2.66090143%2C8.46625845%202.65798871%2C7.89594698%203.01850234%2C7.54483354%20C3.373942%2C7.19866177%203.94940006%2C7.19592841%204.30829608%2C7.5386474%20L6.85276923%2C9.9684299%20L11.5703186%2C3.14417309%20Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A\");\n}\n[dir=rtl] .tox-checklist > li:not(.tox-checklist--hidden)::before {\n  margin-left: 0;\n  margin-right: -1.5em;\n}\n/* stylelint-disable */\n/* http://prismjs.com/ */\n/**\n * Dracula Theme originally by Zeno Rocha [@zenorocha]\n * https://draculatheme.com/\n *\n * Ported for PrismJS by Albert Vallverdu [@byverdu]\n */\ncode[class*=\"language-\"],\npre[class*=\"language-\"] {\n  color: #f8f8f2;\n  background: none;\n  text-shadow: 0 1px rgba(0, 0, 0, 0.3);\n  font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;\n  text-align: left;\n  white-space: pre;\n  word-spacing: normal;\n  word-break: normal;\n  word-wrap: normal;\n  line-height: 1.5;\n  -moz-tab-size: 4;\n  tab-size: 4;\n  -webkit-hyphens: none;\n  hyphens: none;\n}\n/* Code blocks */\npre[class*=\"language-\"] {\n  padding: 1em;\n  margin: 0.5em 0;\n  overflow: auto;\n  border-radius: 0.3em;\n}\n:not(pre) > code[class*=\"language-\"],\npre[class*=\"language-\"] {\n  background: #282a36;\n}\n/* Inline code */\n:not(pre) > code[class*=\"language-\"] {\n  padding: 0.1em;\n  border-radius: 0.3em;\n  white-space: normal;\n}\n.token.comment,\n.token.prolog,\n.token.doctype,\n.token.cdata {\n  color: #6272a4;\n}\n.token.punctuation {\n  color: #f8f8f2;\n}\n.namespace {\n  opacity: 0.7;\n}\n.token.property,\n.token.tag,\n.token.constant,\n.token.symbol,\n.token.deleted {\n  color: #ff79c6;\n}\n.token.boolean,\n.token.number {\n  color: #bd93f9;\n}\n.token.selector,\n.token.attr-name,\n.token.string,\n.token.char,\n.token.builtin,\n.token.inserted {\n  color: #50fa7b;\n}\n.token.operator,\n.token.entity,\n.token.url,\n.language-css .token.string,\n.style .token.string,\n.token.variable {\n  color: #f8f8f2;\n}\n.token.atrule,\n.token.attr-value,\n.token.function,\n.token.class-name {\n  color: #f1fa8c;\n}\n.token.keyword {\n  color: #8be9fd;\n}\n.token.regex,\n.token.important {\n  color: #ffb86c;\n}\n.token.important,\n.token.bold {\n  font-weight: bold;\n}\n.token.italic {\n  font-style: italic;\n}\n.token.entity {\n  cursor: help;\n}\n/* stylelint-enable */\n.mce-content-body {\n  overflow-wrap: break-word;\n  word-wrap: break-word;\n}\n.mce-content-body .mce-visual-caret {\n  background-color: black;\n  background-color: currentColor;\n  position: absolute;\n}\n.mce-content-body .mce-visual-caret-hidden {\n  display: none;\n}\n.mce-content-body *[data-mce-caret] {\n  left: -1000px;\n  margin: 0;\n  padding: 0;\n  position: absolute;\n  right: auto;\n  top: 0;\n}\n.mce-content-body .mce-offscreen-selection {\n  left: -2000000px;\n  max-width: 1000000px;\n  position: absolute;\n}\n.mce-content-body *[contentEditable=false] {\n  cursor: default;\n}\n.mce-content-body *[contentEditable=true] {\n  cursor: text;\n}\n.tox-cursor-format-painter {\n  cursor: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A\"), default;\n}\ndiv.mce-footnotes hr {\n  margin-inline-end: auto;\n  margin-inline-start: 0;\n  width: 25%;\n}\ndiv.mce-footnotes li > a.mce-footnotes-backlink {\n  text-decoration: none;\n}\n@media print {\n  sup.mce-footnote a {\n    color: black;\n    text-decoration: none;\n  }\n  div.mce-footnotes {\n    break-inside: avoid;\n    width: 100%;\n  }\n  div.mce-footnotes li > a.mce-footnotes-backlink {\n    display: none;\n  }\n}\n.mce-content-body figure.align-left {\n  float: left;\n}\n.mce-content-body figure.align-right {\n  float: right;\n}\n.mce-content-body figure.image.align-center {\n  display: table;\n  margin-left: auto;\n  margin-right: auto;\n}\n.mce-preview-object {\n  border: 1px solid gray;\n  display: inline-block;\n  line-height: 0;\n  margin: 0 2px 0 2px;\n  position: relative;\n}\n.mce-preview-object .mce-shim {\n  background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);\n  height: 100%;\n  left: 0;\n  position: absolute;\n  top: 0;\n  width: 100%;\n}\n.mce-preview-object[data-mce-selected=\"2\"] .mce-shim {\n  display: none;\n}\n.mce-content-body .mce-mergetag {\n  cursor: default !important;\n  -webkit-user-select: none;\n     -moz-user-select: none;\n          user-select: none;\n}\n.mce-content-body .mce-mergetag:hover {\n  background-color: rgba(0, 108, 231, 0.3);\n}\n.mce-content-body .mce-mergetag-affix {\n  background-color: rgba(0, 108, 231, 0.3);\n  color: #006ce7;\n}\n.mce-object {\n  background: transparent url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%20fill%3D%22%23cccccc%22%2F%3E%3C%2Fsvg%3E%0A\") no-repeat center;\n  border: 1px dashed #aaa;\n}\n.mce-pagebreak {\n  border: 1px dashed #aaa;\n  cursor: default;\n  display: block;\n  height: 5px;\n  margin-top: 15px;\n  page-break-before: always;\n  width: 100%;\n}\n@media print {\n  .mce-pagebreak {\n    border: 0;\n  }\n}\n.tiny-pageembed .mce-shim {\n  background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);\n  height: 100%;\n  left: 0;\n  position: absolute;\n  top: 0;\n  width: 100%;\n}\n.tiny-pageembed[data-mce-selected=\"2\"] .mce-shim {\n  display: none;\n}\n.tiny-pageembed {\n  display: inline-block;\n  position: relative;\n}\n.tiny-pageembed--21by9,\n.tiny-pageembed--16by9,\n.tiny-pageembed--4by3,\n.tiny-pageembed--1by1 {\n  display: block;\n  overflow: hidden;\n  padding: 0;\n  position: relative;\n  width: 100%;\n}\n.tiny-pageembed--21by9 {\n  padding-top: 42.857143%;\n}\n.tiny-pageembed--16by9 {\n  padding-top: 56.25%;\n}\n.tiny-pageembed--4by3 {\n  padding-top: 75%;\n}\n.tiny-pageembed--1by1 {\n  padding-top: 100%;\n}\n.tiny-pageembed--21by9 iframe,\n.tiny-pageembed--16by9 iframe,\n.tiny-pageembed--4by3 iframe,\n.tiny-pageembed--1by1 iframe {\n  border: 0;\n  height: 100%;\n  left: 0;\n  position: absolute;\n  top: 0;\n  width: 100%;\n}\n.mce-content-body[data-mce-placeholder] {\n  position: relative;\n}\n.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before {\n  color: rgba(34, 47, 62, 0.7);\n  content: attr(data-mce-placeholder);\n  position: absolute;\n}\n.mce-content-body:not([dir=rtl])[data-mce-placeholder]:not(.mce-visualblocks)::before {\n  left: 1px;\n}\n.mce-content-body[dir=rtl][data-mce-placeholder]:not(.mce-visualblocks)::before {\n  right: 1px;\n}\n.mce-content-body div.mce-resizehandle {\n  background-color: #4099ff;\n  border-color: #4099ff;\n  border-style: solid;\n  border-width: 1px;\n  box-sizing: border-box;\n  height: 10px;\n  position: absolute;\n  width: 10px;\n  z-index: 1298;\n}\n.mce-content-body div.mce-resizehandle:hover {\n  background-color: #4099ff;\n}\n.mce-content-body div.mce-resizehandle:nth-of-type(1) {\n  cursor: nwse-resize;\n}\n.mce-content-body div.mce-resizehandle:nth-of-type(2) {\n  cursor: nesw-resize;\n}\n.mce-content-body div.mce-resizehandle:nth-of-type(3) {\n  cursor: nwse-resize;\n}\n.mce-content-body div.mce-resizehandle:nth-of-type(4) {\n  cursor: nesw-resize;\n}\n.mce-content-body .mce-resize-backdrop {\n  z-index: 10000;\n}\n.mce-content-body .mce-clonedresizable {\n  cursor: default;\n  opacity: 0.5;\n  outline: 1px dashed black;\n  position: absolute;\n  z-index: 10001;\n}\n.mce-content-body .mce-clonedresizable.mce-resizetable-columns th,\n.mce-content-body .mce-clonedresizable.mce-resizetable-columns td {\n  border: 0;\n}\n.mce-content-body .mce-resize-helper {\n  background: #555;\n  background: rgba(0, 0, 0, 0.75);\n  border: 1px;\n  border-radius: 3px;\n  color: white;\n  display: none;\n  font-family: sans-serif;\n  font-size: 12px;\n  line-height: 14px;\n  margin: 5px 10px;\n  padding: 5px;\n  position: absolute;\n  white-space: nowrap;\n  z-index: 10002;\n}\n.tox-rtc-user-selection {\n  position: relative;\n}\n.tox-rtc-user-cursor {\n  bottom: 0;\n  cursor: default;\n  position: absolute;\n  top: 0;\n  width: 2px;\n}\n.tox-rtc-user-cursor::before {\n  background-color: inherit;\n  border-radius: 50%;\n  content: '';\n  display: block;\n  height: 8px;\n  position: absolute;\n  right: -3px;\n  top: -3px;\n  width: 8px;\n}\n.tox-rtc-user-cursor:hover::after {\n  background-color: inherit;\n  border-radius: 100px;\n  box-sizing: border-box;\n  color: #fff;\n  content: attr(data-user);\n  display: block;\n  font-size: 12px;\n  font-weight: bold;\n  left: -5px;\n  min-height: 8px;\n  min-width: 8px;\n  padding: 0 12px;\n  position: absolute;\n  top: -11px;\n  white-space: nowrap;\n  z-index: 1000;\n}\n.tox-rtc-user-selection--1 .tox-rtc-user-cursor {\n  background-color: #2dc26b;\n}\n.tox-rtc-user-selection--2 .tox-rtc-user-cursor {\n  background-color: #e03e2d;\n}\n.tox-rtc-user-selection--3 .tox-rtc-user-cursor {\n  background-color: #f1c40f;\n}\n.tox-rtc-user-selection--4 .tox-rtc-user-cursor {\n  background-color: #3598db;\n}\n.tox-rtc-user-selection--5 .tox-rtc-user-cursor {\n  background-color: #b96ad9;\n}\n.tox-rtc-user-selection--6 .tox-rtc-user-cursor {\n  background-color: #e67e23;\n}\n.tox-rtc-user-selection--7 .tox-rtc-user-cursor {\n  background-color: #aaa69d;\n}\n.tox-rtc-user-selection--8 .tox-rtc-user-cursor {\n  background-color: #f368e0;\n}\n.tox-rtc-remote-image {\n  background: #eaeaea url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%2236%22%20height%3D%2212%22%20viewBox%3D%220%200%2036%2012%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%3Ccircle%20cx%3D%226%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2218%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.33s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2230%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.66s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%3C%2Fsvg%3E%0A\") no-repeat center center;\n  border: 1px solid #ccc;\n  min-height: 240px;\n  min-width: 320px;\n}\n.mce-match-marker {\n  background: #aaa;\n  color: #fff;\n}\n.mce-match-marker-selected {\n  background: #39f;\n  color: #fff;\n}\n.mce-match-marker-selected::-moz-selection {\n  background: #39f;\n  color: #fff;\n}\n.mce-match-marker-selected::selection {\n  background: #39f;\n  color: #fff;\n}\n.mce-content-body img[data-mce-selected],\n.mce-content-body video[data-mce-selected],\n.mce-content-body audio[data-mce-selected],\n.mce-content-body object[data-mce-selected],\n.mce-content-body embed[data-mce-selected],\n.mce-content-body table[data-mce-selected],\n.mce-content-body details[data-mce-selected] {\n  outline: 3px solid #4099ff;\n}\n.mce-content-body hr[data-mce-selected] {\n  outline: 3px solid #4099ff;\n  outline-offset: 1px;\n}\n.mce-content-body *[contentEditable=false] *[contentEditable=true]:focus {\n  outline: 3px solid #4099ff;\n}\n.mce-content-body *[contentEditable=false] *[contentEditable=true]:hover {\n  outline: 3px solid #4099ff;\n}\n.mce-content-body *[contentEditable=false][data-mce-selected] {\n  cursor: not-allowed;\n  outline: 3px solid #4099ff;\n}\n.mce-content-body.mce-content-readonly *[contentEditable=true]:focus,\n.mce-content-body.mce-content-readonly *[contentEditable=true]:hover {\n  outline: none;\n}\n.mce-content-body *[data-mce-selected=\"inline-boundary\"] {\n  background-color: #4099ff;\n}\n.mce-content-body .mce-edit-focus {\n  outline: 3px solid #4099ff;\n}\n.mce-content-body td[data-mce-selected],\n.mce-content-body th[data-mce-selected] {\n  position: relative;\n}\n.mce-content-body td[data-mce-selected]::-moz-selection,\n.mce-content-body th[data-mce-selected]::-moz-selection {\n  background: none;\n}\n.mce-content-body td[data-mce-selected]::selection,\n.mce-content-body th[data-mce-selected]::selection {\n  background: none;\n}\n.mce-content-body td[data-mce-selected] *,\n.mce-content-body th[data-mce-selected] * {\n  outline: none;\n  -webkit-touch-callout: none;\n  -webkit-user-select: none;\n     -moz-user-select: none;\n          user-select: none;\n}\n.mce-content-body td[data-mce-selected]::after,\n.mce-content-body th[data-mce-selected]::after {\n  background-color: rgba(180, 215, 255, 0.7);\n  border: 1px solid transparent;\n  bottom: -1px;\n  content: '';\n  left: -1px;\n  mix-blend-mode: lighten;\n  position: absolute;\n  right: -1px;\n  top: -1px;\n}\n@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {\n  .mce-content-body td[data-mce-selected]::after,\n  .mce-content-body th[data-mce-selected]::after {\n    border-color: rgba(0, 84, 180, 0.7);\n  }\n}\n.mce-content-body img[data-mce-selected]::-moz-selection {\n  background: none;\n}\n.mce-content-body img[data-mce-selected]::selection {\n  background: none;\n}\n.ephox-snooker-resizer-bar {\n  background-color: #4099ff;\n  opacity: 0;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  user-select: none;\n}\n.ephox-snooker-resizer-cols {\n  cursor: col-resize;\n}\n.ephox-snooker-resizer-rows {\n  cursor: row-resize;\n}\n.ephox-snooker-resizer-bar.ephox-snooker-resizer-bar-dragging {\n  opacity: 1;\n}\n.mce-spellchecker-word {\n  background-image: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%23ff0000'%20fill%3D'none'%20stroke-linecap%3D'round'%20stroke-opacity%3D'.75'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A\");\n  background-position: 0 calc(100% + 1px);\n  background-repeat: repeat-x;\n  background-size: auto 6px;\n  cursor: default;\n  height: 2rem;\n}\n.mce-spellchecker-grammar {\n  background-image: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%2300A835'%20fill%3D'none'%20stroke-linecap%3D'round'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A\");\n  background-position: 0 calc(100% + 1px);\n  background-repeat: repeat-x;\n  background-size: auto 6px;\n  cursor: default;\n}\n.mce-toc {\n  border: 1px solid gray;\n}\n.mce-toc h2 {\n  margin: 4px;\n}\n.mce-toc li {\n  list-style-type: none;\n}\n[data-mce-block] {\n  display: block;\n}\ntable[style*=\"border-width: 0px\"],\n.mce-item-table:not([border]),\n.mce-item-table[border=\"0\"],\ntable[style*=\"border-width: 0px\"] td,\n.mce-item-table:not([border]) td,\n.mce-item-table[border=\"0\"] td,\ntable[style*=\"border-width: 0px\"] th,\n.mce-item-table:not([border]) th,\n.mce-item-table[border=\"0\"] th,\ntable[style*=\"border-width: 0px\"] caption,\n.mce-item-table:not([border]) caption,\n.mce-item-table[border=\"0\"] caption {\n  border: 1px dashed #bbb;\n}\n.mce-visualblocks p,\n.mce-visualblocks h1,\n.mce-visualblocks h2,\n.mce-visualblocks h3,\n.mce-visualblocks h4,\n.mce-visualblocks h5,\n.mce-visualblocks h6,\n.mce-visualblocks div:not([data-mce-bogus]),\n.mce-visualblocks section,\n.mce-visualblocks article,\n.mce-visualblocks blockquote,\n.mce-visualblocks address,\n.mce-visualblocks pre,\n.mce-visualblocks figure,\n.mce-visualblocks figcaption,\n.mce-visualblocks hgroup,\n.mce-visualblocks aside,\n.mce-visualblocks ul,\n.mce-visualblocks ol,\n.mce-visualblocks dl {\n  background-repeat: no-repeat;\n  border: 1px dashed #bbb;\n  margin-left: 3px;\n  padding-top: 10px;\n}\n.mce-visualblocks p {\n  background-image: url(data:image/gif;base64,R0lGODlhCQAJAJEAAAAAAP///7u7u////yH5BAEAAAMALAAAAAAJAAkAAAIQnG+CqCN/mlyvsRUpThG6AgA7);\n}\n.mce-visualblocks h1 {\n  background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGu1JuxHoAfRNRW3TWXyF2YiRUAOw==);\n}\n.mce-visualblocks h2 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8Hybbx4oOuqgTynJd6bGlWg3DkJzoaUAAAOw==);\n}\n.mce-visualblocks h3 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIZjI8Hybbx4oOuqgTynJf2Ln2NOHpQpmhAAQA7);\n}\n.mce-visualblocks h4 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxInR0zqeAdhtJlXwV1oCll2HaWgAAOw==);\n}\n.mce-visualblocks h5 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjane4iq5GlW05GgIkIZUAAAOw==);\n}\n.mce-visualblocks h6 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjan04jep1iZ1XRlAo5bVgAAOw==);\n}\n.mce-visualblocks div:not([data-mce-bogus]) {\n  background-image: url(data:image/gif;base64,R0lGODlhEgAKAIABALu7u////yH5BAEAAAEALAAAAAASAAoAAAIfjI9poI0cgDywrhuxfbrzDEbQM2Ei5aRjmoySW4pAAQA7);\n}\n.mce-visualblocks section {\n  background-image: url(data:image/gif;base64,R0lGODlhKAAKAIABALu7u////yH5BAEAAAEALAAAAAAoAAoAAAI5jI+pywcNY3sBWHdNrplytD2ellDeSVbp+GmWqaDqDMepc8t17Y4vBsK5hDyJMcI6KkuYU+jpjLoKADs=);\n}\n.mce-visualblocks article {\n  background-image: url(data:image/gif;base64,R0lGODlhKgAKAIABALu7u////yH5BAEAAAEALAAAAAAqAAoAAAI6jI+pywkNY3wG0GBvrsd2tXGYSGnfiF7ikpXemTpOiJScasYoDJJrjsG9gkCJ0ag6KhmaIe3pjDYBBQA7);\n}\n.mce-visualblocks blockquote {\n  background-image: url(data:image/gif;base64,R0lGODlhPgAKAIABALu7u////yH5BAEAAAEALAAAAAA+AAoAAAJPjI+py+0Knpz0xQDyuUhvfoGgIX5iSKZYgq5uNL5q69asZ8s5rrf0yZmpNkJZzFesBTu8TOlDVAabUyatguVhWduud3EyiUk45xhTTgMBBQA7);\n}\n.mce-visualblocks address {\n  background-image: url(data:image/gif;base64,R0lGODlhLQAKAIABALu7u////yH5BAEAAAEALAAAAAAtAAoAAAI/jI+pywwNozSP1gDyyZcjb3UaRpXkWaXmZW4OqKLhBmLs+K263DkJK7OJeifh7FicKD9A1/IpGdKkyFpNmCkAADs=);\n}\n.mce-visualblocks pre {\n  background-image: url(data:image/gif;base64,R0lGODlhFQAKAIABALu7uwAAACH5BAEAAAEALAAAAAAVAAoAAAIjjI+ZoN0cgDwSmnpz1NCueYERhnibZVKLNnbOq8IvKpJtVQAAOw==);\n}\n.mce-visualblocks figure {\n  background-image: url(data:image/gif;base64,R0lGODlhJAAKAIAAALu7u////yH5BAEAAAEALAAAAAAkAAoAAAI0jI+py+2fwAHUSFvD3RlvG4HIp4nX5JFSpnZUJ6LlrM52OE7uSWosBHScgkSZj7dDKnWAAgA7);\n}\n.mce-visualblocks figcaption {\n  border: 1px dashed #bbb;\n}\n.mce-visualblocks hgroup {\n  background-image: url(data:image/gif;base64,R0lGODlhJwAKAIABALu7uwAAACH5BAEAAAEALAAAAAAnAAoAAAI3jI+pywYNI3uB0gpsRtt5fFnfNZaVSYJil4Wo03Hv6Z62uOCgiXH1kZIIJ8NiIxRrAZNMZAtQAAA7);\n}\n.mce-visualblocks aside {\n  background-image: url(data:image/gif;base64,R0lGODlhHgAKAIABAKqqqv///yH5BAEAAAEALAAAAAAeAAoAAAItjI+pG8APjZOTzgtqy7I3f1yehmQcFY4WKZbqByutmW4aHUd6vfcVbgudgpYCADs=);\n}\n.mce-visualblocks ul {\n  background-image: url(data:image/gif;base64,R0lGODlhDQAKAIAAALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGuYnqUVSjvw26DzzXiqIDlVwAAOw==);\n}\n.mce-visualblocks ol {\n  background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybH6HHt0qourxC6CvzXieHyeWQAAOw==);\n}\n.mce-visualblocks dl {\n  background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybEOnmOvUoWznTqeuEjNSCqeGRUAOw==);\n}\n.mce-visualblocks:not([dir=rtl]) p,\n.mce-visualblocks:not([dir=rtl]) h1,\n.mce-visualblocks:not([dir=rtl]) h2,\n.mce-visualblocks:not([dir=rtl]) h3,\n.mce-visualblocks:not([dir=rtl]) h4,\n.mce-visualblocks:not([dir=rtl]) h5,\n.mce-visualblocks:not([dir=rtl]) h6,\n.mce-visualblocks:not([dir=rtl]) div:not([data-mce-bogus]),\n.mce-visualblocks:not([dir=rtl]) section,\n.mce-visualblocks:not([dir=rtl]) article,\n.mce-visualblocks:not([dir=rtl]) blockquote,\n.mce-visualblocks:not([dir=rtl]) address,\n.mce-visualblocks:not([dir=rtl]) pre,\n.mce-visualblocks:not([dir=rtl]) figure,\n.mce-visualblocks:not([dir=rtl]) figcaption,\n.mce-visualblocks:not([dir=rtl]) hgroup,\n.mce-visualblocks:not([dir=rtl]) aside,\n.mce-visualblocks:not([dir=rtl]) ul,\n.mce-visualblocks:not([dir=rtl]) ol,\n.mce-visualblocks:not([dir=rtl]) dl {\n  margin-left: 3px;\n}\n.mce-visualblocks[dir=rtl] p,\n.mce-visualblocks[dir=rtl] h1,\n.mce-visualblocks[dir=rtl] h2,\n.mce-visualblocks[dir=rtl] h3,\n.mce-visualblocks[dir=rtl] h4,\n.mce-visualblocks[dir=rtl] h5,\n.mce-visualblocks[dir=rtl] h6,\n.mce-visualblocks[dir=rtl] div:not([data-mce-bogus]),\n.mce-visualblocks[dir=rtl] section,\n.mce-visualblocks[dir=rtl] article,\n.mce-visualblocks[dir=rtl] blockquote,\n.mce-visualblocks[dir=rtl] address,\n.mce-visualblocks[dir=rtl] pre,\n.mce-visualblocks[dir=rtl] figure,\n.mce-visualblocks[dir=rtl] figcaption,\n.mce-visualblocks[dir=rtl] hgroup,\n.mce-visualblocks[dir=rtl] aside,\n.mce-visualblocks[dir=rtl] ul,\n.mce-visualblocks[dir=rtl] ol,\n.mce-visualblocks[dir=rtl] dl {\n  background-position-x: right;\n  margin-right: 3px;\n}\n.mce-nbsp,\n.mce-shy {\n  background: #aaa;\n}\n.mce-shy::after {\n  content: '-';\n}\nbody {\n  font-family: sans-serif;\n}\ntable {\n  border-collapse: collapse;\n}\n"
  },
  {
    "path": "minimalist-vue3/public/tinymce/skins/ui/oxide-dark/content.inline.css",
    "content": ".mce-content-body .mce-item-anchor {\n  background: transparent url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'8'%20height%3D'12'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20d%3D'M0%200L8%200%208%2012%204.09117821%209%200%2012z'%2F%3E%3C%2Fsvg%3E%0A\") no-repeat center;\n}\n.mce-content-body .mce-item-anchor:empty {\n  cursor: default;\n  display: inline-block;\n  height: 12px !important;\n  padding: 0 2px;\n  -webkit-user-modify: read-only;\n  -moz-user-modify: read-only;\n  -webkit-user-select: all;\n  -moz-user-select: all;\n  user-select: all;\n  width: 8px !important;\n}\n.mce-content-body .mce-item-anchor:not(:empty) {\n  background-position-x: 2px;\n  display: inline-block;\n  padding-left: 12px;\n}\n.mce-content-body .mce-item-anchor[data-mce-selected] {\n  outline-offset: 1px;\n}\n.tox-comments-visible .tox-comment[contenteditable=\"false\"]:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment img:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment > audio:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment > video:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment span.mce-preview-object:not([data-mce-selected]) {\n  outline: 3px solid #ffe89d;\n}\n.tox-comments-visible .tox-comment[contenteditable=\"false\"][data-mce-annotation-active=\"true\"]:not([data-mce-selected]) {\n  outline: 3px solid #fed635;\n}\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"] img:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"] > audio:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"] > video:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"] span.mce-preview-object:not([data-mce-selected]) {\n  outline: 3px solid #fed635;\n}\n.tox-comments-visible span.tox-comment:not([data-mce-selected]) {\n  background-color: #ffe89d;\n  outline: none;\n}\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"]:not([data-mce-selected=\"inline-boundary\"]) {\n  background-color: #fed635;\n}\n.tox-checklist > li:not(.tox-checklist--hidden) {\n  list-style: none;\n  margin: 0.25em 0;\n}\n.tox-checklist > li:not(.tox-checklist--hidden)::before {\n  content: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-unchecked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2215%22%20height%3D%2215%22%20x%3D%22.5%22%20y%3D%22.5%22%20fill-rule%3D%22nonzero%22%20stroke%3D%22%234C4C4C%22%20rx%3D%222%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A\");\n  cursor: pointer;\n  height: 1em;\n  margin-left: -1.5em;\n  margin-top: 0.125em;\n  position: absolute;\n  width: 1em;\n}\n.tox-checklist li:not(.tox-checklist--hidden).tox-checklist--checked::before {\n  content: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-checked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22%234099FF%22%20fill-rule%3D%22nonzero%22%20rx%3D%222%22%2F%3E%3Cpath%20id%3D%22Path%22%20fill%3D%22%23FFF%22%20fill-rule%3D%22nonzero%22%20d%3D%22M11.5703186%2C3.14417309%20C11.8516238%2C2.73724603%2012.4164781%2C2.62829933%2012.83558%2C2.89774797%20C13.260121%2C3.17069355%2013.3759736%2C3.72932262%2013.0909105%2C4.14168582%20L7.7580587%2C11.8560195%20C7.43776896%2C12.3193404%206.76483983%2C12.3852142%206.35607322%2C11.9948725%20L3.02491697%2C8.8138662%20C2.66090143%2C8.46625845%202.65798871%2C7.89594698%203.01850234%2C7.54483354%20C3.373942%2C7.19866177%203.94940006%2C7.19592841%204.30829608%2C7.5386474%20L6.85276923%2C9.9684299%20L11.5703186%2C3.14417309%20Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A\");\n}\n[dir=rtl] .tox-checklist > li:not(.tox-checklist--hidden)::before {\n  margin-left: 0;\n  margin-right: -1.5em;\n}\n/* stylelint-disable */\n/* http://prismjs.com/ */\n/**\n * prism.js default theme for JavaScript, CSS and HTML\n * Based on dabblet (http://dabblet.com)\n * @author Lea Verou\n */\ncode[class*=\"language-\"],\npre[class*=\"language-\"] {\n  color: black;\n  background: none;\n  text-shadow: 0 1px white;\n  font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;\n  font-size: 1em;\n  text-align: left;\n  white-space: pre;\n  word-spacing: normal;\n  word-break: normal;\n  word-wrap: normal;\n  line-height: 1.5;\n  -moz-tab-size: 4;\n  tab-size: 4;\n  -webkit-hyphens: none;\n  hyphens: none;\n}\npre[class*=\"language-\"]::-moz-selection,\npre[class*=\"language-\"] ::-moz-selection,\ncode[class*=\"language-\"]::-moz-selection,\ncode[class*=\"language-\"] ::-moz-selection {\n  text-shadow: none;\n  background: #b3d4fc;\n}\npre[class*=\"language-\"]::selection,\npre[class*=\"language-\"] ::selection,\ncode[class*=\"language-\"]::selection,\ncode[class*=\"language-\"] ::selection {\n  text-shadow: none;\n  background: #b3d4fc;\n}\n@media print {\n  code[class*=\"language-\"],\n  pre[class*=\"language-\"] {\n    text-shadow: none;\n  }\n}\n/* Code blocks */\npre[class*=\"language-\"] {\n  padding: 1em;\n  margin: 0.5em 0;\n  overflow: auto;\n}\n:not(pre) > code[class*=\"language-\"],\npre[class*=\"language-\"] {\n  background: #f5f2f0;\n}\n/* Inline code */\n:not(pre) > code[class*=\"language-\"] {\n  padding: 0.1em;\n  border-radius: 0.3em;\n  white-space: normal;\n}\n.token.comment,\n.token.prolog,\n.token.doctype,\n.token.cdata {\n  color: slategray;\n}\n.token.punctuation {\n  color: #999;\n}\n.token.namespace {\n  opacity: 0.7;\n}\n.token.property,\n.token.tag,\n.token.boolean,\n.token.number,\n.token.constant,\n.token.symbol,\n.token.deleted {\n  color: #905;\n}\n.token.selector,\n.token.attr-name,\n.token.string,\n.token.char,\n.token.builtin,\n.token.inserted {\n  color: #690;\n}\n.token.operator,\n.token.entity,\n.token.url,\n.language-css .token.string,\n.style .token.string {\n  color: #9a6e3a;\n  /* This background color was intended by the author of this theme. */\n  background: hsla(0, 0%, 100%, 0.5);\n}\n.token.atrule,\n.token.attr-value,\n.token.keyword {\n  color: #07a;\n}\n.token.function,\n.token.class-name {\n  color: #DD4A68;\n}\n.token.regex,\n.token.important,\n.token.variable {\n  color: #e90;\n}\n.token.important,\n.token.bold {\n  font-weight: bold;\n}\n.token.italic {\n  font-style: italic;\n}\n.token.entity {\n  cursor: help;\n}\n/* stylelint-enable */\n.mce-content-body {\n  overflow-wrap: break-word;\n  word-wrap: break-word;\n}\n.mce-content-body .mce-visual-caret {\n  background-color: black;\n  background-color: currentColor;\n  position: absolute;\n}\n.mce-content-body .mce-visual-caret-hidden {\n  display: none;\n}\n.mce-content-body *[data-mce-caret] {\n  left: -1000px;\n  margin: 0;\n  padding: 0;\n  position: absolute;\n  right: auto;\n  top: 0;\n}\n.mce-content-body .mce-offscreen-selection {\n  left: -2000000px;\n  max-width: 1000000px;\n  position: absolute;\n}\n.mce-content-body *[contentEditable=false] {\n  cursor: default;\n}\n.mce-content-body *[contentEditable=true] {\n  cursor: text;\n}\n.tox-cursor-format-painter {\n  cursor: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A\"), default;\n}\ndiv.mce-footnotes hr {\n  margin-inline-end: auto;\n  margin-inline-start: 0;\n  width: 25%;\n}\ndiv.mce-footnotes li > a.mce-footnotes-backlink {\n  text-decoration: none;\n}\n@media print {\n  sup.mce-footnote a {\n    color: black;\n    text-decoration: none;\n  }\n  div.mce-footnotes {\n    break-inside: avoid;\n    width: 100%;\n  }\n  div.mce-footnotes li > a.mce-footnotes-backlink {\n    display: none;\n  }\n}\n.mce-content-body figure.align-left {\n  float: left;\n}\n.mce-content-body figure.align-right {\n  float: right;\n}\n.mce-content-body figure.image.align-center {\n  display: table;\n  margin-left: auto;\n  margin-right: auto;\n}\n.mce-preview-object {\n  border: 1px solid gray;\n  display: inline-block;\n  line-height: 0;\n  margin: 0 2px 0 2px;\n  position: relative;\n}\n.mce-preview-object .mce-shim {\n  background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);\n  height: 100%;\n  left: 0;\n  position: absolute;\n  top: 0;\n  width: 100%;\n}\n.mce-preview-object[data-mce-selected=\"2\"] .mce-shim {\n  display: none;\n}\n.mce-content-body .mce-mergetag {\n  cursor: default !important;\n  -webkit-user-select: none;\n     -moz-user-select: none;\n          user-select: none;\n}\n.mce-content-body .mce-mergetag:hover {\n  background-color: rgba(0, 108, 231, 0.1);\n}\n.mce-content-body .mce-mergetag-affix {\n  background-color: rgba(0, 108, 231, 0.1);\n  color: #006ce7;\n}\n.mce-object {\n  background: transparent url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%2F%3E%3C%2Fsvg%3E%0A\") no-repeat center;\n  border: 1px dashed #aaa;\n}\n.mce-pagebreak {\n  border: 1px dashed #aaa;\n  cursor: default;\n  display: block;\n  height: 5px;\n  margin-top: 15px;\n  page-break-before: always;\n  width: 100%;\n}\n@media print {\n  .mce-pagebreak {\n    border: 0;\n  }\n}\n.tiny-pageembed .mce-shim {\n  background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);\n  height: 100%;\n  left: 0;\n  position: absolute;\n  top: 0;\n  width: 100%;\n}\n.tiny-pageembed[data-mce-selected=\"2\"] .mce-shim {\n  display: none;\n}\n.tiny-pageembed {\n  display: inline-block;\n  position: relative;\n}\n.tiny-pageembed--21by9,\n.tiny-pageembed--16by9,\n.tiny-pageembed--4by3,\n.tiny-pageembed--1by1 {\n  display: block;\n  overflow: hidden;\n  padding: 0;\n  position: relative;\n  width: 100%;\n}\n.tiny-pageembed--21by9 {\n  padding-top: 42.857143%;\n}\n.tiny-pageembed--16by9 {\n  padding-top: 56.25%;\n}\n.tiny-pageembed--4by3 {\n  padding-top: 75%;\n}\n.tiny-pageembed--1by1 {\n  padding-top: 100%;\n}\n.tiny-pageembed--21by9 iframe,\n.tiny-pageembed--16by9 iframe,\n.tiny-pageembed--4by3 iframe,\n.tiny-pageembed--1by1 iframe {\n  border: 0;\n  height: 100%;\n  left: 0;\n  position: absolute;\n  top: 0;\n  width: 100%;\n}\n.mce-content-body[data-mce-placeholder] {\n  position: relative;\n}\n.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before {\n  color: rgba(34, 47, 62, 0.7);\n  content: attr(data-mce-placeholder);\n  position: absolute;\n}\n.mce-content-body:not([dir=rtl])[data-mce-placeholder]:not(.mce-visualblocks)::before {\n  left: 1px;\n}\n.mce-content-body[dir=rtl][data-mce-placeholder]:not(.mce-visualblocks)::before {\n  right: 1px;\n}\n.mce-content-body div.mce-resizehandle {\n  background-color: #4099ff;\n  border-color: #4099ff;\n  border-style: solid;\n  border-width: 1px;\n  box-sizing: border-box;\n  height: 10px;\n  position: absolute;\n  width: 10px;\n  z-index: 1298;\n}\n.mce-content-body div.mce-resizehandle:hover {\n  background-color: #4099ff;\n}\n.mce-content-body div.mce-resizehandle:nth-of-type(1) {\n  cursor: nwse-resize;\n}\n.mce-content-body div.mce-resizehandle:nth-of-type(2) {\n  cursor: nesw-resize;\n}\n.mce-content-body div.mce-resizehandle:nth-of-type(3) {\n  cursor: nwse-resize;\n}\n.mce-content-body div.mce-resizehandle:nth-of-type(4) {\n  cursor: nesw-resize;\n}\n.mce-content-body .mce-resize-backdrop {\n  z-index: 10000;\n}\n.mce-content-body .mce-clonedresizable {\n  cursor: default;\n  opacity: 0.5;\n  outline: 1px dashed black;\n  position: absolute;\n  z-index: 10001;\n}\n.mce-content-body .mce-clonedresizable.mce-resizetable-columns th,\n.mce-content-body .mce-clonedresizable.mce-resizetable-columns td {\n  border: 0;\n}\n.mce-content-body .mce-resize-helper {\n  background: #555;\n  background: rgba(0, 0, 0, 0.75);\n  border: 1px;\n  border-radius: 3px;\n  color: white;\n  display: none;\n  font-family: sans-serif;\n  font-size: 12px;\n  line-height: 14px;\n  margin: 5px 10px;\n  padding: 5px;\n  position: absolute;\n  white-space: nowrap;\n  z-index: 10002;\n}\n.tox-rtc-user-selection {\n  position: relative;\n}\n.tox-rtc-user-cursor {\n  bottom: 0;\n  cursor: default;\n  position: absolute;\n  top: 0;\n  width: 2px;\n}\n.tox-rtc-user-cursor::before {\n  background-color: inherit;\n  border-radius: 50%;\n  content: '';\n  display: block;\n  height: 8px;\n  position: absolute;\n  right: -3px;\n  top: -3px;\n  width: 8px;\n}\n.tox-rtc-user-cursor:hover::after {\n  background-color: inherit;\n  border-radius: 100px;\n  box-sizing: border-box;\n  color: #fff;\n  content: attr(data-user);\n  display: block;\n  font-size: 12px;\n  font-weight: bold;\n  left: -5px;\n  min-height: 8px;\n  min-width: 8px;\n  padding: 0 12px;\n  position: absolute;\n  top: -11px;\n  white-space: nowrap;\n  z-index: 1000;\n}\n.tox-rtc-user-selection--1 .tox-rtc-user-cursor {\n  background-color: #2dc26b;\n}\n.tox-rtc-user-selection--2 .tox-rtc-user-cursor {\n  background-color: #e03e2d;\n}\n.tox-rtc-user-selection--3 .tox-rtc-user-cursor {\n  background-color: #f1c40f;\n}\n.tox-rtc-user-selection--4 .tox-rtc-user-cursor {\n  background-color: #3598db;\n}\n.tox-rtc-user-selection--5 .tox-rtc-user-cursor {\n  background-color: #b96ad9;\n}\n.tox-rtc-user-selection--6 .tox-rtc-user-cursor {\n  background-color: #e67e23;\n}\n.tox-rtc-user-selection--7 .tox-rtc-user-cursor {\n  background-color: #aaa69d;\n}\n.tox-rtc-user-selection--8 .tox-rtc-user-cursor {\n  background-color: #f368e0;\n}\n.tox-rtc-remote-image {\n  background: #eaeaea url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%2236%22%20height%3D%2212%22%20viewBox%3D%220%200%2036%2012%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%3Ccircle%20cx%3D%226%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2218%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.33s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2230%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.66s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%3C%2Fsvg%3E%0A\") no-repeat center center;\n  border: 1px solid #ccc;\n  min-height: 240px;\n  min-width: 320px;\n}\n.mce-match-marker {\n  background: #aaa;\n  color: #fff;\n}\n.mce-match-marker-selected {\n  background: #39f;\n  color: #fff;\n}\n.mce-match-marker-selected::-moz-selection {\n  background: #39f;\n  color: #fff;\n}\n.mce-match-marker-selected::selection {\n  background: #39f;\n  color: #fff;\n}\n.mce-content-body img[data-mce-selected],\n.mce-content-body video[data-mce-selected],\n.mce-content-body audio[data-mce-selected],\n.mce-content-body object[data-mce-selected],\n.mce-content-body embed[data-mce-selected],\n.mce-content-body table[data-mce-selected],\n.mce-content-body details[data-mce-selected] {\n  outline: 3px solid #b4d7ff;\n}\n.mce-content-body hr[data-mce-selected] {\n  outline: 3px solid #b4d7ff;\n  outline-offset: 1px;\n}\n.mce-content-body *[contentEditable=false] *[contentEditable=true]:focus {\n  outline: 3px solid #b4d7ff;\n}\n.mce-content-body *[contentEditable=false] *[contentEditable=true]:hover {\n  outline: 3px solid #b4d7ff;\n}\n.mce-content-body *[contentEditable=false][data-mce-selected] {\n  cursor: not-allowed;\n  outline: 3px solid #b4d7ff;\n}\n.mce-content-body.mce-content-readonly *[contentEditable=true]:focus,\n.mce-content-body.mce-content-readonly *[contentEditable=true]:hover {\n  outline: none;\n}\n.mce-content-body *[data-mce-selected=\"inline-boundary\"] {\n  background-color: #b4d7ff;\n}\n.mce-content-body .mce-edit-focus {\n  outline: 3px solid #b4d7ff;\n}\n.mce-content-body td[data-mce-selected],\n.mce-content-body th[data-mce-selected] {\n  position: relative;\n}\n.mce-content-body td[data-mce-selected]::-moz-selection,\n.mce-content-body th[data-mce-selected]::-moz-selection {\n  background: none;\n}\n.mce-content-body td[data-mce-selected]::selection,\n.mce-content-body th[data-mce-selected]::selection {\n  background: none;\n}\n.mce-content-body td[data-mce-selected] *,\n.mce-content-body th[data-mce-selected] * {\n  outline: none;\n  -webkit-touch-callout: none;\n  -webkit-user-select: none;\n     -moz-user-select: none;\n          user-select: none;\n}\n.mce-content-body td[data-mce-selected]::after,\n.mce-content-body th[data-mce-selected]::after {\n  background-color: rgba(180, 215, 255, 0.7);\n  border: 1px solid rgba(180, 215, 255, 0.7);\n  bottom: -1px;\n  content: '';\n  left: -1px;\n  mix-blend-mode: multiply;\n  position: absolute;\n  right: -1px;\n  top: -1px;\n}\n@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {\n  .mce-content-body td[data-mce-selected]::after,\n  .mce-content-body th[data-mce-selected]::after {\n    border-color: rgba(0, 84, 180, 0.7);\n  }\n}\n.mce-content-body img[data-mce-selected]::-moz-selection {\n  background: none;\n}\n.mce-content-body img[data-mce-selected]::selection {\n  background: none;\n}\n.ephox-snooker-resizer-bar {\n  background-color: #b4d7ff;\n  opacity: 0;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  user-select: none;\n}\n.ephox-snooker-resizer-cols {\n  cursor: col-resize;\n}\n.ephox-snooker-resizer-rows {\n  cursor: row-resize;\n}\n.ephox-snooker-resizer-bar.ephox-snooker-resizer-bar-dragging {\n  opacity: 1;\n}\n.mce-spellchecker-word {\n  background-image: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%23ff0000'%20fill%3D'none'%20stroke-linecap%3D'round'%20stroke-opacity%3D'.75'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A\");\n  background-position: 0 calc(100% + 1px);\n  background-repeat: repeat-x;\n  background-size: auto 6px;\n  cursor: default;\n  height: 2rem;\n}\n.mce-spellchecker-grammar {\n  background-image: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%2300A835'%20fill%3D'none'%20stroke-linecap%3D'round'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A\");\n  background-position: 0 calc(100% + 1px);\n  background-repeat: repeat-x;\n  background-size: auto 6px;\n  cursor: default;\n}\n.mce-toc {\n  border: 1px solid gray;\n}\n.mce-toc h2 {\n  margin: 4px;\n}\n.mce-toc li {\n  list-style-type: none;\n}\n[data-mce-block] {\n  display: block;\n}\ntable[style*=\"border-width: 0px\"],\n.mce-item-table:not([border]),\n.mce-item-table[border=\"0\"],\ntable[style*=\"border-width: 0px\"] td,\n.mce-item-table:not([border]) td,\n.mce-item-table[border=\"0\"] td,\ntable[style*=\"border-width: 0px\"] th,\n.mce-item-table:not([border]) th,\n.mce-item-table[border=\"0\"] th,\ntable[style*=\"border-width: 0px\"] caption,\n.mce-item-table:not([border]) caption,\n.mce-item-table[border=\"0\"] caption {\n  border: 1px dashed #bbb;\n}\n.mce-visualblocks p,\n.mce-visualblocks h1,\n.mce-visualblocks h2,\n.mce-visualblocks h3,\n.mce-visualblocks h4,\n.mce-visualblocks h5,\n.mce-visualblocks h6,\n.mce-visualblocks div:not([data-mce-bogus]),\n.mce-visualblocks section,\n.mce-visualblocks article,\n.mce-visualblocks blockquote,\n.mce-visualblocks address,\n.mce-visualblocks pre,\n.mce-visualblocks figure,\n.mce-visualblocks figcaption,\n.mce-visualblocks hgroup,\n.mce-visualblocks aside,\n.mce-visualblocks ul,\n.mce-visualblocks ol,\n.mce-visualblocks dl {\n  background-repeat: no-repeat;\n  border: 1px dashed #bbb;\n  margin-left: 3px;\n  padding-top: 10px;\n}\n.mce-visualblocks p {\n  background-image: url(data:image/gif;base64,R0lGODlhCQAJAJEAAAAAAP///7u7u////yH5BAEAAAMALAAAAAAJAAkAAAIQnG+CqCN/mlyvsRUpThG6AgA7);\n}\n.mce-visualblocks h1 {\n  background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGu1JuxHoAfRNRW3TWXyF2YiRUAOw==);\n}\n.mce-visualblocks h2 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8Hybbx4oOuqgTynJd6bGlWg3DkJzoaUAAAOw==);\n}\n.mce-visualblocks h3 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIZjI8Hybbx4oOuqgTynJf2Ln2NOHpQpmhAAQA7);\n}\n.mce-visualblocks h4 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxInR0zqeAdhtJlXwV1oCll2HaWgAAOw==);\n}\n.mce-visualblocks h5 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjane4iq5GlW05GgIkIZUAAAOw==);\n}\n.mce-visualblocks h6 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjan04jep1iZ1XRlAo5bVgAAOw==);\n}\n.mce-visualblocks div:not([data-mce-bogus]) {\n  background-image: url(data:image/gif;base64,R0lGODlhEgAKAIABALu7u////yH5BAEAAAEALAAAAAASAAoAAAIfjI9poI0cgDywrhuxfbrzDEbQM2Ei5aRjmoySW4pAAQA7);\n}\n.mce-visualblocks section {\n  background-image: url(data:image/gif;base64,R0lGODlhKAAKAIABALu7u////yH5BAEAAAEALAAAAAAoAAoAAAI5jI+pywcNY3sBWHdNrplytD2ellDeSVbp+GmWqaDqDMepc8t17Y4vBsK5hDyJMcI6KkuYU+jpjLoKADs=);\n}\n.mce-visualblocks article {\n  background-image: url(data:image/gif;base64,R0lGODlhKgAKAIABALu7u////yH5BAEAAAEALAAAAAAqAAoAAAI6jI+pywkNY3wG0GBvrsd2tXGYSGnfiF7ikpXemTpOiJScasYoDJJrjsG9gkCJ0ag6KhmaIe3pjDYBBQA7);\n}\n.mce-visualblocks blockquote {\n  background-image: url(data:image/gif;base64,R0lGODlhPgAKAIABALu7u////yH5BAEAAAEALAAAAAA+AAoAAAJPjI+py+0Knpz0xQDyuUhvfoGgIX5iSKZYgq5uNL5q69asZ8s5rrf0yZmpNkJZzFesBTu8TOlDVAabUyatguVhWduud3EyiUk45xhTTgMBBQA7);\n}\n.mce-visualblocks address {\n  background-image: url(data:image/gif;base64,R0lGODlhLQAKAIABALu7u////yH5BAEAAAEALAAAAAAtAAoAAAI/jI+pywwNozSP1gDyyZcjb3UaRpXkWaXmZW4OqKLhBmLs+K263DkJK7OJeifh7FicKD9A1/IpGdKkyFpNmCkAADs=);\n}\n.mce-visualblocks pre {\n  background-image: url(data:image/gif;base64,R0lGODlhFQAKAIABALu7uwAAACH5BAEAAAEALAAAAAAVAAoAAAIjjI+ZoN0cgDwSmnpz1NCueYERhnibZVKLNnbOq8IvKpJtVQAAOw==);\n}\n.mce-visualblocks figure {\n  background-image: url(data:image/gif;base64,R0lGODlhJAAKAIAAALu7u////yH5BAEAAAEALAAAAAAkAAoAAAI0jI+py+2fwAHUSFvD3RlvG4HIp4nX5JFSpnZUJ6LlrM52OE7uSWosBHScgkSZj7dDKnWAAgA7);\n}\n.mce-visualblocks figcaption {\n  border: 1px dashed #bbb;\n}\n.mce-visualblocks hgroup {\n  background-image: url(data:image/gif;base64,R0lGODlhJwAKAIABALu7uwAAACH5BAEAAAEALAAAAAAnAAoAAAI3jI+pywYNI3uB0gpsRtt5fFnfNZaVSYJil4Wo03Hv6Z62uOCgiXH1kZIIJ8NiIxRrAZNMZAtQAAA7);\n}\n.mce-visualblocks aside {\n  background-image: url(data:image/gif;base64,R0lGODlhHgAKAIABAKqqqv///yH5BAEAAAEALAAAAAAeAAoAAAItjI+pG8APjZOTzgtqy7I3f1yehmQcFY4WKZbqByutmW4aHUd6vfcVbgudgpYCADs=);\n}\n.mce-visualblocks ul {\n  background-image: url(data:image/gif;base64,R0lGODlhDQAKAIAAALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGuYnqUVSjvw26DzzXiqIDlVwAAOw==);\n}\n.mce-visualblocks ol {\n  background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybH6HHt0qourxC6CvzXieHyeWQAAOw==);\n}\n.mce-visualblocks dl {\n  background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybEOnmOvUoWznTqeuEjNSCqeGRUAOw==);\n}\n.mce-visualblocks:not([dir=rtl]) p,\n.mce-visualblocks:not([dir=rtl]) h1,\n.mce-visualblocks:not([dir=rtl]) h2,\n.mce-visualblocks:not([dir=rtl]) h3,\n.mce-visualblocks:not([dir=rtl]) h4,\n.mce-visualblocks:not([dir=rtl]) h5,\n.mce-visualblocks:not([dir=rtl]) h6,\n.mce-visualblocks:not([dir=rtl]) div:not([data-mce-bogus]),\n.mce-visualblocks:not([dir=rtl]) section,\n.mce-visualblocks:not([dir=rtl]) article,\n.mce-visualblocks:not([dir=rtl]) blockquote,\n.mce-visualblocks:not([dir=rtl]) address,\n.mce-visualblocks:not([dir=rtl]) pre,\n.mce-visualblocks:not([dir=rtl]) figure,\n.mce-visualblocks:not([dir=rtl]) figcaption,\n.mce-visualblocks:not([dir=rtl]) hgroup,\n.mce-visualblocks:not([dir=rtl]) aside,\n.mce-visualblocks:not([dir=rtl]) ul,\n.mce-visualblocks:not([dir=rtl]) ol,\n.mce-visualblocks:not([dir=rtl]) dl {\n  margin-left: 3px;\n}\n.mce-visualblocks[dir=rtl] p,\n.mce-visualblocks[dir=rtl] h1,\n.mce-visualblocks[dir=rtl] h2,\n.mce-visualblocks[dir=rtl] h3,\n.mce-visualblocks[dir=rtl] h4,\n.mce-visualblocks[dir=rtl] h5,\n.mce-visualblocks[dir=rtl] h6,\n.mce-visualblocks[dir=rtl] div:not([data-mce-bogus]),\n.mce-visualblocks[dir=rtl] section,\n.mce-visualblocks[dir=rtl] article,\n.mce-visualblocks[dir=rtl] blockquote,\n.mce-visualblocks[dir=rtl] address,\n.mce-visualblocks[dir=rtl] pre,\n.mce-visualblocks[dir=rtl] figure,\n.mce-visualblocks[dir=rtl] figcaption,\n.mce-visualblocks[dir=rtl] hgroup,\n.mce-visualblocks[dir=rtl] aside,\n.mce-visualblocks[dir=rtl] ul,\n.mce-visualblocks[dir=rtl] ol,\n.mce-visualblocks[dir=rtl] dl {\n  background-position-x: right;\n  margin-right: 3px;\n}\n.mce-nbsp,\n.mce-shy {\n  background: #aaa;\n}\n.mce-shy::after {\n  content: '-';\n}\n"
  },
  {
    "path": "minimalist-vue3/public/tinymce/skins/ui/oxide-dark/skin.css",
    "content": ".tox {\n  box-shadow: none;\n  box-sizing: content-box;\n  color: #222f3e;\n  cursor: auto;\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n  font-size: 16px;\n  font-style: normal;\n  font-weight: normal;\n  line-height: normal;\n  -webkit-tap-highlight-color: transparent;\n  text-decoration: none;\n  text-shadow: none;\n  text-transform: none;\n  vertical-align: initial;\n  white-space: normal;\n}\n.tox *:not(svg):not(rect) {\n  box-sizing: inherit;\n  color: inherit;\n  cursor: inherit;\n  direction: inherit;\n  font-family: inherit;\n  font-size: inherit;\n  font-style: inherit;\n  font-weight: inherit;\n  line-height: inherit;\n  -webkit-tap-highlight-color: inherit;\n  text-align: inherit;\n  text-decoration: inherit;\n  text-shadow: inherit;\n  text-transform: inherit;\n  vertical-align: inherit;\n  white-space: inherit;\n}\n.tox *:not(svg):not(rect) {\n  /* stylelint-disable-line no-duplicate-selectors */\n  background: transparent;\n  border: 0;\n  box-shadow: none;\n  float: none;\n  height: auto;\n  margin: 0;\n  max-width: none;\n  outline: 0;\n  padding: 0;\n  position: static;\n  width: auto;\n}\n.tox:not([dir=rtl]) {\n  direction: ltr;\n  text-align: left;\n}\n.tox[dir=rtl] {\n  direction: rtl;\n  text-align: right;\n}\n.tox-tinymce {\n  border: 2px solid #161f29;\n  border-radius: 10px;\n  box-shadow: none;\n  box-sizing: border-box;\n  display: flex;\n  flex-direction: column;\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n  overflow: hidden;\n  position: relative;\n  visibility: inherit !important;\n}\n.tox.tox-tinymce-inline {\n  border: none;\n  box-shadow: none;\n  overflow: initial;\n}\n.tox.tox-tinymce-inline .tox-editor-container {\n  overflow: initial;\n}\n.tox.tox-tinymce-inline .tox-editor-header {\n  background-color: #222F3E;\n  border: 2px solid #161f29;\n  border-radius: 10px;\n  box-shadow: none;\n  overflow: hidden;\n}\n.tox-tinymce-aux {\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n  z-index: 1300;\n}\n.tox-tinymce *:focus,\n.tox-tinymce-aux *:focus {\n  outline: none;\n}\nbutton::-moz-focus-inner {\n  border: 0;\n}\n.tox[dir=rtl] .tox-icon--flip svg {\n  transform: rotateY(180deg);\n}\n.tox .accessibility-issue__header {\n  align-items: center;\n  display: flex;\n  margin-bottom: 4px;\n}\n.tox .accessibility-issue__description {\n  align-items: stretch;\n  border-radius: 6px;\n  display: flex;\n  justify-content: space-between;\n}\n.tox .accessibility-issue__description > div {\n  padding-bottom: 4px;\n}\n.tox .accessibility-issue__description > div > div {\n  align-items: center;\n  display: flex;\n  margin-bottom: 4px;\n}\n.tox .accessibility-issue__description > div > div .tox-icon svg {\n  display: block;\n}\n.tox .accessibility-issue__repair {\n  margin-top: 16px;\n}\n.tox .tox-dialog__body-content .accessibility-issue--info .accessibility-issue__description {\n  background-color: rgba(0, 101, 216, 0.4);\n  color: #fff;\n}\n.tox .tox-dialog__body-content .accessibility-issue--info .tox-form__group h2 {\n  color: #fff;\n}\n.tox .tox-dialog__body-content .accessibility-issue--info .tox-icon svg {\n  fill: #fff;\n}\n.tox .tox-dialog__body-content .accessibility-issue--info a.tox-button--naked.tox-button--icon {\n  background-color: #006ce7;\n  color: #fff;\n}\n.tox .tox-dialog__body-content .accessibility-issue--info a.tox-button--naked.tox-button--icon:hover,\n.tox .tox-dialog__body-content .accessibility-issue--info a.tox-button--naked.tox-button--icon:focus {\n  background-color: #0060ce;\n}\n.tox .tox-dialog__body-content .accessibility-issue--info a.tox-button--naked.tox-button--icon:active {\n  background-color: #0054b4;\n}\n.tox .tox-dialog__body-content .accessibility-issue--warn {\n  /* stylelint-disable-next-line no-descending-specificity */\n}\n.tox .tox-dialog__body-content .accessibility-issue--warn .accessibility-issue__description {\n  background-color: rgba(255, 165, 0, 0.5);\n  color: #fff;\n}\n.tox .tox-dialog__body-content .accessibility-issue--warn .tox-form__group h2 {\n  color: #fff;\n}\n.tox .tox-dialog__body-content .accessibility-issue--warn .tox-icon svg {\n  fill: #fff;\n}\n.tox .tox-dialog__body-content .accessibility-issue--warn a.tox-button--naked.tox-button--icon {\n  background-color: #FFE89D;\n  color: #222f3e;\n}\n.tox .tox-dialog__body-content .accessibility-issue--warn a.tox-button--naked.tox-button--icon:hover,\n.tox .tox-dialog__body-content .accessibility-issue--warn a.tox-button--naked.tox-button--icon:focus {\n  background-color: #F2D574;\n  color: #222f3e;\n}\n.tox .tox-dialog__body-content .accessibility-issue--warn a.tox-button--naked.tox-button--icon:active {\n  background-color: #E8C657;\n  color: #222f3e;\n}\n.tox .tox-dialog__body-content .accessibility-issue--error {\n  /* stylelint-disable-next-line no-descending-specificity */\n}\n.tox .tox-dialog__body-content .accessibility-issue--error .accessibility-issue__description {\n  background-color: rgba(204, 0, 0, 0.5);\n  color: #fff;\n}\n.tox .tox-dialog__body-content .accessibility-issue--error .tox-form__group h2 {\n  color: #fff;\n}\n.tox .tox-dialog__body-content .accessibility-issue--error .tox-icon svg {\n  fill: #fff;\n}\n.tox .tox-dialog__body-content .accessibility-issue--error a.tox-button--naked.tox-button--icon {\n  background-color: #F2BFBF;\n  color: #222f3e;\n}\n.tox .tox-dialog__body-content .accessibility-issue--error a.tox-button--naked.tox-button--icon:hover,\n.tox .tox-dialog__body-content .accessibility-issue--error a.tox-button--naked.tox-button--icon:focus {\n  background-color: #E9A4A4;\n  color: #222f3e;\n}\n.tox .tox-dialog__body-content .accessibility-issue--error a.tox-button--naked.tox-button--icon:active {\n  background-color: #EE9494;\n  color: #222f3e;\n}\n.tox .tox-dialog__body-content .accessibility-issue--success .accessibility-issue__description {\n  background-color: rgba(120, 171, 70, 0.5);\n  color: #fff;\n}\n.tox .tox-dialog__body-content .accessibility-issue--success .accessibility-issue__description > *:last-child {\n  display: none;\n}\n.tox .tox-dialog__body-content .accessibility-issue--success .tox-form__group h2 {\n  color: #fff;\n}\n.tox .tox-dialog__body-content .accessibility-issue--success .tox-icon svg {\n  fill: #fff;\n}\n.tox .tox-dialog__body-content .accessibility-issue__header .tox-form__group h1,\n.tox .tox-dialog__body-content .tox-form__group .accessibility-issue__description h2 {\n  font-size: 14px;\n  margin-top: 0;\n}\n.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__header .tox-button {\n  margin-left: 4px;\n}\n.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__header > *:nth-last-child(2) {\n  margin-left: auto;\n}\n.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__description {\n  padding: 4px 4px 4px 8px;\n}\n.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__header .tox-button {\n  margin-right: 4px;\n}\n.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__header > *:nth-last-child(2) {\n  margin-right: auto;\n}\n.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__description {\n  padding: 4px 8px 4px 4px;\n}\n.tox .tox-advtemplate .tox-form__grid {\n  flex: 1;\n}\n.tox .tox-advtemplate .tox-form__grid > div:first-child {\n  display: flex;\n  flex-direction: column;\n  width: 30%;\n}\n.tox .tox-advtemplate .tox-form__grid > div:first-child > div:nth-child(2) {\n  flex-basis: 0;\n  flex-grow: 1;\n  overflow: auto;\n}\n@media only screen and (max-width: 767px ) {\n  body:not(.tox-force-desktop) .tox .tox-advtemplate .tox-form__grid > div:first-child {\n    width: 100%;\n  }\n}\n.tox .tox-advtemplate iframe {\n  border-color: #161f29;\n  border-radius: 10px;\n  border-style: solid;\n  border-width: 1px;\n  margin: 0 10px;\n}\n.tox .tox-anchorbar {\n  display: flex;\n  flex: 0 0 auto;\n}\n.tox .tox-bottom-anchorbar {\n  display: flex;\n  flex: 0 0 auto;\n}\n.tox .tox-bar {\n  display: flex;\n  flex: 0 0 auto;\n}\n.tox .tox-button {\n  background-color: #006ce7;\n  background-image: none;\n  background-position: 0 0;\n  background-repeat: repeat;\n  border-color: #006ce7;\n  border-radius: 6px;\n  border-style: solid;\n  border-width: 1px;\n  box-shadow: none;\n  box-sizing: border-box;\n  color: #fff;\n  cursor: pointer;\n  display: inline-block;\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: bold;\n  letter-spacing: normal;\n  line-height: 24px;\n  margin: 0;\n  outline: none;\n  padding: 4px 16px;\n  position: relative;\n  text-align: center;\n  text-decoration: none;\n  text-transform: none;\n  white-space: nowrap;\n}\n.tox .tox-button::before {\n  border-radius: 6px;\n  bottom: -1px;\n  box-shadow: inset 0 0 0 2px #fff, 0 0 0 1px #006ce7, 0 0 0 3px rgba(0, 108, 231, 0.25);\n  content: '';\n  left: -1px;\n  opacity: 0;\n  pointer-events: none;\n  position: absolute;\n  right: -1px;\n  top: -1px;\n}\n.tox .tox-button[disabled] {\n  background-color: #006ce7;\n  background-image: none;\n  border-color: #006ce7;\n  box-shadow: none;\n  color: rgba(255, 255, 255, 0.5);\n  cursor: not-allowed;\n}\n.tox .tox-button:focus:not(:disabled) {\n  background-color: #0060ce;\n  background-image: none;\n  border-color: #0060ce;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button:focus-visible:not(:disabled)::before {\n  opacity: 1;\n}\n.tox .tox-button:hover:not(:disabled) {\n  background-color: #0060ce;\n  background-image: none;\n  border-color: #0060ce;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button:active:not(:disabled) {\n  background-color: #0054b4;\n  background-image: none;\n  border-color: #0054b4;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button.tox-button--enabled {\n  background-color: #0054b4;\n  background-image: none;\n  border-color: #0054b4;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button.tox-button--enabled[disabled] {\n  background-color: #0054b4;\n  background-image: none;\n  border-color: #0054b4;\n  box-shadow: none;\n  color: rgba(255, 255, 255, 0.5);\n  cursor: not-allowed;\n}\n.tox .tox-button.tox-button--enabled:focus:not(:disabled) {\n  background-color: #00489b;\n  background-image: none;\n  border-color: #00489b;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button.tox-button--enabled:hover:not(:disabled) {\n  background-color: #00489b;\n  background-image: none;\n  border-color: #00489b;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button.tox-button--enabled:active:not(:disabled) {\n  background-color: #003c81;\n  background-image: none;\n  border-color: #003c81;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button--icon-and-text,\n.tox .tox-button.tox-button--icon-and-text,\n.tox .tox-button.tox-button--secondary.tox-button--icon-and-text {\n  display: flex;\n  padding: 5px 4px;\n}\n.tox .tox-button--icon-and-text .tox-icon svg,\n.tox .tox-button.tox-button--icon-and-text .tox-icon svg,\n.tox .tox-button.tox-button--secondary.tox-button--icon-and-text .tox-icon svg {\n  display: block;\n  fill: currentColor;\n}\n.tox .tox-button--secondary {\n  background-color: #3d546f;\n  background-image: none;\n  background-position: 0 0;\n  background-repeat: repeat;\n  border-color: #3d546f;\n  border-radius: 6px;\n  border-style: solid;\n  border-width: 1px;\n  box-shadow: none;\n  color: #fff;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: bold;\n  letter-spacing: normal;\n  outline: none;\n  padding: 4px 16px;\n  text-decoration: none;\n  text-transform: none;\n}\n.tox .tox-button--secondary[disabled] {\n  background-color: #3d546f;\n  background-image: none;\n  border-color: #3d546f;\n  box-shadow: none;\n  color: rgba(255, 255, 255, 0.5);\n}\n.tox .tox-button--secondary:focus:not(:disabled) {\n  background-color: #34485f;\n  background-image: none;\n  border-color: #34485f;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button--secondary:hover:not(:disabled) {\n  background-color: #34485f;\n  background-image: none;\n  border-color: #34485f;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button--secondary:active:not(:disabled) {\n  background-color: #2b3b4e;\n  background-image: none;\n  border-color: #2b3b4e;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button--secondary.tox-button--enabled {\n  background-color: #2b5c93;\n  background-image: none;\n  border-color: #2b5c93;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button--secondary.tox-button--enabled[disabled] {\n  background-color: #2b5c93;\n  background-image: none;\n  border-color: #2b5c93;\n  box-shadow: none;\n  color: rgba(255, 255, 255, 0.5);\n}\n.tox .tox-button--secondary.tox-button--enabled:focus:not(:disabled) {\n  background-color: #254f80;\n  background-image: none;\n  border-color: #254f80;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button--secondary.tox-button--enabled:hover:not(:disabled) {\n  background-color: #254f80;\n  background-image: none;\n  border-color: #254f80;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button--secondary.tox-button--enabled:active:not(:disabled) {\n  background-color: #1f436c;\n  background-image: none;\n  border-color: #1f436c;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button--icon,\n.tox .tox-button.tox-button--icon,\n.tox .tox-button.tox-button--secondary.tox-button--icon {\n  padding: 4px;\n}\n.tox .tox-button--icon .tox-icon svg,\n.tox .tox-button.tox-button--icon .tox-icon svg,\n.tox .tox-button.tox-button--secondary.tox-button--icon .tox-icon svg {\n  display: block;\n  fill: currentColor;\n}\n.tox .tox-button-link {\n  background: 0;\n  border: none;\n  box-sizing: border-box;\n  cursor: pointer;\n  display: inline-block;\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n  font-size: 16px;\n  font-weight: normal;\n  line-height: 1.3;\n  margin: 0;\n  padding: 0;\n  white-space: nowrap;\n}\n.tox .tox-button-link--sm {\n  font-size: 14px;\n}\n.tox .tox-button--naked {\n  background-color: transparent;\n  border-color: transparent;\n  box-shadow: unset;\n  color: #fff;\n}\n.tox .tox-button--naked[disabled] {\n  background-color: rgba(255, 255, 255, 0.2);\n  border-color: transparent;\n  box-shadow: unset;\n  color: rgba(255, 255, 255, 0.5);\n}\n.tox .tox-button--naked:hover:not(:disabled) {\n  background-color: rgba(255, 255, 255, 0.2);\n  border-color: transparent;\n  box-shadow: unset;\n  color: #fff;\n}\n.tox .tox-button--naked:focus:not(:disabled) {\n  background-color: rgba(255, 255, 255, 0.2);\n  border-color: transparent;\n  box-shadow: unset;\n  color: #fff;\n}\n.tox .tox-button--naked:active:not(:disabled) {\n  background-color: rgba(255, 255, 255, 0.3);\n  border-color: transparent;\n  box-shadow: unset;\n  color: #fff;\n}\n.tox .tox-button--naked .tox-icon svg {\n  fill: currentColor;\n}\n.tox .tox-button--naked.tox-button--icon:hover:not(:disabled) {\n  color: #fff;\n}\n.tox .tox-checkbox {\n  align-items: center;\n  border-radius: 6px;\n  cursor: pointer;\n  display: flex;\n  height: 36px;\n  min-width: 36px;\n}\n.tox .tox-checkbox__input {\n  /* Hide from view but visible to screen readers */\n  height: 1px;\n  overflow: hidden;\n  position: absolute;\n  top: auto;\n  width: 1px;\n}\n.tox .tox-checkbox__icons {\n  align-items: center;\n  border-radius: 6px;\n  box-shadow: 0 0 0 2px transparent;\n  box-sizing: content-box;\n  display: flex;\n  height: 24px;\n  justify-content: center;\n  padding: calc(4px - 1px);\n  width: 24px;\n}\n.tox .tox-checkbox__icons .tox-checkbox-icon__unchecked svg {\n  display: block;\n  fill: rgba(255, 255, 255, 0.2);\n}\n.tox .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg {\n  display: none;\n  fill: #006ce7;\n}\n.tox .tox-checkbox__icons .tox-checkbox-icon__checked svg {\n  display: none;\n  fill: #006ce7;\n}\n.tox .tox-checkbox--disabled {\n  color: rgba(255, 255, 255, 0.5);\n  cursor: not-allowed;\n}\n.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__checked svg {\n  fill: rgba(255, 255, 255, 0.5);\n}\n.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__unchecked svg {\n  fill: rgba(255, 255, 255, 0.5);\n}\n.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg {\n  fill: rgba(255, 255, 255, 0.5);\n}\n.tox input.tox-checkbox__input:checked + .tox-checkbox__icons .tox-checkbox-icon__unchecked svg {\n  display: none;\n}\n.tox input.tox-checkbox__input:checked + .tox-checkbox__icons .tox-checkbox-icon__checked svg {\n  display: block;\n}\n.tox input.tox-checkbox__input:indeterminate + .tox-checkbox__icons .tox-checkbox-icon__unchecked svg {\n  display: none;\n}\n.tox input.tox-checkbox__input:indeterminate + .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg {\n  display: block;\n}\n.tox input.tox-checkbox__input:focus + .tox-checkbox__icons {\n  border-radius: 6px;\n  box-shadow: inset 0 0 0 1px #006ce7;\n  padding: calc(4px - 1px);\n}\n.tox:not([dir=rtl]) .tox-checkbox__label {\n  margin-left: 4px;\n}\n.tox:not([dir=rtl]) .tox-checkbox__input {\n  left: -10000px;\n}\n.tox:not([dir=rtl]) .tox-bar .tox-checkbox {\n  margin-left: 4px;\n}\n.tox[dir=rtl] .tox-checkbox__label {\n  margin-right: 4px;\n}\n.tox[dir=rtl] .tox-checkbox__input {\n  right: -10000px;\n}\n.tox[dir=rtl] .tox-bar .tox-checkbox {\n  margin-right: 4px;\n}\n.tox {\n  /* stylelint-disable-next-line no-descending-specificity */\n}\n.tox .tox-collection--toolbar .tox-collection__group {\n  display: flex;\n  padding: 0;\n}\n.tox .tox-collection--grid .tox-collection__group {\n  display: flex;\n  flex-wrap: wrap;\n  max-height: 208px;\n  overflow-x: hidden;\n  overflow-y: auto;\n  padding: 0;\n}\n.tox .tox-collection--list .tox-collection__group {\n  border-bottom-width: 0;\n  border-color: rgba(255, 255, 255, 0.15);\n  border-left-width: 0;\n  border-right-width: 0;\n  border-style: solid;\n  border-top-width: 1px;\n  padding: 4px 0;\n}\n.tox .tox-collection--list .tox-collection__group:first-child {\n  border-top-width: 0;\n}\n.tox .tox-collection__group-heading {\n  background-color: rgba(255, 255, 255, 0.15);\n  color: rgba(255, 255, 255, 0.5);\n  cursor: default;\n  font-size: 12px;\n  font-style: normal;\n  font-weight: normal;\n  margin-bottom: 4px;\n  margin-top: -4px;\n  padding: 4px 8px;\n  text-transform: none;\n  -webkit-touch-callout: none;\n  -webkit-user-select: none;\n     -moz-user-select: none;\n          user-select: none;\n}\n.tox .tox-collection__item {\n  align-items: center;\n  border-radius: 3px;\n  color: #fff;\n  display: flex;\n  -webkit-touch-callout: none;\n  -webkit-user-select: none;\n     -moz-user-select: none;\n          user-select: none;\n}\n.tox .tox-collection--list .tox-collection__item {\n  padding: 4px 8px;\n}\n.tox .tox-collection--toolbar .tox-collection__item {\n  border-radius: 3px;\n  padding: 4px;\n}\n.tox .tox-collection--grid .tox-collection__item {\n  border-radius: 3px;\n  padding: 4px;\n}\n.tox .tox-collection--list .tox-collection__item--enabled {\n  background-color: #2b3b4e;\n  color: #fff;\n}\n.tox .tox-collection--list .tox-collection__item--active {\n  background-color: #3389ec;\n}\n.tox .tox-collection--toolbar .tox-collection__item--enabled {\n  background-color: #599fef;\n  color: #fff;\n}\n.tox .tox-collection--toolbar .tox-collection__item--active {\n  background-color: #3389ec;\n}\n.tox .tox-collection--grid .tox-collection__item--enabled {\n  background-color: #599fef;\n  color: #fff;\n}\n.tox .tox-collection--grid .tox-collection__item--active:not(.tox-collection__item--state-disabled) {\n  background-color: #3389ec;\n  color: #fff;\n}\n.tox .tox-collection--list .tox-collection__item--active:not(.tox-collection__item--state-disabled) {\n  color: #fff;\n}\n.tox .tox-collection--toolbar .tox-collection__item--active:not(.tox-collection__item--state-disabled) {\n  color: #fff;\n}\n.tox .tox-collection__item-icon,\n.tox .tox-collection__item-checkmark {\n  align-items: center;\n  display: flex;\n  height: 24px;\n  justify-content: center;\n  width: 24px;\n}\n.tox .tox-collection__item-icon svg,\n.tox .tox-collection__item-checkmark svg {\n  fill: currentColor;\n}\n.tox .tox-collection--toolbar-lg .tox-collection__item-icon {\n  height: 48px;\n  width: 48px;\n}\n.tox .tox-collection__item-label {\n  color: currentColor;\n  display: inline-block;\n  flex: 1;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: normal;\n  line-height: 24px;\n  text-transform: none;\n  word-break: break-all;\n}\n.tox .tox-collection__item-accessory {\n  color: rgba(255, 255, 255, 0.5);\n  display: inline-block;\n  font-size: 14px;\n  height: 24px;\n  line-height: 24px;\n  text-transform: none;\n}\n.tox .tox-collection__item-caret {\n  align-items: center;\n  display: flex;\n  min-height: 24px;\n}\n.tox .tox-collection__item-caret::after {\n  content: '';\n  font-size: 0;\n  min-height: inherit;\n}\n.tox .tox-collection__item-caret svg {\n  fill: #fff;\n}\n.tox .tox-collection__item--state-disabled {\n  background-color: transparent;\n  color: rgba(255, 255, 255, 0.5);\n  cursor: not-allowed;\n}\n.tox .tox-collection__item--state-disabled .tox-collection__item-caret svg {\n  fill: rgba(255, 255, 255, 0.5);\n}\n.tox .tox-collection--list .tox-collection__item:not(.tox-collection__item--enabled) .tox-collection__item-checkmark svg {\n  display: none;\n}\n.tox .tox-collection--list .tox-collection__item:not(.tox-collection__item--enabled) .tox-collection__item-accessory + .tox-collection__item-checkmark {\n  display: none;\n}\n.tox .tox-collection--horizontal {\n  background-color: #2b3b4e;\n  border: 1px solid rgba(255, 255, 255, 0.15);\n  border-radius: 6px;\n  box-shadow: 0 0 2px 0 rgba(34, 47, 62, 0.2), 0 4px 8px 0 rgba(34, 47, 62, 0.15);\n  display: flex;\n  flex: 0 0 auto;\n  flex-shrink: 0;\n  flex-wrap: nowrap;\n  margin-bottom: 0;\n  overflow-x: auto;\n  padding: 0;\n}\n.tox .tox-collection--horizontal .tox-collection__group {\n  align-items: center;\n  display: flex;\n  flex-wrap: nowrap;\n  margin: 0;\n  padding: 0 4px;\n}\n.tox .tox-collection--horizontal .tox-collection__item {\n  height: 28px;\n  margin: 6px 1px 5px 0;\n  padding: 0 4px;\n}\n.tox .tox-collection--horizontal .tox-collection__item-label {\n  white-space: nowrap;\n}\n.tox .tox-collection--horizontal .tox-collection__item-caret {\n  margin-left: 4px;\n}\n.tox .tox-collection__item-container {\n  display: flex;\n}\n.tox .tox-collection__item-container--row {\n  align-items: center;\n  flex: 1 1 auto;\n  flex-direction: row;\n}\n.tox .tox-collection__item-container--row.tox-collection__item-container--align-left {\n  margin-right: auto;\n}\n.tox .tox-collection__item-container--row.tox-collection__item-container--align-right {\n  justify-content: flex-end;\n  margin-left: auto;\n}\n.tox .tox-collection__item-container--row.tox-collection__item-container--valign-top {\n  align-items: flex-start;\n  margin-bottom: auto;\n}\n.tox .tox-collection__item-container--row.tox-collection__item-container--valign-middle {\n  align-items: center;\n}\n.tox .tox-collection__item-container--row.tox-collection__item-container--valign-bottom {\n  align-items: flex-end;\n  margin-top: auto;\n}\n.tox .tox-collection__item-container--column {\n  align-self: center;\n  flex: 1 1 auto;\n  flex-direction: column;\n}\n.tox .tox-collection__item-container--column.tox-collection__item-container--align-left {\n  align-items: flex-start;\n}\n.tox .tox-collection__item-container--column.tox-collection__item-container--align-right {\n  align-items: flex-end;\n}\n.tox .tox-collection__item-container--column.tox-collection__item-container--valign-top {\n  align-self: flex-start;\n}\n.tox .tox-collection__item-container--column.tox-collection__item-container--valign-middle {\n  align-self: center;\n}\n.tox .tox-collection__item-container--column.tox-collection__item-container--valign-bottom {\n  align-self: flex-end;\n}\n.tox:not([dir=rtl]) .tox-collection--horizontal .tox-collection__group:not(:last-of-type) {\n  border-right: 1px solid transparent;\n}\n.tox:not([dir=rtl]) .tox-collection--list .tox-collection__item > *:not(:first-child) {\n  margin-left: 8px;\n}\n.tox:not([dir=rtl]) .tox-collection--list .tox-collection__item > .tox-collection__item-label:first-child {\n  margin-left: 4px;\n}\n.tox:not([dir=rtl]) .tox-collection__item-accessory {\n  margin-left: 16px;\n  text-align: right;\n}\n.tox:not([dir=rtl]) .tox-collection .tox-collection__item-caret {\n  margin-left: 16px;\n}\n.tox[dir=rtl] .tox-collection--horizontal .tox-collection__group:not(:last-of-type) {\n  border-left: 1px solid transparent;\n}\n.tox[dir=rtl] .tox-collection--list .tox-collection__item > *:not(:first-child) {\n  margin-right: 8px;\n}\n.tox[dir=rtl] .tox-collection--list .tox-collection__item > .tox-collection__item-label:first-child {\n  margin-right: 4px;\n}\n.tox[dir=rtl] .tox-collection__item-accessory {\n  margin-right: 16px;\n  text-align: left;\n}\n.tox[dir=rtl] .tox-collection .tox-collection__item-caret {\n  margin-right: 16px;\n  transform: rotateY(180deg);\n}\n.tox[dir=rtl] .tox-collection--horizontal .tox-collection__item-caret {\n  margin-right: 4px;\n}\n.tox .tox-color-picker-container {\n  display: flex;\n  flex-direction: row;\n  height: 225px;\n  margin: 0;\n}\n.tox .tox-sv-palette {\n  box-sizing: border-box;\n  display: flex;\n  height: 100%;\n}\n.tox .tox-sv-palette-spectrum {\n  height: 100%;\n}\n.tox .tox-sv-palette,\n.tox .tox-sv-palette-spectrum {\n  width: 225px;\n}\n.tox .tox-sv-palette-thumb {\n  background: none;\n  border: 1px solid black;\n  border-radius: 50%;\n  box-sizing: content-box;\n  height: 12px;\n  position: absolute;\n  width: 12px;\n}\n.tox .tox-sv-palette-inner-thumb {\n  border: 1px solid white;\n  border-radius: 50%;\n  height: 10px;\n  position: absolute;\n  width: 10px;\n}\n.tox .tox-hue-slider {\n  box-sizing: border-box;\n  height: 100%;\n  width: 25px;\n}\n.tox .tox-hue-slider-spectrum {\n  background: linear-gradient(to bottom, #f00, #ff0080, #f0f, #8000ff, #00f, #0080ff, #0ff, #00ff80, #0f0, #80ff00, #ff0, #ff8000, #f00);\n  height: 100%;\n  width: 100%;\n}\n.tox .tox-hue-slider,\n.tox .tox-hue-slider-spectrum {\n  width: 20px;\n}\n.tox .tox-hue-slider-thumb {\n  background: white;\n  border: 1px solid black;\n  box-sizing: content-box;\n  height: 4px;\n  width: 100%;\n}\n.tox .tox-rgb-form {\n  display: flex;\n  flex-direction: column;\n  justify-content: space-between;\n}\n.tox .tox-rgb-form div {\n  align-items: center;\n  display: flex;\n  justify-content: space-between;\n  margin-bottom: 5px;\n  width: inherit;\n}\n.tox .tox-rgb-form input {\n  width: 6em;\n}\n.tox .tox-rgb-form input.tox-invalid {\n  /* Need !important to override Chrome's focus styling unfortunately */\n  border: 1px solid red !important;\n}\n.tox .tox-rgb-form .tox-rgba-preview {\n  border: 1px solid black;\n  flex-grow: 2;\n  margin-bottom: 0;\n}\n.tox:not([dir=rtl]) .tox-sv-palette {\n  margin-right: 15px;\n}\n.tox:not([dir=rtl]) .tox-hue-slider {\n  margin-right: 15px;\n}\n.tox:not([dir=rtl]) .tox-hue-slider-thumb {\n  margin-left: -1px;\n}\n.tox:not([dir=rtl]) .tox-rgb-form label {\n  margin-right: 0.5em;\n}\n.tox[dir=rtl] .tox-sv-palette {\n  margin-left: 15px;\n}\n.tox[dir=rtl] .tox-hue-slider {\n  margin-left: 15px;\n}\n.tox[dir=rtl] .tox-hue-slider-thumb {\n  margin-right: -1px;\n}\n.tox[dir=rtl] .tox-rgb-form label {\n  margin-left: 0.5em;\n}\n.tox .tox-toolbar .tox-swatches,\n.tox .tox-toolbar__primary .tox-swatches,\n.tox .tox-toolbar__overflow .tox-swatches {\n  margin: 5px 0 6px 11px;\n}\n.tox .tox-collection--list .tox-collection__group .tox-swatches-menu {\n  border: 0;\n  margin: -4px -4px;\n}\n.tox .tox-swatches__row {\n  display: flex;\n}\n.tox .tox-swatch {\n  height: 30px;\n  transition: transform 0.15s, box-shadow 0.15s;\n  width: 30px;\n}\n.tox .tox-swatch:hover,\n.tox .tox-swatch:focus {\n  box-shadow: 0 0 0 1px rgba(127, 127, 127, 0.3) inset;\n  transform: scale(0.8);\n}\n.tox .tox-swatch--remove {\n  align-items: center;\n  display: flex;\n  justify-content: center;\n}\n.tox .tox-swatch--remove svg path {\n  stroke: #e74c3c;\n}\n.tox .tox-swatches__picker-btn {\n  align-items: center;\n  background-color: transparent;\n  border: 0;\n  cursor: pointer;\n  display: flex;\n  height: 30px;\n  justify-content: center;\n  outline: none;\n  padding: 0;\n  width: 30px;\n}\n.tox .tox-swatches__picker-btn svg {\n  fill: #fff;\n  height: 24px;\n  width: 24px;\n}\n.tox .tox-swatches__picker-btn:hover {\n  background: #3389ec;\n}\n.tox div.tox-swatch:not(.tox-swatch--remove) svg {\n  display: none;\n  fill: #fff;\n  height: 24px;\n  margin: calc((30px - 24px) / 2) calc((30px - 24px) / 2);\n  width: 24px;\n}\n.tox div.tox-swatch:not(.tox-swatch--remove) svg path {\n  fill: #fff;\n  paint-order: stroke;\n  stroke: #222f3e;\n  stroke-width: 2px;\n}\n.tox div.tox-swatch:not(.tox-swatch--remove).tox-collection__item--enabled svg {\n  display: block;\n}\n.tox:not([dir=rtl]) .tox-swatches__picker-btn {\n  margin-left: auto;\n}\n.tox[dir=rtl] .tox-swatches__picker-btn {\n  margin-right: auto;\n}\n.tox .tox-comment-thread {\n  background: #2b3b4e;\n  position: relative;\n}\n.tox .tox-comment-thread > *:not(:first-child) {\n  margin-top: 8px;\n}\n.tox .tox-comment {\n  background: #2b3b4e;\n  border: 1px solid #161f29;\n  border-radius: 6px;\n  box-shadow: 0 4px 8px 0 rgba(34, 47, 62, 0.1);\n  padding: 8px 8px 16px 8px;\n  position: relative;\n}\n.tox .tox-comment__header {\n  align-items: center;\n  color: #fff;\n  display: flex;\n  justify-content: space-between;\n}\n.tox .tox-comment__date {\n  color: #fff;\n  font-size: 12px;\n  line-height: 18px;\n}\n.tox .tox-comment__body {\n  color: #fff;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: normal;\n  line-height: 1.3;\n  margin-top: 8px;\n  position: relative;\n  text-transform: initial;\n}\n.tox .tox-comment__body textarea {\n  resize: none;\n  white-space: normal;\n  width: 100%;\n}\n.tox .tox-comment__expander {\n  padding-top: 8px;\n}\n.tox .tox-comment__expander p {\n  color: rgba(255, 255, 255, 0.5);\n  font-size: 14px;\n  font-style: normal;\n}\n.tox .tox-comment__body p {\n  margin: 0;\n}\n.tox .tox-comment__buttonspacing {\n  padding-top: 16px;\n  text-align: center;\n}\n.tox .tox-comment-thread__overlay::after {\n  background: #2b3b4e;\n  bottom: 0;\n  content: \"\";\n  display: flex;\n  left: 0;\n  opacity: 0.9;\n  position: absolute;\n  right: 0;\n  top: 0;\n  z-index: 5;\n}\n.tox .tox-comment__reply {\n  display: flex;\n  flex-shrink: 0;\n  flex-wrap: wrap;\n  justify-content: flex-end;\n  margin-top: 8px;\n}\n.tox .tox-comment__reply > *:first-child {\n  margin-bottom: 8px;\n  width: 100%;\n}\n.tox .tox-comment__edit {\n  display: flex;\n  flex-wrap: wrap;\n  justify-content: flex-end;\n  margin-top: 16px;\n}\n.tox .tox-comment__gradient::after {\n  background: linear-gradient(rgba(43, 59, 78, 0), #2b3b4e);\n  bottom: 0;\n  content: \"\";\n  display: block;\n  height: 5em;\n  margin-top: -40px;\n  position: absolute;\n  width: 100%;\n}\n.tox .tox-comment__overlay {\n  background: #2b3b4e;\n  bottom: 0;\n  display: flex;\n  flex-direction: column;\n  flex-grow: 1;\n  left: 0;\n  opacity: 0.9;\n  position: absolute;\n  right: 0;\n  text-align: center;\n  top: 0;\n  z-index: 5;\n}\n.tox .tox-comment__loading-text {\n  align-items: center;\n  color: #fff;\n  display: flex;\n  flex-direction: column;\n  position: relative;\n}\n.tox .tox-comment__loading-text > div {\n  padding-bottom: 16px;\n}\n.tox .tox-comment__overlaytext {\n  bottom: 0;\n  flex-direction: column;\n  font-size: 14px;\n  left: 0;\n  padding: 1em;\n  position: absolute;\n  right: 0;\n  top: 0;\n  z-index: 10;\n}\n.tox .tox-comment__overlaytext p {\n  background-color: #2b3b4e;\n  box-shadow: 0 0 8px 8px #2b3b4e;\n  color: #fff;\n  text-align: center;\n}\n.tox .tox-comment__overlaytext div:nth-of-type(2) {\n  font-size: 0.8em;\n}\n.tox .tox-comment__busy-spinner {\n  align-items: center;\n  background-color: #2b3b4e;\n  bottom: 0;\n  display: flex;\n  justify-content: center;\n  left: 0;\n  position: absolute;\n  right: 0;\n  top: 0;\n  z-index: 20;\n}\n.tox .tox-comment__scroll {\n  display: flex;\n  flex-direction: column;\n  flex-shrink: 1;\n  overflow: auto;\n}\n.tox .tox-conversations {\n  margin: 8px;\n}\n.tox:not([dir=rtl]) .tox-comment__edit {\n  margin-left: 8px;\n}\n.tox:not([dir=rtl]) .tox-comment__buttonspacing > *:last-child,\n.tox:not([dir=rtl]) .tox-comment__edit > *:last-child,\n.tox:not([dir=rtl]) .tox-comment__reply > *:last-child {\n  margin-left: 8px;\n}\n.tox[dir=rtl] .tox-comment__edit {\n  margin-right: 8px;\n}\n.tox[dir=rtl] .tox-comment__buttonspacing > *:last-child,\n.tox[dir=rtl] .tox-comment__edit > *:last-child,\n.tox[dir=rtl] .tox-comment__reply > *:last-child {\n  margin-right: 8px;\n}\n.tox .tox-user {\n  align-items: center;\n  display: flex;\n}\n.tox .tox-user__avatar svg {\n  fill: rgba(255, 255, 255, 0.5);\n}\n.tox .tox-user__avatar img {\n  border-radius: 50%;\n  height: 36px;\n  object-fit: cover;\n  vertical-align: middle;\n  width: 36px;\n}\n.tox .tox-user__name {\n  color: #fff;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: bold;\n  line-height: 18px;\n  text-transform: none;\n}\n.tox:not([dir=rtl]) .tox-user__avatar svg,\n.tox:not([dir=rtl]) .tox-user__avatar img {\n  margin-right: 8px;\n}\n.tox:not([dir=rtl]) .tox-user__avatar + .tox-user__name {\n  margin-left: 8px;\n}\n.tox[dir=rtl] .tox-user__avatar svg,\n.tox[dir=rtl] .tox-user__avatar img {\n  margin-left: 8px;\n}\n.tox[dir=rtl] .tox-user__avatar + .tox-user__name {\n  margin-right: 8px;\n}\n.tox .tox-dialog-wrap {\n  align-items: center;\n  bottom: 0;\n  display: flex;\n  justify-content: center;\n  left: 0;\n  position: fixed;\n  right: 0;\n  top: 0;\n  z-index: 1100;\n}\n.tox .tox-dialog-wrap__backdrop {\n  background-color: rgba(34, 47, 62, 0.75);\n  bottom: 0;\n  left: 0;\n  position: absolute;\n  right: 0;\n  top: 0;\n  z-index: 1;\n}\n.tox .tox-dialog-wrap__backdrop--opaque {\n  background-color: #222F3E;\n}\n.tox .tox-dialog {\n  background-color: #2b3b4e;\n  border-color: #161f29;\n  border-radius: 10px;\n  border-style: solid;\n  border-width: 0px;\n  box-shadow: 0 16px 16px -10px rgba(34, 47, 62, 0.15), 0 0 40px 1px rgba(34, 47, 62, 0.15);\n  display: flex;\n  flex-direction: column;\n  max-height: 100%;\n  max-width: 480px;\n  overflow: hidden;\n  position: relative;\n  width: 95vw;\n  z-index: 2;\n}\n@media only screen and (max-width: 767px ) {\n  body:not(.tox-force-desktop) .tox .tox-dialog {\n    align-self: flex-start;\n    margin: 8px auto;\n    max-height: calc(100vh - 8px * 2);\n    width: calc(100vw - 16px);\n  }\n}\n.tox .tox-dialog-inline {\n  z-index: 1100;\n}\n.tox .tox-dialog__header {\n  align-items: center;\n  background-color: #2b3b4e;\n  border-bottom: none;\n  color: #fff;\n  display: flex;\n  font-size: 16px;\n  justify-content: space-between;\n  padding: 8px 16px 0 16px;\n  position: relative;\n}\n.tox .tox-dialog__header .tox-button {\n  z-index: 1;\n}\n.tox .tox-dialog__draghandle {\n  cursor: grab;\n  height: 100%;\n  left: 0;\n  position: absolute;\n  top: 0;\n  width: 100%;\n}\n.tox .tox-dialog__draghandle:active {\n  cursor: grabbing;\n}\n.tox .tox-dialog__dismiss {\n  margin-left: auto;\n}\n.tox .tox-dialog__title {\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n  font-size: 20px;\n  font-style: normal;\n  font-weight: normal;\n  line-height: 1.3;\n  margin: 0;\n  text-transform: none;\n}\n.tox .tox-dialog__body {\n  color: #fff;\n  display: flex;\n  flex: 1;\n  font-size: 16px;\n  font-style: normal;\n  font-weight: normal;\n  line-height: 1.3;\n  min-width: 0;\n  text-align: left;\n  text-transform: none;\n}\n@media only screen and (max-width: 767px ) {\n  body:not(.tox-force-desktop) .tox .tox-dialog__body {\n    flex-direction: column;\n  }\n}\n.tox .tox-dialog__body-nav {\n  align-items: flex-start;\n  display: flex;\n  flex-direction: column;\n  flex-shrink: 0;\n  padding: 16px 16px;\n}\n@media only screen and (min-width: 768px ) {\n  .tox .tox-dialog__body-nav {\n    max-width: 11em;\n  }\n}\n@media only screen and (max-width: 767px ) {\n  body:not(.tox-force-desktop) .tox .tox-dialog__body-nav {\n    flex-direction: row;\n    -webkit-overflow-scrolling: touch;\n    overflow-x: auto;\n    padding-bottom: 0;\n  }\n}\n.tox .tox-dialog__body-nav-item {\n  border-bottom: 2px solid transparent;\n  color: rgba(255, 255, 255, 0.5);\n  display: inline-block;\n  flex-shrink: 0;\n  font-size: 14px;\n  line-height: 1.3;\n  margin-bottom: 8px;\n  max-width: 13em;\n  text-decoration: none;\n}\n.tox .tox-dialog__body-nav-item:focus {\n  background-color: rgba(0, 108, 231, 0.1);\n}\n.tox .tox-dialog__body-nav-item--active {\n  border-bottom: 2px solid #006ce7;\n  color: #006ce7;\n}\n.tox .tox-dialog__body-content {\n  box-sizing: border-box;\n  display: flex;\n  flex: 1;\n  flex-direction: column;\n  max-height: min(650px, calc(100vh - 110px));\n  overflow: auto;\n  -webkit-overflow-scrolling: touch;\n  padding: 16px 16px;\n}\n.tox .tox-dialog__body-content > * {\n  margin-bottom: 0;\n  margin-top: 16px;\n}\n.tox .tox-dialog__body-content > *:first-child {\n  margin-top: 0;\n}\n.tox .tox-dialog__body-content > *:last-child {\n  margin-bottom: 0;\n}\n.tox .tox-dialog__body-content > *:only-child {\n  margin-bottom: 0;\n  margin-top: 0;\n}\n.tox .tox-dialog__body-content a {\n  color: #006ce7;\n  cursor: pointer;\n  text-decoration: none;\n}\n.tox .tox-dialog__body-content a:hover,\n.tox .tox-dialog__body-content a:focus {\n  color: #0054b4;\n  text-decoration: none;\n}\n.tox .tox-dialog__body-content a:active {\n  color: #0054b4;\n  text-decoration: none;\n}\n.tox .tox-dialog__body-content svg {\n  fill: #fff;\n}\n.tox .tox-dialog__body-content strong {\n  font-weight: bold;\n}\n.tox .tox-dialog__body-content ul {\n  list-style-type: disc;\n}\n.tox .tox-dialog__body-content ul,\n.tox .tox-dialog__body-content ol,\n.tox .tox-dialog__body-content dd {\n  padding-inline-start: 2.5rem;\n}\n.tox .tox-dialog__body-content ul,\n.tox .tox-dialog__body-content ol,\n.tox .tox-dialog__body-content dl {\n  margin-bottom: 16px;\n}\n.tox .tox-dialog__body-content ul,\n.tox .tox-dialog__body-content ol,\n.tox .tox-dialog__body-content dl,\n.tox .tox-dialog__body-content dd,\n.tox .tox-dialog__body-content dt {\n  display: block;\n  margin-inline-end: 0;\n  margin-inline-start: 0;\n}\n.tox .tox-dialog__body-content .tox-form__group h1 {\n  color: #fff;\n  font-size: 20px;\n  font-style: normal;\n  font-weight: bold;\n  letter-spacing: normal;\n  margin-bottom: 16px;\n  margin-top: 2rem;\n  text-transform: none;\n}\n.tox .tox-dialog__body-content .tox-form__group h2 {\n  color: #fff;\n  font-size: 16px;\n  font-style: normal;\n  font-weight: bold;\n  letter-spacing: normal;\n  margin-bottom: 16px;\n  margin-top: 2rem;\n  text-transform: none;\n}\n.tox .tox-dialog__body-content .tox-form__group p {\n  margin-bottom: 16px;\n}\n.tox .tox-dialog__body-content .tox-form__group h1:first-child,\n.tox .tox-dialog__body-content .tox-form__group h2:first-child,\n.tox .tox-dialog__body-content .tox-form__group p:first-child {\n  margin-top: 0;\n}\n.tox .tox-dialog__body-content .tox-form__group h1:last-child,\n.tox .tox-dialog__body-content .tox-form__group h2:last-child,\n.tox .tox-dialog__body-content .tox-form__group p:last-child {\n  margin-bottom: 0;\n}\n.tox .tox-dialog__body-content .tox-form__group h1:only-child,\n.tox .tox-dialog__body-content .tox-form__group h2:only-child,\n.tox .tox-dialog__body-content .tox-form__group p:only-child {\n  margin-bottom: 0;\n  margin-top: 0;\n}\n.tox .tox-dialog__body-content .tox-form__group .tox-label.tox-label--center {\n  text-align: center;\n}\n.tox .tox-dialog__body-content .tox-form__group .tox-label.tox-label--end {\n  text-align: end;\n}\n.tox .tox-dialog--width-lg {\n  height: 650px;\n  max-width: 1200px;\n}\n.tox .tox-dialog--fullscreen {\n  height: 100%;\n  max-width: 100%;\n}\n.tox .tox-dialog--fullscreen .tox-dialog__body-content {\n  max-height: 100%;\n}\n.tox .tox-dialog--width-md {\n  max-width: 800px;\n}\n.tox .tox-dialog--width-md .tox-dialog__body-content {\n  overflow: auto;\n}\n.tox .tox-dialog__body-content--centered {\n  text-align: center;\n}\n.tox .tox-dialog__footer {\n  align-items: center;\n  background-color: #2b3b4e;\n  border-top: none;\n  display: flex;\n  justify-content: space-between;\n  padding: 8px 16px;\n}\n.tox .tox-dialog__footer-start,\n.tox .tox-dialog__footer-end {\n  display: flex;\n}\n.tox .tox-dialog__busy-spinner {\n  align-items: center;\n  background-color: rgba(34, 47, 62, 0.75);\n  bottom: 0;\n  display: flex;\n  justify-content: center;\n  left: 0;\n  position: absolute;\n  right: 0;\n  top: 0;\n  z-index: 3;\n}\n.tox .tox-dialog__table {\n  border-collapse: collapse;\n  width: 100%;\n}\n.tox .tox-dialog__table thead th {\n  font-weight: bold;\n  padding-bottom: 8px;\n}\n.tox .tox-dialog__table thead th:first-child {\n  padding-right: 8px;\n}\n.tox .tox-dialog__table tbody tr {\n  border-bottom: 1px solid #000000;\n}\n.tox .tox-dialog__table tbody tr:last-child {\n  border-bottom: none;\n}\n.tox .tox-dialog__table td {\n  padding-bottom: 8px;\n  padding-top: 8px;\n}\n.tox .tox-dialog__table td:first-child {\n  padding-right: 8px;\n}\n.tox .tox-dialog__iframe {\n  min-height: 200px;\n}\n.tox .tox-dialog__iframe.tox-dialog__iframe--opaque {\n  background: #fff;\n}\n.tox .tox-dialog__iframe.tox-dialog__iframe--bordered {\n  border: 1px solid #161f29;\n  border-radius: 6px;\n}\n.tox .tox-dialog__popups {\n  position: absolute;\n  width: 100%;\n  z-index: 1100;\n}\n.tox .tox-dialog__body-iframe {\n  display: flex;\n  flex: 1;\n  flex-direction: column;\n}\n.tox .tox-dialog__body-iframe .tox-navobj {\n  display: flex;\n  flex: 1;\n}\n.tox .tox-dialog__body-iframe .tox-navobj :nth-child(2) {\n  flex: 1;\n  height: 100%;\n}\n.tox .tox-dialog-dock-fadeout {\n  opacity: 0;\n  visibility: hidden;\n}\n.tox .tox-dialog-dock-fadein {\n  opacity: 1;\n  visibility: visible;\n}\n.tox .tox-dialog-dock-transition {\n  transition: visibility 0s linear 0.3s, opacity 0.3s ease;\n}\n.tox .tox-dialog-dock-transition.tox-dialog-dock-fadein {\n  transition-delay: 0s;\n}\n@media only screen and (max-width: 767px ) {\n  body:not(.tox-force-desktop) .tox:not([dir=rtl]) .tox-dialog__body-nav {\n    margin-right: 0;\n  }\n}\n@media only screen and (max-width: 767px ) {\n  body:not(.tox-force-desktop) .tox:not([dir=rtl]) .tox-dialog__body-nav-item:not(:first-child) {\n    margin-left: 8px;\n  }\n}\n.tox:not([dir=rtl]) .tox-dialog__footer .tox-dialog__footer-start > *,\n.tox:not([dir=rtl]) .tox-dialog__footer .tox-dialog__footer-end > * {\n  margin-left: 8px;\n}\n.tox[dir=rtl] .tox-dialog__body {\n  text-align: right;\n}\n@media only screen and (max-width: 767px ) {\n  body:not(.tox-force-desktop) .tox[dir=rtl] .tox-dialog__body-nav {\n    margin-left: 0;\n  }\n}\n@media only screen and (max-width: 767px ) {\n  body:not(.tox-force-desktop) .tox[dir=rtl] .tox-dialog__body-nav-item:not(:first-child) {\n    margin-right: 8px;\n  }\n}\n.tox[dir=rtl] .tox-dialog__footer .tox-dialog__footer-start > *,\n.tox[dir=rtl] .tox-dialog__footer .tox-dialog__footer-end > * {\n  margin-right: 8px;\n}\nbody.tox-dialog__disable-scroll {\n  overflow: hidden;\n}\n.tox .tox-dropzone-container {\n  display: flex;\n  flex: 1;\n}\n.tox .tox-dropzone {\n  align-items: center;\n  background: #fff;\n  border: 2px dashed #161f29;\n  box-sizing: border-box;\n  display: flex;\n  flex-direction: column;\n  flex-grow: 1;\n  justify-content: center;\n  min-height: 100px;\n  padding: 10px;\n}\n.tox .tox-dropzone p {\n  color: rgba(255, 255, 255, 0.5);\n  margin: 0 0 16px 0;\n}\n.tox .tox-edit-area {\n  display: flex;\n  flex: 1;\n  overflow: hidden;\n  position: relative;\n}\n.tox .tox-edit-area::before {\n  border: 2px solid #FFFFFF;\n  border-radius: 4px;\n  content: '';\n  inset: 0;\n  opacity: 0;\n  pointer-events: none;\n  position: absolute;\n  transition: opacity 0.15s;\n  z-index: 1;\n}\n.tox .tox-edit-area__iframe {\n  background-color: #fff;\n  border: 0;\n  box-sizing: border-box;\n  flex: 1;\n  height: 100%;\n  position: absolute;\n  width: 100%;\n}\n.tox.tox-edit-focus .tox-edit-area::before {\n  opacity: 1;\n}\n.tox.tox-inline-edit-area {\n  border: 1px dotted #161f29;\n}\n.tox .tox-editor-container {\n  display: flex;\n  flex: 1 1 auto;\n  flex-direction: column;\n  overflow: hidden;\n}\n.tox .tox-editor-header {\n  display: grid;\n  grid-template-columns: 1fr min-content;\n  z-index: 2;\n}\n.tox:not(.tox-tinymce-inline) .tox-editor-header {\n  background-color: #222F3E;\n  border-bottom: 1px solid rgba(255, 255, 255, 0.15);\n  box-shadow: none;\n  padding: 4px 0;\n}\n.tox:not(.tox-tinymce-inline) .tox-editor-header:not(.tox-editor-dock-transition) {\n  transition: box-shadow 0.5s;\n}\n.tox:not(.tox-tinymce-inline).tox-tinymce--toolbar-bottom .tox-editor-header {\n  border-top: 1px solid rgba(255, 255, 255, 0.15);\n  box-shadow: none;\n}\n.tox:not(.tox-tinymce-inline).tox-tinymce--toolbar-sticky-on .tox-editor-header {\n  background-color: #222F3E;\n  box-shadow: none;\n  padding: 4px 0;\n}\n.tox:not(.tox-tinymce-inline).tox-tinymce--toolbar-sticky-on.tox-tinymce--toolbar-bottom .tox-editor-header {\n  box-shadow: none;\n}\n.tox.tox:not(.tox-tinymce-inline) .tox-editor-header.tox-editor-header--empty {\n  background: none;\n  border: none;\n  box-shadow: none;\n  padding: 0;\n}\n.tox-editor-dock-fadeout {\n  opacity: 0;\n  visibility: hidden;\n}\n.tox-editor-dock-fadein {\n  opacity: 1;\n  visibility: visible;\n}\n.tox-editor-dock-transition {\n  transition: visibility 0s linear 0.25s, opacity 0.25s ease;\n}\n.tox-editor-dock-transition.tox-editor-dock-fadein {\n  transition-delay: 0s;\n}\n.tox .tox-control-wrap {\n  flex: 1;\n  position: relative;\n}\n.tox .tox-control-wrap:not(.tox-control-wrap--status-invalid) .tox-control-wrap__status-icon-invalid,\n.tox .tox-control-wrap:not(.tox-control-wrap--status-unknown) .tox-control-wrap__status-icon-unknown,\n.tox .tox-control-wrap:not(.tox-control-wrap--status-valid) .tox-control-wrap__status-icon-valid {\n  display: none;\n}\n.tox .tox-control-wrap svg {\n  display: block;\n}\n.tox .tox-control-wrap__status-icon-wrap {\n  position: absolute;\n  top: 50%;\n  transform: translateY(-50%);\n}\n.tox .tox-control-wrap__status-icon-invalid svg {\n  fill: #c00;\n}\n.tox .tox-control-wrap__status-icon-unknown svg {\n  fill: orange;\n}\n.tox .tox-control-wrap__status-icon-valid svg {\n  fill: green;\n}\n.tox:not([dir=rtl]) .tox-control-wrap--status-invalid .tox-textfield,\n.tox:not([dir=rtl]) .tox-control-wrap--status-unknown .tox-textfield,\n.tox:not([dir=rtl]) .tox-control-wrap--status-valid .tox-textfield {\n  padding-right: 32px;\n}\n.tox:not([dir=rtl]) .tox-control-wrap__status-icon-wrap {\n  right: 4px;\n}\n.tox[dir=rtl] .tox-control-wrap--status-invalid .tox-textfield,\n.tox[dir=rtl] .tox-control-wrap--status-unknown .tox-textfield,\n.tox[dir=rtl] .tox-control-wrap--status-valid .tox-textfield {\n  padding-left: 32px;\n}\n.tox[dir=rtl] .tox-control-wrap__status-icon-wrap {\n  left: 4px;\n}\n.tox .tox-autocompleter {\n  max-width: 25em;\n}\n.tox .tox-autocompleter .tox-menu {\n  box-sizing: border-box;\n  max-width: 25em;\n}\n.tox .tox-autocompleter .tox-autocompleter-highlight {\n  font-weight: bold;\n}\n.tox .tox-color-input {\n  display: flex;\n  position: relative;\n  z-index: 1;\n}\n.tox .tox-color-input .tox-textfield {\n  z-index: -1;\n}\n.tox .tox-color-input span {\n  border-color: rgba(34, 47, 62, 0.2);\n  border-radius: 6px;\n  border-style: solid;\n  border-width: 1px;\n  box-shadow: none;\n  box-sizing: border-box;\n  height: 24px;\n  position: absolute;\n  top: 6px;\n  width: 24px;\n}\n.tox .tox-color-input span:hover:not([aria-disabled=true]),\n.tox .tox-color-input span:focus:not([aria-disabled=true]) {\n  border-color: #006ce7;\n  cursor: pointer;\n}\n.tox .tox-color-input span::before {\n  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.25) 25%, transparent 25%), linear-gradient(-45deg, rgba(255, 255, 255, 0.25) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, rgba(255, 255, 255, 0.25) 75%), linear-gradient(-45deg, transparent 75%, rgba(255, 255, 255, 0.25) 75%);\n  background-position: 0 0, 0 6px, 6px -6px, -6px 0;\n  background-size: 12px 12px;\n  border: 1px solid #2b3b4e;\n  border-radius: 6px;\n  box-sizing: border-box;\n  content: '';\n  height: 24px;\n  left: -1px;\n  position: absolute;\n  top: -1px;\n  width: 24px;\n  z-index: -1;\n}\n.tox .tox-color-input span[aria-disabled=true] {\n  cursor: not-allowed;\n}\n.tox:not([dir=rtl]) .tox-color-input {\n  /* stylelint-disable-next-line no-descending-specificity */\n}\n.tox:not([dir=rtl]) .tox-color-input .tox-textfield {\n  padding-left: 36px;\n}\n.tox:not([dir=rtl]) .tox-color-input span {\n  left: 6px;\n}\n.tox[dir=\"rtl\"] .tox-color-input {\n  /* stylelint-disable-next-line no-descending-specificity */\n}\n.tox[dir=\"rtl\"] .tox-color-input .tox-textfield {\n  padding-right: 36px;\n}\n.tox[dir=\"rtl\"] .tox-color-input span {\n  right: 6px;\n}\n.tox .tox-label,\n.tox .tox-toolbar-label {\n  color: rgba(255, 255, 255, 0.5);\n  display: block;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: normal;\n  line-height: 1.3;\n  padding: 0 8px 0 0;\n  text-transform: none;\n  white-space: nowrap;\n}\n.tox .tox-toolbar-label {\n  padding: 0 8px;\n}\n.tox[dir=rtl] .tox-label {\n  padding: 0 0 0 8px;\n}\n.tox .tox-form {\n  display: flex;\n  flex: 1;\n  flex-direction: column;\n}\n.tox .tox-form__group {\n  box-sizing: border-box;\n  margin-bottom: 4px;\n}\n.tox .tox-form-group--maximize {\n  flex: 1;\n}\n.tox .tox-form__group--error {\n  color: #c00;\n}\n.tox .tox-form__group--collection {\n  display: flex;\n}\n.tox .tox-form__grid {\n  display: flex;\n  flex-direction: row;\n  flex-wrap: wrap;\n  justify-content: space-between;\n}\n.tox .tox-form__grid--2col > .tox-form__group {\n  width: calc(50% - (8px / 2));\n}\n.tox .tox-form__grid--3col > .tox-form__group {\n  width: calc(100% / 3 - (8px / 2));\n}\n.tox .tox-form__grid--4col > .tox-form__group {\n  width: calc(25% - (8px / 2));\n}\n.tox .tox-form__controls-h-stack {\n  align-items: center;\n  display: flex;\n}\n.tox .tox-form__group--inline {\n  align-items: center;\n  display: flex;\n}\n.tox .tox-form__group--stretched {\n  display: flex;\n  flex: 1;\n  flex-direction: column;\n}\n.tox .tox-form__group--stretched .tox-textarea {\n  flex: 1;\n}\n.tox .tox-form__group--stretched .tox-navobj {\n  display: flex;\n  flex: 1;\n}\n.tox .tox-form__group--stretched .tox-navobj :nth-child(2) {\n  flex: 1;\n  height: 100%;\n}\n.tox:not([dir=rtl]) .tox-form__controls-h-stack > *:not(:first-child) {\n  margin-left: 4px;\n}\n.tox[dir=rtl] .tox-form__controls-h-stack > *:not(:first-child) {\n  margin-right: 4px;\n}\n.tox .tox-lock.tox-locked .tox-lock-icon__unlock,\n.tox .tox-lock:not(.tox-locked) .tox-lock-icon__lock {\n  display: none;\n}\n.tox .tox-textfield,\n.tox .tox-toolbar-textfield,\n.tox .tox-listboxfield .tox-listbox--select,\n.tox .tox-textarea,\n.tox .tox-textarea-wrap .tox-textarea:focus {\n  -webkit-appearance: none;\n     -moz-appearance: none;\n          appearance: none;\n  background-color: #2b3b4e;\n  border-color: #161f29;\n  border-radius: 6px;\n  border-style: solid;\n  border-width: 1px;\n  box-shadow: none;\n  box-sizing: border-box;\n  color: #fff;\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n  font-size: 16px;\n  line-height: 24px;\n  margin: 0;\n  min-height: 34px;\n  outline: none;\n  padding: 5px 5.5px;\n  resize: none;\n  width: 100%;\n}\n.tox .tox-textfield[disabled],\n.tox .tox-textarea[disabled] {\n  background-color: #222f3e;\n  color: rgba(255, 255, 255, 0.85);\n  cursor: not-allowed;\n}\n.tox .tox-textfield:focus,\n.tox .tox-dialog__iframe.tox-dialog__iframe--bordered:focus-within,\n.tox .tox-listboxfield .tox-listbox--select:focus,\n.tox .tox-textarea-wrap:focus-within,\n.tox .tox-textarea:focus,\n.tox .tox-custom-editor:focus-within {\n  background-color: #2b3b4e;\n  border-color: #006ce7;\n  box-shadow: 0 0 0 2px rgba(0, 108, 231, 0.25);\n  outline: none;\n}\n.tox .tox-toolbar-textfield {\n  border-width: 0;\n  margin-bottom: 3px;\n  margin-top: 2px;\n  max-width: 250px;\n}\n.tox .tox-naked-btn {\n  background-color: transparent;\n  border: 0;\n  border-color: transparent;\n  box-shadow: unset;\n  color: #006ce7;\n  cursor: pointer;\n  display: block;\n  margin: 0;\n  padding: 0;\n}\n.tox .tox-naked-btn svg {\n  display: block;\n  fill: #fff;\n}\n.tox:not([dir=rtl]) .tox-toolbar-textfield + * {\n  margin-left: 4px;\n}\n.tox[dir=rtl] .tox-toolbar-textfield + * {\n  margin-right: 4px;\n}\n.tox .tox-listboxfield {\n  cursor: pointer;\n  position: relative;\n}\n.tox .tox-listboxfield .tox-listbox--select[disabled] {\n  background-color: #19232e;\n  color: rgba(255, 255, 255, 0.85);\n  cursor: not-allowed;\n}\n.tox .tox-listbox__select-label {\n  cursor: default;\n  flex: 1;\n  margin: 0 4px;\n}\n.tox .tox-listbox__select-chevron {\n  align-items: center;\n  display: flex;\n  justify-content: center;\n  width: 16px;\n}\n.tox .tox-listbox__select-chevron svg {\n  fill: #fff;\n}\n.tox .tox-listboxfield .tox-listbox--select {\n  align-items: center;\n  display: flex;\n}\n.tox:not([dir=rtl]) .tox-listboxfield svg {\n  right: 8px;\n}\n.tox[dir=rtl] .tox-listboxfield svg {\n  left: 8px;\n}\n.tox .tox-selectfield {\n  cursor: pointer;\n  position: relative;\n}\n.tox .tox-selectfield select {\n  -webkit-appearance: none;\n     -moz-appearance: none;\n          appearance: none;\n  background-color: #2b3b4e;\n  border-color: #161f29;\n  border-radius: 6px;\n  border-style: solid;\n  border-width: 1px;\n  box-shadow: none;\n  box-sizing: border-box;\n  color: #fff;\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n  font-size: 16px;\n  line-height: 24px;\n  margin: 0;\n  min-height: 34px;\n  outline: none;\n  padding: 5px 5.5px;\n  resize: none;\n  width: 100%;\n}\n.tox .tox-selectfield select[disabled] {\n  background-color: #19232e;\n  color: rgba(255, 255, 255, 0.85);\n  cursor: not-allowed;\n}\n.tox .tox-selectfield select::-ms-expand {\n  display: none;\n}\n.tox .tox-selectfield select:focus {\n  background-color: #2b3b4e;\n  border-color: #006ce7;\n  box-shadow: 0 0 0 2px rgba(0, 108, 231, 0.25);\n  outline: none;\n}\n.tox .tox-selectfield svg {\n  pointer-events: none;\n  position: absolute;\n  top: 50%;\n  transform: translateY(-50%);\n}\n.tox:not([dir=rtl]) .tox-selectfield select[size=\"0\"],\n.tox:not([dir=rtl]) .tox-selectfield select[size=\"1\"] {\n  padding-right: 24px;\n}\n.tox:not([dir=rtl]) .tox-selectfield svg {\n  right: 8px;\n}\n.tox[dir=rtl] .tox-selectfield select[size=\"0\"],\n.tox[dir=rtl] .tox-selectfield select[size=\"1\"] {\n  padding-left: 24px;\n}\n.tox[dir=rtl] .tox-selectfield svg {\n  left: 8px;\n}\n.tox .tox-textarea-wrap {\n  border-color: #161f29;\n  border-radius: 6px;\n  border-style: solid;\n  border-width: 1px;\n  display: flex;\n  flex: 1;\n  overflow: hidden;\n}\n.tox .tox-textarea {\n  -webkit-appearance: textarea;\n     -moz-appearance: textarea;\n          appearance: textarea;\n  white-space: pre-wrap;\n}\n.tox .tox-textarea-wrap .tox-textarea {\n  border: none;\n}\n.tox .tox-textarea-wrap .tox-textarea:focus {\n  border: none;\n}\n.tox-fullscreen {\n  border: 0;\n  height: 100%;\n  margin: 0;\n  overflow: hidden;\n  overscroll-behavior: none;\n  padding: 0;\n  touch-action: pinch-zoom;\n  width: 100%;\n}\n.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle {\n  display: none;\n}\n.tox.tox-tinymce.tox-fullscreen,\n.tox-shadowhost.tox-fullscreen {\n  left: 0;\n  position: fixed;\n  top: 0;\n  z-index: 1200;\n}\n.tox.tox-tinymce.tox-fullscreen {\n  background-color: transparent;\n}\n.tox-fullscreen .tox.tox-tinymce-aux,\n.tox-fullscreen ~ .tox.tox-tinymce-aux {\n  z-index: 1201;\n}\n.tox .tox-help__more-link {\n  list-style: none;\n  margin-top: 1em;\n}\n.tox .tox-imagepreview {\n  background-color: #666;\n  height: 380px;\n  overflow: hidden;\n  position: relative;\n  width: 100%;\n}\n.tox .tox-imagepreview.tox-imagepreview__loaded {\n  overflow: auto;\n}\n.tox .tox-imagepreview__container {\n  display: flex;\n  left: 100vw;\n  position: absolute;\n  top: 100vw;\n}\n.tox .tox-imagepreview__image {\n  background: url(data:image/gif;base64,R0lGODdhDAAMAIABAMzMzP///ywAAAAADAAMAAACFoQfqYeabNyDMkBQb81Uat85nxguUAEAOw==);\n}\n.tox .tox-image-tools .tox-spacer {\n  flex: 1;\n}\n.tox .tox-image-tools .tox-bar {\n  align-items: center;\n  display: flex;\n  height: 60px;\n  justify-content: center;\n}\n.tox .tox-image-tools .tox-imagepreview,\n.tox .tox-image-tools .tox-imagepreview + .tox-bar {\n  margin-top: 8px;\n}\n.tox .tox-image-tools .tox-croprect-block {\n  background: black;\n  filter: alpha(opacity=50);\n  opacity: 0.5;\n  position: absolute;\n  zoom: 1;\n}\n.tox .tox-image-tools .tox-croprect-handle {\n  border: 2px solid white;\n  height: 20px;\n  left: 0;\n  position: absolute;\n  top: 0;\n  width: 20px;\n}\n.tox .tox-image-tools .tox-croprect-handle-move {\n  border: 0;\n  cursor: move;\n  position: absolute;\n}\n.tox .tox-image-tools .tox-croprect-handle-nw {\n  border-width: 2px 0 0 2px;\n  cursor: nw-resize;\n  left: 100px;\n  margin: -2px 0 0 -2px;\n  top: 100px;\n}\n.tox .tox-image-tools .tox-croprect-handle-ne {\n  border-width: 2px 2px 0 0;\n  cursor: ne-resize;\n  left: 200px;\n  margin: -2px 0 0 -20px;\n  top: 100px;\n}\n.tox .tox-image-tools .tox-croprect-handle-sw {\n  border-width: 0 0 2px 2px;\n  cursor: sw-resize;\n  left: 100px;\n  margin: -20px 2px 0 -2px;\n  top: 200px;\n}\n.tox .tox-image-tools .tox-croprect-handle-se {\n  border-width: 0 2px 2px 0;\n  cursor: se-resize;\n  left: 200px;\n  margin: -20px 0 0 -20px;\n  top: 200px;\n}\n.tox .tox-insert-table-picker {\n  display: flex;\n  flex-wrap: wrap;\n  width: 170px;\n}\n.tox .tox-insert-table-picker > div {\n  border-color: rgba(255, 255, 255, 0.15);\n  border-style: solid;\n  border-width: 0 1px 1px 0;\n  box-sizing: border-box;\n  height: 17px;\n  width: 17px;\n}\n.tox .tox-collection--list .tox-collection__group .tox-insert-table-picker {\n  margin: -4px -4px;\n}\n.tox .tox-insert-table-picker .tox-insert-table-picker__selected {\n  background-color: rgba(0, 108, 231, 0.5);\n  border-color: rgba(0, 108, 231, 0.5);\n}\n.tox .tox-insert-table-picker__label {\n  color: #fff;\n  display: block;\n  font-size: 14px;\n  padding: 4px;\n  text-align: center;\n  width: 100%;\n}\n.tox:not([dir=rtl]) {\n  /* stylelint-disable-next-line no-descending-specificity */\n}\n.tox:not([dir=rtl]) .tox-insert-table-picker > div:nth-child(10n) {\n  border-right: 0;\n}\n.tox[dir=rtl] {\n  /* stylelint-disable-next-line no-descending-specificity */\n}\n.tox[dir=rtl] .tox-insert-table-picker > div:nth-child(10n+1) {\n  border-right: 0;\n}\n.tox {\n  /* stylelint-disable */\n  /* stylelint-enable */\n}\n.tox .tox-menu {\n  background-color: #2b3b4e;\n  border: 1px solid rgba(255, 255, 255, 0.15);\n  border-radius: 6px;\n  box-shadow: none;\n  display: inline-block;\n  overflow: hidden;\n  vertical-align: top;\n  z-index: 1150;\n}\n.tox .tox-menu.tox-collection.tox-collection--list {\n  padding: 0 4px;\n}\n.tox .tox-menu.tox-collection.tox-collection--toolbar {\n  padding: 8px;\n}\n.tox .tox-menu.tox-collection.tox-collection--grid {\n  padding: 8px;\n}\n@media only screen and (min-width: 768px ) {\n  .tox .tox-menu .tox-collection__item-label {\n    overflow-wrap: break-word;\n    word-break: normal;\n  }\n}\n.tox .tox-menu__label h1,\n.tox .tox-menu__label h2,\n.tox .tox-menu__label h3,\n.tox .tox-menu__label h4,\n.tox .tox-menu__label h5,\n.tox .tox-menu__label h6,\n.tox .tox-menu__label p,\n.tox .tox-menu__label blockquote,\n.tox .tox-menu__label code {\n  margin: 0;\n}\n.tox .tox-menubar {\n  background: repeating-linear-gradient(transparent 0px 1px, transparent 1px 39px) center top 39px / 100% calc(100% - 39px) no-repeat;\n  background-color: #222F3E;\n  display: flex;\n  flex: 0 0 auto;\n  flex-shrink: 0;\n  flex-wrap: wrap;\n  grid-column: 1 / -1;\n  grid-row: 1;\n  padding: 0 11px 0 12px;\n}\n.tox .tox-promotion + .tox-menubar {\n  grid-column: 1;\n}\n.tox .tox-promotion {\n  background: repeating-linear-gradient(transparent 0px 1px, transparent 1px 39px) center top 39px / 100% calc(100% - 39px) no-repeat;\n  background-color: #222F3E;\n  grid-column: 2;\n  grid-row: 1;\n  padding-inline-end: 8px;\n  padding-inline-start: 4px;\n  padding-top: 5px;\n}\n.tox .tox-promotion-link {\n  align-items: unsafe center;\n  background-color: #E8F1F8;\n  border-radius: 5px;\n  color: #086BE6;\n  cursor: pointer;\n  display: flex;\n  font-size: 14px;\n  height: 26.6px;\n  padding: 4px 8px;\n  white-space: nowrap;\n}\n.tox .tox-promotion-link:hover {\n  background-color: #B4D7FF;\n}\n.tox .tox-promotion-link:focus {\n  background-color: #D9EDF7;\n}\n/* Deprecated. Remove in next major release */\n.tox .tox-mbtn {\n  align-items: center;\n  background: transparent;\n  border: 0;\n  border-radius: 3px;\n  box-shadow: none;\n  color: #fff;\n  display: flex;\n  flex: 0 0 auto;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: normal;\n  height: 28px;\n  justify-content: center;\n  margin: 5px 1px 6px 0;\n  outline: none;\n  overflow: hidden;\n  padding: 0 4px;\n  text-transform: none;\n  width: auto;\n}\n.tox .tox-mbtn[disabled] {\n  background-color: transparent;\n  border: 0;\n  box-shadow: none;\n  color: rgba(255, 255, 255, 0.5);\n  cursor: not-allowed;\n}\n.tox .tox-mbtn:focus:not(:disabled) {\n  background: #3389ec;\n  border: 0;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-mbtn--active {\n  background: #599fef;\n  border: 0;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-mbtn:hover:not(:disabled):not(.tox-mbtn--active) {\n  background: #3389ec;\n  border: 0;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-mbtn__select-label {\n  cursor: default;\n  font-weight: normal;\n  margin: 0 4px;\n}\n.tox .tox-mbtn[disabled] .tox-mbtn__select-label {\n  cursor: not-allowed;\n}\n.tox .tox-mbtn__select-chevron {\n  align-items: center;\n  display: flex;\n  justify-content: center;\n  width: 16px;\n  display: none;\n}\n.tox .tox-notification {\n  border-radius: 6px;\n  border-style: solid;\n  border-width: 1px;\n  box-shadow: none;\n  box-sizing: border-box;\n  display: grid;\n  font-size: 14px;\n  font-weight: normal;\n  grid-template-columns: minmax(40px, 1fr) auto minmax(40px, 1fr);\n  margin-top: 4px;\n  opacity: 0;\n  padding: 4px;\n  transition: transform 100ms ease-in, opacity 150ms ease-in;\n}\n.tox .tox-notification p {\n  font-size: 14px;\n  font-weight: normal;\n}\n.tox .tox-notification a {\n  cursor: pointer;\n  text-decoration: underline;\n}\n.tox .tox-notification--in {\n  opacity: 1;\n}\n.tox .tox-notification--success {\n  background-color: #334840;\n  border-color: #3c5440;\n  color: #fff;\n}\n.tox .tox-notification--success p {\n  color: #fff;\n}\n.tox .tox-notification--success a {\n  color: #b5d199;\n}\n.tox .tox-notification--success svg {\n  fill: #fff;\n}\n.tox .tox-notification--error {\n  background-color: #442632;\n  border-color: #55212b;\n  color: #fff;\n}\n.tox .tox-notification--error p {\n  color: #fff;\n}\n.tox .tox-notification--error a {\n  color: #e68080;\n}\n.tox .tox-notification--error svg {\n  fill: #fff;\n}\n.tox .tox-notification--warn,\n.tox .tox-notification--warning {\n  background-color: #222F3E;\n  border-color: rgba(255, 255, 255, 0.15);\n  color: #fff0b3;\n}\n.tox .tox-notification--warn p,\n.tox .tox-notification--warning p {\n  color: #fff0b3;\n}\n.tox .tox-notification--warn a,\n.tox .tox-notification--warning a {\n  color: #ffcc00;\n}\n.tox .tox-notification--warn svg,\n.tox .tox-notification--warning svg {\n  fill: #fff0b3;\n}\n.tox .tox-notification--info {\n  background-color: #254161;\n  border-color: #264972;\n  color: #fff;\n}\n.tox .tox-notification--info p {\n  color: #fff;\n}\n.tox .tox-notification--info a {\n  color: #83b7f3;\n}\n.tox .tox-notification--info svg {\n  fill: #fff;\n}\n.tox .tox-notification__body {\n  align-self: center;\n  color: #fff;\n  font-size: 14px;\n  grid-column-end: 3;\n  grid-column-start: 2;\n  grid-row-end: 2;\n  grid-row-start: 1;\n  text-align: center;\n  white-space: normal;\n  word-break: break-all;\n  word-break: break-word;\n}\n.tox .tox-notification__body > * {\n  margin: 0;\n}\n.tox .tox-notification__body > * + * {\n  margin-top: 1rem;\n}\n.tox .tox-notification__icon {\n  align-self: center;\n  grid-column-end: 2;\n  grid-column-start: 1;\n  grid-row-end: 2;\n  grid-row-start: 1;\n  justify-self: end;\n}\n.tox .tox-notification__icon svg {\n  display: block;\n}\n.tox .tox-notification__dismiss {\n  align-self: start;\n  grid-column-end: 4;\n  grid-column-start: 3;\n  grid-row-end: 2;\n  grid-row-start: 1;\n  justify-self: end;\n}\n.tox .tox-notification .tox-progress-bar {\n  grid-column-end: 4;\n  grid-column-start: 1;\n  grid-row-end: 3;\n  grid-row-start: 2;\n  justify-self: center;\n}\n.tox .tox-pop {\n  display: inline-block;\n  position: relative;\n}\n.tox .tox-pop--resizing {\n  transition: width 0.1s ease;\n}\n.tox .tox-pop--resizing .tox-toolbar,\n.tox .tox-pop--resizing .tox-toolbar__group {\n  flex-wrap: nowrap;\n}\n.tox .tox-pop--transition {\n  transition: 0.15s ease;\n  transition-property: left, right, top, bottom;\n}\n.tox .tox-pop--transition::before,\n.tox .tox-pop--transition::after {\n  transition: all 0.15s, visibility 0s, opacity 0.075s ease 0.075s;\n}\n.tox .tox-pop__dialog {\n  background-color: #222F3E;\n  border: 1px solid #161f29;\n  border-radius: 6px;\n  box-shadow: 0 0 2px 0 rgba(34, 47, 62, 0.2), 0 4px 8px 0 rgba(34, 47, 62, 0.15);\n  min-width: 0;\n  overflow: hidden;\n}\n.tox .tox-pop__dialog > *:not(.tox-toolbar) {\n  margin: 4px 4px 4px 8px;\n}\n.tox .tox-pop__dialog .tox-toolbar {\n  background-color: transparent;\n  margin-bottom: -1px;\n}\n.tox .tox-pop::before,\n.tox .tox-pop::after {\n  border-style: solid;\n  content: '';\n  display: block;\n  height: 0;\n  opacity: 1;\n  position: absolute;\n  width: 0;\n}\n.tox .tox-pop.tox-pop--inset::before,\n.tox .tox-pop.tox-pop--inset::after {\n  opacity: 0;\n  transition: all 0s 0.15s, visibility 0s, opacity 0.075s ease;\n}\n.tox .tox-pop.tox-pop--bottom::before,\n.tox .tox-pop.tox-pop--bottom::after {\n  left: 50%;\n  top: 100%;\n}\n.tox .tox-pop.tox-pop--bottom::after {\n  border-color: #222F3E transparent transparent transparent;\n  border-width: 8px;\n  margin-left: -8px;\n  margin-top: -1px;\n}\n.tox .tox-pop.tox-pop--bottom::before {\n  border-color: #161f29 transparent transparent transparent;\n  border-width: 9px;\n  margin-left: -9px;\n}\n.tox .tox-pop.tox-pop--top::before,\n.tox .tox-pop.tox-pop--top::after {\n  left: 50%;\n  top: 0;\n  transform: translateY(-100%);\n}\n.tox .tox-pop.tox-pop--top::after {\n  border-color: transparent transparent #222F3E transparent;\n  border-width: 8px;\n  margin-left: -8px;\n  margin-top: 1px;\n}\n.tox .tox-pop.tox-pop--top::before {\n  border-color: transparent transparent #161f29 transparent;\n  border-width: 9px;\n  margin-left: -9px;\n}\n.tox .tox-pop.tox-pop--left::before,\n.tox .tox-pop.tox-pop--left::after {\n  left: 0;\n  top: calc(50% - 1px);\n  transform: translateY(-50%);\n}\n.tox .tox-pop.tox-pop--left::after {\n  border-color: transparent #222F3E transparent transparent;\n  border-width: 8px;\n  margin-left: -15px;\n}\n.tox .tox-pop.tox-pop--left::before {\n  border-color: transparent #161f29 transparent transparent;\n  border-width: 10px;\n  margin-left: -19px;\n}\n.tox .tox-pop.tox-pop--right::before,\n.tox .tox-pop.tox-pop--right::after {\n  left: 100%;\n  top: calc(50% + 1px);\n  transform: translateY(-50%);\n}\n.tox .tox-pop.tox-pop--right::after {\n  border-color: transparent transparent transparent #222F3E;\n  border-width: 8px;\n  margin-left: -1px;\n}\n.tox .tox-pop.tox-pop--right::before {\n  border-color: transparent transparent transparent #161f29;\n  border-width: 10px;\n  margin-left: -1px;\n}\n.tox .tox-pop.tox-pop--align-left::before,\n.tox .tox-pop.tox-pop--align-left::after {\n  left: 20px;\n}\n.tox .tox-pop.tox-pop--align-right::before,\n.tox .tox-pop.tox-pop--align-right::after {\n  left: calc(100% - 20px);\n}\n.tox .tox-sidebar-wrap {\n  display: flex;\n  flex-direction: row;\n  flex-grow: 1;\n  min-height: 0;\n}\n.tox .tox-sidebar {\n  background-color: #222F3E;\n  display: flex;\n  flex-direction: row;\n  justify-content: flex-end;\n}\n.tox .tox-sidebar__slider {\n  display: flex;\n  overflow: hidden;\n}\n.tox .tox-sidebar__pane-container {\n  display: flex;\n}\n.tox .tox-sidebar__pane {\n  display: flex;\n}\n.tox .tox-sidebar--sliding-closed {\n  opacity: 0;\n}\n.tox .tox-sidebar--sliding-open {\n  opacity: 1;\n}\n.tox .tox-sidebar--sliding-growing,\n.tox .tox-sidebar--sliding-shrinking {\n  transition: width 0.5s ease, opacity 0.5s ease;\n}\n.tox .tox-selector {\n  background-color: #4099ff;\n  border-color: #4099ff;\n  border-style: solid;\n  border-width: 1px;\n  box-sizing: border-box;\n  display: inline-block;\n  height: 10px;\n  position: absolute;\n  width: 10px;\n}\n.tox.tox-platform-touch .tox-selector {\n  height: 12px;\n  width: 12px;\n}\n.tox .tox-slider {\n  align-items: center;\n  display: flex;\n  flex: 1;\n  height: 24px;\n  justify-content: center;\n  position: relative;\n}\n.tox .tox-slider__rail {\n  background-color: transparent;\n  border: 1px solid #161f29;\n  border-radius: 6px;\n  height: 10px;\n  min-width: 120px;\n  width: 100%;\n}\n.tox .tox-slider__handle {\n  background-color: #006ce7;\n  border: 2px solid #0054b4;\n  border-radius: 6px;\n  box-shadow: none;\n  height: 24px;\n  left: 50%;\n  position: absolute;\n  top: 50%;\n  transform: translateX(-50%) translateY(-50%);\n  width: 14px;\n}\n.tox .tox-form__controls-h-stack > .tox-slider:not(:first-of-type) {\n  margin-inline-start: 8px;\n}\n.tox .tox-form__controls-h-stack > .tox-form__group + .tox-slider {\n  margin-inline-start: 32px;\n}\n.tox .tox-form__controls-h-stack > .tox-slider + .tox-form__group {\n  margin-inline-start: 32px;\n}\n.tox .tox-source-code {\n  overflow: auto;\n}\n.tox .tox-spinner {\n  display: flex;\n}\n.tox .tox-spinner > div {\n  animation: tam-bouncing-dots 1.5s ease-in-out 0s infinite both;\n  background-color: rgba(255, 255, 255, 0.5);\n  border-radius: 100%;\n  height: 8px;\n  width: 8px;\n}\n.tox .tox-spinner > div:nth-child(1) {\n  animation-delay: -0.32s;\n}\n.tox .tox-spinner > div:nth-child(2) {\n  animation-delay: -0.16s;\n}\n@keyframes tam-bouncing-dots {\n  0%,\n  80%,\n  100% {\n    transform: scale(0);\n  }\n  40% {\n    transform: scale(1);\n  }\n}\n.tox:not([dir=rtl]) .tox-spinner > div:not(:first-child) {\n  margin-left: 4px;\n}\n.tox[dir=rtl] .tox-spinner > div:not(:first-child) {\n  margin-right: 4px;\n}\n.tox .tox-statusbar {\n  align-items: center;\n  background-color: #222F3E;\n  border-top: 1px solid rgba(255, 255, 255, 0.15);\n  color: rgba(255, 255, 255, 0.75);\n  display: flex;\n  flex: 0 0 auto;\n  font-size: 14px;\n  font-weight: normal;\n  height: 25px;\n  overflow: hidden;\n  padding: 0 8px;\n  position: relative;\n  text-transform: none;\n}\n.tox .tox-statusbar__text-container {\n  display: flex;\n  flex: 1 1 auto;\n  justify-content: flex-end;\n  overflow: hidden;\n}\n.tox .tox-statusbar__path {\n  display: flex;\n  flex: 1 1 auto;\n  margin-right: auto;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n.tox .tox-statusbar__path > * {\n  display: inline;\n  white-space: nowrap;\n}\n.tox .tox-statusbar__wordcount {\n  flex: 0 0 auto;\n  margin-left: 1ch;\n}\n.tox .tox-statusbar a,\n.tox .tox-statusbar__path-item,\n.tox .tox-statusbar__wordcount {\n  color: rgba(255, 255, 255, 0.75);\n  text-decoration: none;\n}\n.tox .tox-statusbar a:hover:not(:disabled):not([aria-disabled=true]),\n.tox .tox-statusbar__path-item:hover:not(:disabled):not([aria-disabled=true]),\n.tox .tox-statusbar__wordcount:hover:not(:disabled):not([aria-disabled=true]),\n.tox .tox-statusbar a:focus:not(:disabled):not([aria-disabled=true]),\n.tox .tox-statusbar__path-item:focus:not(:disabled):not([aria-disabled=true]),\n.tox .tox-statusbar__wordcount:focus:not(:disabled):not([aria-disabled=true]) {\n  color: #fff;\n  cursor: pointer;\n}\n.tox .tox-statusbar__branding svg {\n  fill: rgba(255, 255, 255, 0.8);\n  height: 1.14em;\n  vertical-align: -0.28em;\n  width: 3.6em;\n}\n.tox .tox-statusbar__branding a:hover:not(:disabled):not([aria-disabled=true]) svg,\n.tox .tox-statusbar__branding a:focus:not(:disabled):not([aria-disabled=true]) svg {\n  fill: #fff;\n}\n.tox .tox-statusbar__resize-handle {\n  align-items: flex-end;\n  align-self: stretch;\n  cursor: nwse-resize;\n  display: flex;\n  flex: 0 0 auto;\n  justify-content: flex-end;\n  margin-left: auto;\n  margin-right: -8px;\n  padding-bottom: 3px;\n  padding-left: 1ch;\n  padding-right: 3px;\n}\n.tox .tox-statusbar__resize-handle svg {\n  display: block;\n  fill: rgba(255, 255, 255, 0.5);\n}\n.tox .tox-statusbar__resize-handle:focus svg {\n  background-color: #434e5b;\n  border-radius: 1px 1px 5px 1px;\n  box-shadow: 0 0 0 2px #434e5b;\n}\n.tox:not([dir=rtl]) .tox-statusbar__path > * {\n  margin-right: 4px;\n}\n.tox:not([dir=rtl]) .tox-statusbar__branding {\n  margin-left: 2ch;\n}\n.tox[dir=rtl] .tox-statusbar {\n  flex-direction: row-reverse;\n}\n.tox[dir=rtl] .tox-statusbar__path > * {\n  margin-left: 4px;\n}\n.tox .tox-throbber {\n  z-index: 1299;\n}\n.tox .tox-throbber__busy-spinner {\n  align-items: center;\n  background-color: rgba(34, 47, 62, 0.6);\n  bottom: 0;\n  display: flex;\n  justify-content: center;\n  left: 0;\n  position: absolute;\n  right: 0;\n  top: 0;\n}\n.tox .tox-tbtn {\n  align-items: center;\n  background: transparent;\n  border: 0;\n  border-radius: 3px;\n  box-shadow: none;\n  color: #fff;\n  display: flex;\n  flex: 0 0 auto;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: normal;\n  height: 28px;\n  justify-content: center;\n  margin: 6px 1px 5px 0;\n  outline: none;\n  overflow: hidden;\n  padding: 0;\n  text-transform: none;\n  width: 34px;\n}\n.tox .tox-tbtn svg {\n  display: block;\n  fill: #fff;\n}\n.tox .tox-tbtn.tox-tbtn-more {\n  padding-left: 5px;\n  padding-right: 5px;\n  width: inherit;\n}\n.tox .tox-tbtn:focus {\n  background: #3389ec;\n  border: 0;\n  box-shadow: none;\n}\n.tox .tox-tbtn:hover {\n  background: #3389ec;\n  border: 0;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-tbtn:hover svg {\n  fill: #fff;\n}\n.tox .tox-tbtn:active {\n  background: #599fef;\n  border: 0;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-tbtn:active svg {\n  fill: #fff;\n}\n.tox .tox-tbtn--disabled .tox-tbtn--enabled svg {\n  fill: rgba(255, 255, 255, 0.5);\n}\n.tox .tox-tbtn--disabled,\n.tox .tox-tbtn--disabled:hover,\n.tox .tox-tbtn:disabled,\n.tox .tox-tbtn:disabled:hover {\n  background: transparent;\n  border: 0;\n  box-shadow: none;\n  color: rgba(255, 255, 255, 0.5);\n  cursor: not-allowed;\n}\n.tox .tox-tbtn--disabled svg,\n.tox .tox-tbtn--disabled:hover svg,\n.tox .tox-tbtn:disabled svg,\n.tox .tox-tbtn:disabled:hover svg {\n  /* stylelint-disable-line no-descending-specificity */\n  fill: rgba(255, 255, 255, 0.5);\n}\n.tox .tox-tbtn--enabled,\n.tox .tox-tbtn--enabled:hover {\n  background: #599fef;\n  border: 0;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-tbtn--enabled > *,\n.tox .tox-tbtn--enabled:hover > * {\n  transform: none;\n}\n.tox .tox-tbtn--enabled svg,\n.tox .tox-tbtn--enabled:hover svg {\n  /* stylelint-disable-line no-descending-specificity */\n  fill: #fff;\n}\n.tox .tox-tbtn--enabled.tox-tbtn--disabled svg,\n.tox .tox-tbtn--enabled:hover.tox-tbtn--disabled svg {\n  fill: rgba(255, 255, 255, 0.5);\n}\n.tox .tox-tbtn:focus:not(.tox-tbtn--disabled) {\n  color: #fff;\n}\n.tox .tox-tbtn:focus:not(.tox-tbtn--disabled) svg {\n  fill: #fff;\n}\n.tox .tox-tbtn:active > * {\n  transform: none;\n}\n.tox .tox-tbtn--md {\n  height: 42px;\n  width: 51px;\n}\n.tox .tox-tbtn--lg {\n  flex-direction: column;\n  height: 56px;\n  width: 68px;\n}\n.tox .tox-tbtn--return {\n  align-self: stretch;\n  height: unset;\n  width: 16px;\n}\n.tox .tox-tbtn--labeled {\n  padding: 0 4px;\n  width: unset;\n}\n.tox .tox-tbtn__vlabel {\n  display: block;\n  font-size: 10px;\n  font-weight: normal;\n  letter-spacing: -0.025em;\n  margin-bottom: 4px;\n  white-space: nowrap;\n}\n.tox .tox-number-input {\n  border-radius: 3px;\n  display: flex;\n  margin: 6px 1px 5px 0;\n  padding: 0 4px;\n  width: auto;\n}\n.tox .tox-number-input .tox-input-wrapper {\n  background: #2f4055;\n  display: flex;\n  pointer-events: none;\n  text-align: center;\n}\n.tox .tox-number-input .tox-input-wrapper:focus {\n  background: #3389ec;\n}\n.tox .tox-number-input input {\n  border-radius: 3px;\n  color: #fff;\n  font-size: 14px;\n  margin: 2px 0;\n  pointer-events: all;\n  width: 60px;\n}\n.tox .tox-number-input input:hover {\n  background: #3389ec;\n  color: #fff;\n}\n.tox .tox-number-input input:focus {\n  background: #fff;\n  color: #222f3e;\n}\n.tox .tox-number-input input:disabled {\n  background: transparent;\n  border: 0;\n  box-shadow: none;\n  color: rgba(255, 255, 255, 0.5);\n  cursor: not-allowed;\n}\n.tox .tox-number-input button {\n  background: #2f4055;\n  color: #fff;\n  height: 28px;\n  text-align: center;\n  width: 24px;\n}\n.tox .tox-number-input button svg {\n  display: block;\n  fill: #fff;\n  margin: 0 auto;\n  transform: scale(0.67);\n}\n.tox .tox-number-input button:focus {\n  background: #3389ec;\n}\n.tox .tox-number-input button:hover {\n  background: #3389ec;\n  border: 0;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-number-input button:hover svg {\n  fill: #fff;\n}\n.tox .tox-number-input button:active {\n  background: #599fef;\n  border: 0;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-number-input button:active svg {\n  fill: #fff;\n}\n.tox .tox-number-input button:disabled {\n  background: transparent;\n  border: 0;\n  box-shadow: none;\n  color: rgba(255, 255, 255, 0.5);\n  cursor: not-allowed;\n}\n.tox .tox-number-input button:disabled svg {\n  fill: rgba(255, 255, 255, 0.5);\n}\n.tox .tox-number-input button.minus {\n  border-radius: 3px 0 0 3px;\n}\n.tox .tox-number-input button.plus {\n  border-radius: 0 3px 3px 0;\n}\n.tox .tox-number-input:focus:not(:active) > button,\n.tox .tox-number-input:focus:not(:active) > .tox-input-wrapper {\n  background: #3389ec;\n}\n.tox .tox-tbtn--select {\n  margin: 6px 1px 5px 0;\n  padding: 0 4px;\n  width: auto;\n}\n.tox .tox-tbtn__select-label {\n  cursor: default;\n  font-weight: normal;\n  height: initial;\n  margin: 0 4px;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n.tox .tox-tbtn__select-chevron {\n  align-items: center;\n  display: flex;\n  justify-content: center;\n  width: 16px;\n}\n.tox .tox-tbtn__select-chevron svg {\n  fill: rgba(255, 255, 255, 0.5);\n}\n.tox .tox-tbtn--bespoke {\n  background: #2f4055;\n}\n.tox .tox-tbtn--bespoke + .tox-tbtn--bespoke {\n  margin-inline-start: 4px;\n}\n.tox .tox-tbtn--bespoke .tox-tbtn__select-label {\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  width: 7em;\n}\n.tox .tox-tbtn--disabled .tox-tbtn__select-label,\n.tox .tox-tbtn--select:disabled .tox-tbtn__select-label {\n  cursor: not-allowed;\n}\n.tox .tox-split-button {\n  border: 0;\n  border-radius: 3px;\n  box-sizing: border-box;\n  display: flex;\n  margin: 6px 1px 5px 0;\n  overflow: hidden;\n}\n.tox .tox-split-button:hover {\n  box-shadow: 0 0 0 1px #3389ec inset;\n}\n.tox .tox-split-button:focus {\n  background: #3389ec;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-split-button > * {\n  border-radius: 0;\n}\n.tox .tox-split-button__chevron {\n  width: 16px;\n}\n.tox .tox-split-button__chevron svg {\n  fill: rgba(255, 255, 255, 0.5);\n}\n.tox .tox-split-button .tox-tbtn {\n  margin: 0;\n}\n.tox .tox-split-button.tox-tbtn--disabled:hover,\n.tox .tox-split-button.tox-tbtn--disabled:focus,\n.tox .tox-split-button.tox-tbtn--disabled .tox-tbtn:hover,\n.tox .tox-split-button.tox-tbtn--disabled .tox-tbtn:focus {\n  background: transparent;\n  box-shadow: none;\n  color: rgba(255, 255, 255, 0.5);\n}\n.tox.tox-platform-touch .tox-split-button .tox-tbtn--select {\n  padding: 0 0px;\n}\n.tox.tox-platform-touch .tox-split-button .tox-tbtn:not(.tox-tbtn--select):first-child {\n  width: 30px;\n}\n.tox.tox-platform-touch .tox-split-button__chevron {\n  width: 20px;\n}\n.tox .tox-split-button.tox-tbtn--disabled svg #tox-icon-text-color__color,\n.tox .tox-split-button.tox-tbtn--disabled svg #tox-icon-highlight-bg-color__color {\n  opacity: 0.6;\n}\n.tox .tox-toolbar-overlord {\n  background-color: #222F3E;\n}\n.tox .tox-toolbar,\n.tox .tox-toolbar__primary,\n.tox .tox-toolbar__overflow {\n  background-attachment: local;\n  background-color: #222F3E;\n  background-image: repeating-linear-gradient(rgba(255, 255, 255, 0.15) 0px 1px, transparent 1px 39px);\n  background-position: center top 40px;\n  background-repeat: no-repeat;\n  background-size: calc(100% - 11px * 2) calc(100% - 41px);\n  display: flex;\n  flex: 0 0 auto;\n  flex-shrink: 0;\n  flex-wrap: wrap;\n  padding: 0 0px;\n  transform: perspective(1px);\n}\n.tox .tox-toolbar-overlord > .tox-toolbar,\n.tox .tox-toolbar-overlord > .tox-toolbar__primary,\n.tox .tox-toolbar-overlord > .tox-toolbar__overflow {\n  background-position: center top 0px;\n  background-size: calc(100% - 11px * 2) calc(100% - 0px);\n}\n.tox .tox-toolbar__overflow.tox-toolbar__overflow--closed {\n  height: 0;\n  opacity: 0;\n  padding-bottom: 0;\n  padding-top: 0;\n  visibility: hidden;\n}\n.tox .tox-toolbar__overflow--growing {\n  transition: height 0.3s ease, opacity 0.2s linear 0.1s;\n}\n.tox .tox-toolbar__overflow--shrinking {\n  transition: opacity 0.3s ease, height 0.2s linear 0.1s, visibility 0s linear 0.3s;\n}\n.tox .tox-toolbar-overlord,\n.tox .tox-anchorbar {\n  grid-column: 1 / -1;\n}\n.tox .tox-menubar + .tox-toolbar,\n.tox .tox-menubar + .tox-toolbar-overlord {\n  border-top: 1px solid transparent;\n  margin-top: -1px;\n  padding-bottom: 1px;\n  padding-top: 1px;\n}\n.tox .tox-toolbar--scrolling {\n  flex-wrap: nowrap;\n  overflow-x: auto;\n}\n.tox .tox-pop .tox-toolbar {\n  border-width: 0;\n}\n.tox .tox-toolbar--no-divider {\n  background-image: none;\n}\n.tox .tox-toolbar-overlord .tox-toolbar:not(.tox-toolbar--scrolling):first-child,\n.tox .tox-toolbar-overlord .tox-toolbar__primary {\n  background-position: center top 39px;\n}\n.tox .tox-editor-header > .tox-toolbar--scrolling,\n.tox .tox-toolbar-overlord .tox-toolbar--scrolling:first-child {\n  background-image: none;\n}\n.tox.tox-tinymce-aux .tox-toolbar__overflow {\n  background-color: #222F3E;\n  background-position: center top 43px;\n  background-size: calc(100% - 8px * 2) calc(100% - 51px);\n  border: none;\n  border-radius: 6px;\n  box-shadow: 0 0 2px 0 rgba(34, 47, 62, 0.2), 0 4px 8px 0 rgba(34, 47, 62, 0.15);\n  overscroll-behavior: none;\n  padding: 4px 0;\n}\n.tox-pop .tox-pop__dialog {\n  /* stylelint-disable-next-line no-descending-specificity */\n}\n.tox-pop .tox-pop__dialog .tox-toolbar {\n  background-position: center top 43px;\n  background-size: calc(100% - 11px * 2) calc(100% - 51px);\n  padding: 4px 0;\n}\n.tox .tox-toolbar__group {\n  align-items: center;\n  display: flex;\n  flex-wrap: wrap;\n  margin: 0 0;\n  padding: 0 11px 0 12px;\n}\n.tox .tox-toolbar__group--pull-right {\n  margin-left: auto;\n}\n.tox .tox-toolbar--scrolling .tox-toolbar__group {\n  flex-shrink: 0;\n  flex-wrap: nowrap;\n}\n.tox:not([dir=rtl]) .tox-toolbar__group:not(:last-of-type) {\n  border-right: 1px solid transparent;\n}\n.tox[dir=rtl] .tox-toolbar__group:not(:last-of-type) {\n  border-left: 1px solid transparent;\n}\n.tox .tox-tooltip {\n  display: inline-block;\n  padding: 8px;\n  position: relative;\n}\n.tox .tox-tooltip__body {\n  background-color: #3d546f;\n  border-radius: 6px;\n  box-shadow: 0 2px 4px rgba(34, 47, 62, 0.3);\n  color: rgba(255, 255, 255, 0.75);\n  font-size: 14px;\n  font-style: normal;\n  font-weight: normal;\n  padding: 4px 8px;\n  text-transform: none;\n}\n.tox .tox-tooltip__arrow {\n  position: absolute;\n}\n.tox .tox-tooltip--down .tox-tooltip__arrow {\n  border-left: 8px solid transparent;\n  border-right: 8px solid transparent;\n  border-top: 8px solid #3d546f;\n  bottom: 0;\n  left: 50%;\n  position: absolute;\n  transform: translateX(-50%);\n}\n.tox .tox-tooltip--up .tox-tooltip__arrow {\n  border-bottom: 8px solid #3d546f;\n  border-left: 8px solid transparent;\n  border-right: 8px solid transparent;\n  left: 50%;\n  position: absolute;\n  top: 0;\n  transform: translateX(-50%);\n}\n.tox .tox-tooltip--right .tox-tooltip__arrow {\n  border-bottom: 8px solid transparent;\n  border-left: 8px solid #3d546f;\n  border-top: 8px solid transparent;\n  position: absolute;\n  right: 0;\n  top: 50%;\n  transform: translateY(-50%);\n}\n.tox .tox-tooltip--left .tox-tooltip__arrow {\n  border-bottom: 8px solid transparent;\n  border-right: 8px solid #3d546f;\n  border-top: 8px solid transparent;\n  left: 0;\n  position: absolute;\n  top: 50%;\n  transform: translateY(-50%);\n}\n.tox .tox-tree {\n  display: flex;\n  flex-direction: column;\n}\n.tox .tox-tree .tox-trbtn {\n  align-items: center;\n  background: transparent;\n  border: 0;\n  border-radius: 4px;\n  box-shadow: none;\n  color: #fff;\n  display: flex;\n  flex: 0 0 auto;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: normal;\n  height: 28px;\n  margin-bottom: 4px;\n  margin-top: 4px;\n  outline: none;\n  overflow: hidden;\n  padding: 0;\n  padding-left: 8px;\n  text-transform: none;\n}\n.tox .tox-tree .tox-trbtn .tox-tree__label {\n  cursor: default;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n.tox .tox-tree .tox-trbtn svg {\n  display: block;\n  fill: #fff;\n}\n.tox .tox-tree .tox-trbtn:focus {\n  background: #3389ec;\n  border: 0;\n  box-shadow: none;\n}\n.tox .tox-tree .tox-trbtn:hover {\n  background: #3389ec;\n  border: 0;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-tree .tox-trbtn:hover svg {\n  fill: #fff;\n}\n.tox .tox-tree .tox-trbtn:active {\n  background: #599fef;\n  border: 0;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-tree .tox-trbtn:active svg {\n  fill: #fff;\n}\n.tox .tox-tree .tox-trbtn--disabled,\n.tox .tox-tree .tox-trbtn--disabled:hover,\n.tox .tox-tree .tox-trbtn:disabled,\n.tox .tox-tree .tox-trbtn:disabled:hover {\n  background: transparent;\n  border: 0;\n  box-shadow: none;\n  color: rgba(255, 255, 255, 0.5);\n  cursor: not-allowed;\n}\n.tox .tox-tree .tox-trbtn--disabled svg,\n.tox .tox-tree .tox-trbtn--disabled:hover svg,\n.tox .tox-tree .tox-trbtn:disabled svg,\n.tox .tox-tree .tox-trbtn:disabled:hover svg {\n  /* stylelint-disable-line no-descending-specificity */\n  fill: rgba(255, 255, 255, 0.5);\n}\n.tox .tox-tree .tox-trbtn--enabled,\n.tox .tox-tree .tox-trbtn--enabled:hover {\n  background: #599fef;\n  border: 0;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-tree .tox-trbtn--enabled > *,\n.tox .tox-tree .tox-trbtn--enabled:hover > * {\n  transform: none;\n}\n.tox .tox-tree .tox-trbtn--enabled svg,\n.tox .tox-tree .tox-trbtn--enabled:hover svg {\n  /* stylelint-disable-line no-descending-specificity */\n  fill: #fff;\n}\n.tox .tox-tree .tox-trbtn:focus:not(.tox-trbtn--disabled) {\n  color: #fff;\n}\n.tox .tox-tree .tox-trbtn:focus:not(.tox-trbtn--disabled) svg {\n  fill: #fff;\n}\n.tox .tox-tree .tox-trbtn:active > * {\n  transform: none;\n}\n.tox .tox-tree .tox-trbtn--return {\n  align-self: stretch;\n  height: unset;\n  width: 16px;\n}\n.tox .tox-tree .tox-trbtn--labeled {\n  padding: 0 4px;\n  width: unset;\n}\n.tox .tox-tree .tox-trbtn__vlabel {\n  display: block;\n  font-size: 10px;\n  font-weight: normal;\n  letter-spacing: -0.025em;\n  margin-bottom: 4px;\n  white-space: nowrap;\n}\n.tox .tox-tree .tox-tree--directory {\n  display: flex;\n  flex-direction: column;\n  /* stylelint-disable no-descending-specificity */\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label {\n  font-weight: bold;\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-mbtn {\n  margin-left: auto;\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-mbtn svg {\n  fill: transparent;\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-mbtn.tox-mbtn--active svg,\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-mbtn:focus svg {\n  fill: #fff;\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:hover .tox-mbtn svg,\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:focus .tox-mbtn svg {\n  fill: #fff;\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:hover:has(.tox-mbtn:hover) {\n  background-color: transparent;\n  color: #fff;\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:hover:has(.tox-mbtn:hover) .tox-chevron svg {\n  fill: #fff;\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-chevron {\n  margin-right: 6px;\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:has(+ .tox-tree--directory__children--growing) .tox-chevron,\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:has(+ .tox-tree--directory__children--shrinking) .tox-chevron {\n  transition: transform 0.5s ease-in-out;\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:has(+ .tox-tree--directory__children--growing) .tox-chevron,\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:has(+ .tox-tree--directory__children--open) .tox-chevron {\n  transform: rotate(90deg);\n}\n.tox .tox-tree .tox-tree--leaf__label {\n  font-weight: normal;\n}\n.tox .tox-tree .tox-tree--leaf__label .tox-mbtn {\n  margin-left: auto;\n}\n.tox .tox-tree .tox-tree--leaf__label .tox-mbtn svg {\n  fill: transparent;\n}\n.tox .tox-tree .tox-tree--leaf__label .tox-mbtn.tox-mbtn--active svg,\n.tox .tox-tree .tox-tree--leaf__label .tox-mbtn:focus svg {\n  fill: #fff;\n}\n.tox .tox-tree .tox-tree--leaf__label:hover .tox-mbtn svg {\n  fill: #fff;\n}\n.tox .tox-tree .tox-tree--leaf__label:hover:has(.tox-mbtn:hover) {\n  background-color: transparent;\n  color: #fff;\n}\n.tox .tox-tree .tox-tree--leaf__label:hover:has(.tox-mbtn:hover) .tox-chevron svg {\n  fill: #fff;\n}\n.tox .tox-tree .tox-tree--directory__children {\n  overflow: hidden;\n  padding-left: 16px;\n}\n.tox .tox-tree .tox-tree--directory__children.tox-tree--directory__children--growing,\n.tox .tox-tree .tox-tree--directory__children.tox-tree--directory__children--shrinking {\n  transition: height 0.5s ease-in-out;\n}\n.tox .tox-tree .tox-trbtn.tox-tree--leaf__label {\n  display: flex;\n  justify-content: space-between;\n}\n.tox .tox-view-wrap,\n.tox .tox-view-wrap__slot-container {\n  background-color: #222F3E;\n  display: flex;\n  flex: 1;\n  flex-direction: column;\n}\n.tox .tox-view {\n  display: flex;\n  flex: 1 1 auto;\n  flex-direction: column;\n  overflow: hidden;\n}\n.tox .tox-view__header {\n  align-items: center;\n  display: flex;\n  font-size: 16px;\n  justify-content: space-between;\n  padding: 8px 8px 0 8px;\n  position: relative;\n}\n.tox .tox-view--mobile.tox-view__header,\n.tox .tox-view--mobile.tox-view__toolbar {\n  padding: 8px;\n}\n.tox .tox-view--scrolling {\n  flex-wrap: nowrap;\n  overflow-x: auto;\n}\n.tox .tox-view__toolbar {\n  display: flex;\n  flex-direction: row;\n  gap: 8px;\n  justify-content: space-between;\n  padding: 8px 8px 0 8px;\n}\n.tox .tox-view__toolbar__group {\n  display: flex;\n  flex-direction: row;\n  gap: 12px;\n}\n.tox .tox-view__header-start,\n.tox .tox-view__header-end {\n  display: flex;\n}\n.tox .tox-view__pane {\n  height: 100%;\n  padding: 8px;\n  width: 100%;\n}\n.tox .tox-view__pane_panel {\n  border: 1px solid #161f29;\n  border-radius: 6px;\n}\n.tox:not([dir=rtl]) .tox-view__header .tox-view__header-start > *,\n.tox:not([dir=rtl]) .tox-view__header .tox-view__header-end > * {\n  margin-left: 8px;\n}\n.tox[dir=rtl] .tox-view__header .tox-view__header-start > *,\n.tox[dir=rtl] .tox-view__header .tox-view__header-end > * {\n  margin-right: 8px;\n}\n.tox .tox-well {\n  border: 1px solid #161f29;\n  border-radius: 6px;\n  padding: 8px;\n  width: 100%;\n}\n.tox .tox-well > *:first-child {\n  margin-top: 0;\n}\n.tox .tox-well > *:last-child {\n  margin-bottom: 0;\n}\n.tox .tox-well > *:only-child {\n  margin: 0;\n}\n.tox .tox-custom-editor {\n  border: 1px solid #161f29;\n  border-radius: 6px;\n  display: flex;\n  flex: 1;\n  overflow: hidden;\n  position: relative;\n}\n/* stylelint-disable */\n.tox {\n  /* stylelint-enable */\n}\n.tox .tox-dialog-loading::before {\n  background-color: rgba(0, 0, 0, 0.5);\n  content: \"\";\n  height: 100%;\n  position: absolute;\n  width: 100%;\n  z-index: 1000;\n}\n.tox .tox-tab {\n  cursor: pointer;\n}\n.tox .tox-dialog__content-js {\n  display: flex;\n  flex: 1;\n}\n.tox .tox-dialog__body-content .tox-collection {\n  display: flex;\n  flex: 1;\n}\n.tox.tox-tinymce-aux .tox-toolbar__overflow {\n  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.15);\n}\n"
  },
  {
    "path": "minimalist-vue3/public/tinymce/skins/ui/oxide-dark/skin.shadowdom.css",
    "content": "body.tox-dialog__disable-scroll {\n  overflow: hidden;\n}\n.tox-fullscreen {\n  border: 0;\n  height: 100%;\n  margin: 0;\n  overflow: hidden;\n  overscroll-behavior: none;\n  padding: 0;\n  touch-action: pinch-zoom;\n  width: 100%;\n}\n.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle {\n  display: none;\n}\n.tox.tox-tinymce.tox-fullscreen,\n.tox-shadowhost.tox-fullscreen {\n  left: 0;\n  position: fixed;\n  top: 0;\n  z-index: 1200;\n}\n.tox.tox-tinymce.tox-fullscreen {\n  background-color: transparent;\n}\n.tox-fullscreen .tox.tox-tinymce-aux,\n.tox-fullscreen ~ .tox.tox-tinymce-aux {\n  z-index: 1201;\n}\n"
  },
  {
    "path": "minimalist-vue3/public/tinymce/skins/ui/tinymce-5/content.css",
    "content": ".mce-content-body .mce-item-anchor {\n  background: transparent url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'8'%20height%3D'12'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20d%3D'M0%200L8%200%208%2012%204.09117821%209%200%2012z'%2F%3E%3C%2Fsvg%3E%0A\") no-repeat center;\n}\n.mce-content-body .mce-item-anchor:empty {\n  cursor: default;\n  display: inline-block;\n  height: 12px !important;\n  padding: 0 2px;\n  -webkit-user-modify: read-only;\n  -moz-user-modify: read-only;\n  -webkit-user-select: all;\n  -moz-user-select: all;\n  user-select: all;\n  width: 8px !important;\n}\n.mce-content-body .mce-item-anchor:not(:empty) {\n  background-position-x: 2px;\n  display: inline-block;\n  padding-left: 12px;\n}\n.mce-content-body .mce-item-anchor[data-mce-selected] {\n  outline-offset: 1px;\n}\n.tox-comments-visible .tox-comment[contenteditable=\"false\"]:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment img:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment > audio:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment > video:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment span.mce-preview-object:not([data-mce-selected]) {\n  outline: 3px solid #ffe89d;\n}\n.tox-comments-visible .tox-comment[contenteditable=\"false\"][data-mce-annotation-active=\"true\"]:not([data-mce-selected]) {\n  outline: 3px solid #fed635;\n}\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"] img:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"] > audio:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"] > video:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"] span.mce-preview-object:not([data-mce-selected]) {\n  outline: 3px solid #fed635;\n}\n.tox-comments-visible span.tox-comment:not([data-mce-selected]) {\n  background-color: #ffe89d;\n  outline: none;\n}\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"]:not([data-mce-selected=\"inline-boundary\"]) {\n  background-color: #fed635;\n}\n.tox-checklist > li:not(.tox-checklist--hidden) {\n  list-style: none;\n  margin: 0.25em 0;\n}\n.tox-checklist > li:not(.tox-checklist--hidden)::before {\n  content: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-unchecked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2215%22%20height%3D%2215%22%20x%3D%22.5%22%20y%3D%22.5%22%20fill-rule%3D%22nonzero%22%20stroke%3D%22%234C4C4C%22%20rx%3D%222%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A\");\n  cursor: pointer;\n  height: 1em;\n  margin-left: -1.5em;\n  margin-top: 0.125em;\n  position: absolute;\n  width: 1em;\n}\n.tox-checklist li:not(.tox-checklist--hidden).tox-checklist--checked::before {\n  content: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-checked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22%234099FF%22%20fill-rule%3D%22nonzero%22%20rx%3D%222%22%2F%3E%3Cpath%20id%3D%22Path%22%20fill%3D%22%23FFF%22%20fill-rule%3D%22nonzero%22%20d%3D%22M11.5703186%2C3.14417309%20C11.8516238%2C2.73724603%2012.4164781%2C2.62829933%2012.83558%2C2.89774797%20C13.260121%2C3.17069355%2013.3759736%2C3.72932262%2013.0909105%2C4.14168582%20L7.7580587%2C11.8560195%20C7.43776896%2C12.3193404%206.76483983%2C12.3852142%206.35607322%2C11.9948725%20L3.02491697%2C8.8138662%20C2.66090143%2C8.46625845%202.65798871%2C7.89594698%203.01850234%2C7.54483354%20C3.373942%2C7.19866177%203.94940006%2C7.19592841%204.30829608%2C7.5386474%20L6.85276923%2C9.9684299%20L11.5703186%2C3.14417309%20Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A\");\n}\n[dir=rtl] .tox-checklist > li:not(.tox-checklist--hidden)::before {\n  margin-left: 0;\n  margin-right: -1.5em;\n}\n/* stylelint-disable */\n/* http://prismjs.com/ */\n/**\n * prism.js default theme for JavaScript, CSS and HTML\n * Based on dabblet (http://dabblet.com)\n * @author Lea Verou\n */\ncode[class*=\"language-\"],\npre[class*=\"language-\"] {\n  color: black;\n  background: none;\n  text-shadow: 0 1px white;\n  font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;\n  font-size: 1em;\n  text-align: left;\n  white-space: pre;\n  word-spacing: normal;\n  word-break: normal;\n  word-wrap: normal;\n  line-height: 1.5;\n  -moz-tab-size: 4;\n  tab-size: 4;\n  -webkit-hyphens: none;\n  hyphens: none;\n}\npre[class*=\"language-\"]::-moz-selection,\npre[class*=\"language-\"] ::-moz-selection,\ncode[class*=\"language-\"]::-moz-selection,\ncode[class*=\"language-\"] ::-moz-selection {\n  text-shadow: none;\n  background: #b3d4fc;\n}\npre[class*=\"language-\"]::selection,\npre[class*=\"language-\"] ::selection,\ncode[class*=\"language-\"]::selection,\ncode[class*=\"language-\"] ::selection {\n  text-shadow: none;\n  background: #b3d4fc;\n}\n@media print {\n  code[class*=\"language-\"],\n  pre[class*=\"language-\"] {\n    text-shadow: none;\n  }\n}\n/* Code blocks */\npre[class*=\"language-\"] {\n  padding: 1em;\n  margin: 0.5em 0;\n  overflow: auto;\n}\n:not(pre) > code[class*=\"language-\"],\npre[class*=\"language-\"] {\n  background: #f5f2f0;\n}\n/* Inline code */\n:not(pre) > code[class*=\"language-\"] {\n  padding: 0.1em;\n  border-radius: 0.3em;\n  white-space: normal;\n}\n.token.comment,\n.token.prolog,\n.token.doctype,\n.token.cdata {\n  color: slategray;\n}\n.token.punctuation {\n  color: #999;\n}\n.token.namespace {\n  opacity: 0.7;\n}\n.token.property,\n.token.tag,\n.token.boolean,\n.token.number,\n.token.constant,\n.token.symbol,\n.token.deleted {\n  color: #905;\n}\n.token.selector,\n.token.attr-name,\n.token.string,\n.token.char,\n.token.builtin,\n.token.inserted {\n  color: #690;\n}\n.token.operator,\n.token.entity,\n.token.url,\n.language-css .token.string,\n.style .token.string {\n  color: #9a6e3a;\n  /* This background color was intended by the author of this theme. */\n  background: hsla(0, 0%, 100%, 0.5);\n}\n.token.atrule,\n.token.attr-value,\n.token.keyword {\n  color: #07a;\n}\n.token.function,\n.token.class-name {\n  color: #DD4A68;\n}\n.token.regex,\n.token.important,\n.token.variable {\n  color: #e90;\n}\n.token.important,\n.token.bold {\n  font-weight: bold;\n}\n.token.italic {\n  font-style: italic;\n}\n.token.entity {\n  cursor: help;\n}\n/* stylelint-enable */\n.mce-content-body {\n  overflow-wrap: break-word;\n  word-wrap: break-word;\n}\n.mce-content-body .mce-visual-caret {\n  background-color: black;\n  background-color: currentColor;\n  position: absolute;\n}\n.mce-content-body .mce-visual-caret-hidden {\n  display: none;\n}\n.mce-content-body *[data-mce-caret] {\n  left: -1000px;\n  margin: 0;\n  padding: 0;\n  position: absolute;\n  right: auto;\n  top: 0;\n}\n.mce-content-body .mce-offscreen-selection {\n  left: -2000000px;\n  max-width: 1000000px;\n  position: absolute;\n}\n.mce-content-body *[contentEditable=false] {\n  cursor: default;\n}\n.mce-content-body *[contentEditable=true] {\n  cursor: text;\n}\n.tox-cursor-format-painter {\n  cursor: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A\"), default;\n}\ndiv.mce-footnotes hr {\n  margin-inline-end: auto;\n  margin-inline-start: 0;\n  width: 25%;\n}\ndiv.mce-footnotes li > a.mce-footnotes-backlink {\n  text-decoration: none;\n}\n@media print {\n  sup.mce-footnote a {\n    color: black;\n    text-decoration: none;\n  }\n  div.mce-footnotes {\n    break-inside: avoid;\n    width: 100%;\n  }\n  div.mce-footnotes li > a.mce-footnotes-backlink {\n    display: none;\n  }\n}\n.mce-content-body figure.align-left {\n  float: left;\n}\n.mce-content-body figure.align-right {\n  float: right;\n}\n.mce-content-body figure.image.align-center {\n  display: table;\n  margin-left: auto;\n  margin-right: auto;\n}\n.mce-preview-object {\n  border: 1px solid gray;\n  display: inline-block;\n  line-height: 0;\n  margin: 0 2px 0 2px;\n  position: relative;\n}\n.mce-preview-object .mce-shim {\n  background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);\n  height: 100%;\n  left: 0;\n  position: absolute;\n  top: 0;\n  width: 100%;\n}\n.mce-preview-object[data-mce-selected=\"2\"] .mce-shim {\n  display: none;\n}\n.mce-content-body .mce-mergetag {\n  cursor: default !important;\n  -webkit-user-select: none;\n     -moz-user-select: none;\n          user-select: none;\n}\n.mce-content-body .mce-mergetag:hover {\n  background-color: rgba(0, 108, 231, 0.1);\n}\n.mce-content-body .mce-mergetag-affix {\n  background-color: rgba(0, 108, 231, 0.1);\n  color: #006ce7;\n}\n.mce-object {\n  background: transparent url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%2F%3E%3C%2Fsvg%3E%0A\") no-repeat center;\n  border: 1px dashed #aaa;\n}\n.mce-pagebreak {\n  border: 1px dashed #aaa;\n  cursor: default;\n  display: block;\n  height: 5px;\n  margin-top: 15px;\n  page-break-before: always;\n  width: 100%;\n}\n@media print {\n  .mce-pagebreak {\n    border: 0;\n  }\n}\n.tiny-pageembed .mce-shim {\n  background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);\n  height: 100%;\n  left: 0;\n  position: absolute;\n  top: 0;\n  width: 100%;\n}\n.tiny-pageembed[data-mce-selected=\"2\"] .mce-shim {\n  display: none;\n}\n.tiny-pageembed {\n  display: inline-block;\n  position: relative;\n}\n.tiny-pageembed--21by9,\n.tiny-pageembed--16by9,\n.tiny-pageembed--4by3,\n.tiny-pageembed--1by1 {\n  display: block;\n  overflow: hidden;\n  padding: 0;\n  position: relative;\n  width: 100%;\n}\n.tiny-pageembed--21by9 {\n  padding-top: 42.857143%;\n}\n.tiny-pageembed--16by9 {\n  padding-top: 56.25%;\n}\n.tiny-pageembed--4by3 {\n  padding-top: 75%;\n}\n.tiny-pageembed--1by1 {\n  padding-top: 100%;\n}\n.tiny-pageembed--21by9 iframe,\n.tiny-pageembed--16by9 iframe,\n.tiny-pageembed--4by3 iframe,\n.tiny-pageembed--1by1 iframe {\n  border: 0;\n  height: 100%;\n  left: 0;\n  position: absolute;\n  top: 0;\n  width: 100%;\n}\n.mce-content-body[data-mce-placeholder] {\n  position: relative;\n}\n.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before {\n  color: rgba(34, 47, 62, 0.7);\n  content: attr(data-mce-placeholder);\n  position: absolute;\n}\n.mce-content-body:not([dir=rtl])[data-mce-placeholder]:not(.mce-visualblocks)::before {\n  left: 1px;\n}\n.mce-content-body[dir=rtl][data-mce-placeholder]:not(.mce-visualblocks)::before {\n  right: 1px;\n}\n.mce-content-body div.mce-resizehandle {\n  background-color: #4099ff;\n  border-color: #4099ff;\n  border-style: solid;\n  border-width: 1px;\n  box-sizing: border-box;\n  height: 10px;\n  position: absolute;\n  width: 10px;\n  z-index: 1298;\n}\n.mce-content-body div.mce-resizehandle:hover {\n  background-color: #4099ff;\n}\n.mce-content-body div.mce-resizehandle:nth-of-type(1) {\n  cursor: nwse-resize;\n}\n.mce-content-body div.mce-resizehandle:nth-of-type(2) {\n  cursor: nesw-resize;\n}\n.mce-content-body div.mce-resizehandle:nth-of-type(3) {\n  cursor: nwse-resize;\n}\n.mce-content-body div.mce-resizehandle:nth-of-type(4) {\n  cursor: nesw-resize;\n}\n.mce-content-body .mce-resize-backdrop {\n  z-index: 10000;\n}\n.mce-content-body .mce-clonedresizable {\n  cursor: default;\n  opacity: 0.5;\n  outline: 1px dashed black;\n  position: absolute;\n  z-index: 10001;\n}\n.mce-content-body .mce-clonedresizable.mce-resizetable-columns th,\n.mce-content-body .mce-clonedresizable.mce-resizetable-columns td {\n  border: 0;\n}\n.mce-content-body .mce-resize-helper {\n  background: #555;\n  background: rgba(0, 0, 0, 0.75);\n  border: 1px;\n  border-radius: 3px;\n  color: white;\n  display: none;\n  font-family: sans-serif;\n  font-size: 12px;\n  line-height: 14px;\n  margin: 5px 10px;\n  padding: 5px;\n  position: absolute;\n  white-space: nowrap;\n  z-index: 10002;\n}\n.tox-rtc-user-selection {\n  position: relative;\n}\n.tox-rtc-user-cursor {\n  bottom: 0;\n  cursor: default;\n  position: absolute;\n  top: 0;\n  width: 2px;\n}\n.tox-rtc-user-cursor::before {\n  background-color: inherit;\n  border-radius: 50%;\n  content: '';\n  display: block;\n  height: 8px;\n  position: absolute;\n  right: -3px;\n  top: -3px;\n  width: 8px;\n}\n.tox-rtc-user-cursor:hover::after {\n  background-color: inherit;\n  border-radius: 100px;\n  box-sizing: border-box;\n  color: #fff;\n  content: attr(data-user);\n  display: block;\n  font-size: 12px;\n  font-weight: bold;\n  left: -5px;\n  min-height: 8px;\n  min-width: 8px;\n  padding: 0 12px;\n  position: absolute;\n  top: -11px;\n  white-space: nowrap;\n  z-index: 1000;\n}\n.tox-rtc-user-selection--1 .tox-rtc-user-cursor {\n  background-color: #2dc26b;\n}\n.tox-rtc-user-selection--2 .tox-rtc-user-cursor {\n  background-color: #e03e2d;\n}\n.tox-rtc-user-selection--3 .tox-rtc-user-cursor {\n  background-color: #f1c40f;\n}\n.tox-rtc-user-selection--4 .tox-rtc-user-cursor {\n  background-color: #3598db;\n}\n.tox-rtc-user-selection--5 .tox-rtc-user-cursor {\n  background-color: #b96ad9;\n}\n.tox-rtc-user-selection--6 .tox-rtc-user-cursor {\n  background-color: #e67e23;\n}\n.tox-rtc-user-selection--7 .tox-rtc-user-cursor {\n  background-color: #aaa69d;\n}\n.tox-rtc-user-selection--8 .tox-rtc-user-cursor {\n  background-color: #f368e0;\n}\n.tox-rtc-remote-image {\n  background: #eaeaea url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%2236%22%20height%3D%2212%22%20viewBox%3D%220%200%2036%2012%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%3Ccircle%20cx%3D%226%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2218%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.33s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2230%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.66s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%3C%2Fsvg%3E%0A\") no-repeat center center;\n  border: 1px solid #ccc;\n  min-height: 240px;\n  min-width: 320px;\n}\n.mce-match-marker {\n  background: #aaa;\n  color: #fff;\n}\n.mce-match-marker-selected {\n  background: #39f;\n  color: #fff;\n}\n.mce-match-marker-selected::-moz-selection {\n  background: #39f;\n  color: #fff;\n}\n.mce-match-marker-selected::selection {\n  background: #39f;\n  color: #fff;\n}\n.mce-content-body img[data-mce-selected],\n.mce-content-body video[data-mce-selected],\n.mce-content-body audio[data-mce-selected],\n.mce-content-body object[data-mce-selected],\n.mce-content-body embed[data-mce-selected],\n.mce-content-body table[data-mce-selected],\n.mce-content-body details[data-mce-selected] {\n  outline: 3px solid #b4d7ff;\n}\n.mce-content-body hr[data-mce-selected] {\n  outline: 3px solid #b4d7ff;\n  outline-offset: 1px;\n}\n.mce-content-body *[contentEditable=false] *[contentEditable=true]:focus {\n  outline: 3px solid #b4d7ff;\n}\n.mce-content-body *[contentEditable=false] *[contentEditable=true]:hover {\n  outline: 3px solid #b4d7ff;\n}\n.mce-content-body *[contentEditable=false][data-mce-selected] {\n  cursor: not-allowed;\n  outline: 3px solid #b4d7ff;\n}\n.mce-content-body.mce-content-readonly *[contentEditable=true]:focus,\n.mce-content-body.mce-content-readonly *[contentEditable=true]:hover {\n  outline: none;\n}\n.mce-content-body *[data-mce-selected=\"inline-boundary\"] {\n  background-color: #b4d7ff;\n}\n.mce-content-body .mce-edit-focus {\n  outline: 3px solid #b4d7ff;\n}\n.mce-content-body td[data-mce-selected],\n.mce-content-body th[data-mce-selected] {\n  position: relative;\n}\n.mce-content-body td[data-mce-selected]::-moz-selection,\n.mce-content-body th[data-mce-selected]::-moz-selection {\n  background: none;\n}\n.mce-content-body td[data-mce-selected]::selection,\n.mce-content-body th[data-mce-selected]::selection {\n  background: none;\n}\n.mce-content-body td[data-mce-selected] *,\n.mce-content-body th[data-mce-selected] * {\n  outline: none;\n  -webkit-touch-callout: none;\n  -webkit-user-select: none;\n     -moz-user-select: none;\n          user-select: none;\n}\n.mce-content-body td[data-mce-selected]::after,\n.mce-content-body th[data-mce-selected]::after {\n  background-color: rgba(180, 215, 255, 0.7);\n  border: 1px solid rgba(180, 215, 255, 0.7);\n  bottom: -1px;\n  content: '';\n  left: -1px;\n  mix-blend-mode: multiply;\n  position: absolute;\n  right: -1px;\n  top: -1px;\n}\n@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {\n  .mce-content-body td[data-mce-selected]::after,\n  .mce-content-body th[data-mce-selected]::after {\n    border-color: rgba(0, 84, 180, 0.7);\n  }\n}\n.mce-content-body img[data-mce-selected]::-moz-selection {\n  background: none;\n}\n.mce-content-body img[data-mce-selected]::selection {\n  background: none;\n}\n.ephox-snooker-resizer-bar {\n  background-color: #b4d7ff;\n  opacity: 0;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  user-select: none;\n}\n.ephox-snooker-resizer-cols {\n  cursor: col-resize;\n}\n.ephox-snooker-resizer-rows {\n  cursor: row-resize;\n}\n.ephox-snooker-resizer-bar.ephox-snooker-resizer-bar-dragging {\n  opacity: 1;\n}\n.mce-spellchecker-word {\n  background-image: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%23ff0000'%20fill%3D'none'%20stroke-linecap%3D'round'%20stroke-opacity%3D'.75'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A\");\n  background-position: 0 calc(100% + 1px);\n  background-repeat: repeat-x;\n  background-size: auto 6px;\n  cursor: default;\n  height: 2rem;\n}\n.mce-spellchecker-grammar {\n  background-image: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%2300A835'%20fill%3D'none'%20stroke-linecap%3D'round'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A\");\n  background-position: 0 calc(100% + 1px);\n  background-repeat: repeat-x;\n  background-size: auto 6px;\n  cursor: default;\n}\n.mce-toc {\n  border: 1px solid gray;\n}\n.mce-toc h2 {\n  margin: 4px;\n}\n.mce-toc li {\n  list-style-type: none;\n}\n[data-mce-block] {\n  display: block;\n}\ntable[style*=\"border-width: 0px\"],\n.mce-item-table:not([border]),\n.mce-item-table[border=\"0\"],\ntable[style*=\"border-width: 0px\"] td,\n.mce-item-table:not([border]) td,\n.mce-item-table[border=\"0\"] td,\ntable[style*=\"border-width: 0px\"] th,\n.mce-item-table:not([border]) th,\n.mce-item-table[border=\"0\"] th,\ntable[style*=\"border-width: 0px\"] caption,\n.mce-item-table:not([border]) caption,\n.mce-item-table[border=\"0\"] caption {\n  border: 1px dashed #bbb;\n}\n.mce-visualblocks p,\n.mce-visualblocks h1,\n.mce-visualblocks h2,\n.mce-visualblocks h3,\n.mce-visualblocks h4,\n.mce-visualblocks h5,\n.mce-visualblocks h6,\n.mce-visualblocks div:not([data-mce-bogus]),\n.mce-visualblocks section,\n.mce-visualblocks article,\n.mce-visualblocks blockquote,\n.mce-visualblocks address,\n.mce-visualblocks pre,\n.mce-visualblocks figure,\n.mce-visualblocks figcaption,\n.mce-visualblocks hgroup,\n.mce-visualblocks aside,\n.mce-visualblocks ul,\n.mce-visualblocks ol,\n.mce-visualblocks dl {\n  background-repeat: no-repeat;\n  border: 1px dashed #bbb;\n  margin-left: 3px;\n  padding-top: 10px;\n}\n.mce-visualblocks p {\n  background-image: url(data:image/gif;base64,R0lGODlhCQAJAJEAAAAAAP///7u7u////yH5BAEAAAMALAAAAAAJAAkAAAIQnG+CqCN/mlyvsRUpThG6AgA7);\n}\n.mce-visualblocks h1 {\n  background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGu1JuxHoAfRNRW3TWXyF2YiRUAOw==);\n}\n.mce-visualblocks h2 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8Hybbx4oOuqgTynJd6bGlWg3DkJzoaUAAAOw==);\n}\n.mce-visualblocks h3 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIZjI8Hybbx4oOuqgTynJf2Ln2NOHpQpmhAAQA7);\n}\n.mce-visualblocks h4 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxInR0zqeAdhtJlXwV1oCll2HaWgAAOw==);\n}\n.mce-visualblocks h5 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjane4iq5GlW05GgIkIZUAAAOw==);\n}\n.mce-visualblocks h6 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjan04jep1iZ1XRlAo5bVgAAOw==);\n}\n.mce-visualblocks div:not([data-mce-bogus]) {\n  background-image: url(data:image/gif;base64,R0lGODlhEgAKAIABALu7u////yH5BAEAAAEALAAAAAASAAoAAAIfjI9poI0cgDywrhuxfbrzDEbQM2Ei5aRjmoySW4pAAQA7);\n}\n.mce-visualblocks section {\n  background-image: url(data:image/gif;base64,R0lGODlhKAAKAIABALu7u////yH5BAEAAAEALAAAAAAoAAoAAAI5jI+pywcNY3sBWHdNrplytD2ellDeSVbp+GmWqaDqDMepc8t17Y4vBsK5hDyJMcI6KkuYU+jpjLoKADs=);\n}\n.mce-visualblocks article {\n  background-image: url(data:image/gif;base64,R0lGODlhKgAKAIABALu7u////yH5BAEAAAEALAAAAAAqAAoAAAI6jI+pywkNY3wG0GBvrsd2tXGYSGnfiF7ikpXemTpOiJScasYoDJJrjsG9gkCJ0ag6KhmaIe3pjDYBBQA7);\n}\n.mce-visualblocks blockquote {\n  background-image: url(data:image/gif;base64,R0lGODlhPgAKAIABALu7u////yH5BAEAAAEALAAAAAA+AAoAAAJPjI+py+0Knpz0xQDyuUhvfoGgIX5iSKZYgq5uNL5q69asZ8s5rrf0yZmpNkJZzFesBTu8TOlDVAabUyatguVhWduud3EyiUk45xhTTgMBBQA7);\n}\n.mce-visualblocks address {\n  background-image: url(data:image/gif;base64,R0lGODlhLQAKAIABALu7u////yH5BAEAAAEALAAAAAAtAAoAAAI/jI+pywwNozSP1gDyyZcjb3UaRpXkWaXmZW4OqKLhBmLs+K263DkJK7OJeifh7FicKD9A1/IpGdKkyFpNmCkAADs=);\n}\n.mce-visualblocks pre {\n  background-image: url(data:image/gif;base64,R0lGODlhFQAKAIABALu7uwAAACH5BAEAAAEALAAAAAAVAAoAAAIjjI+ZoN0cgDwSmnpz1NCueYERhnibZVKLNnbOq8IvKpJtVQAAOw==);\n}\n.mce-visualblocks figure {\n  background-image: url(data:image/gif;base64,R0lGODlhJAAKAIAAALu7u////yH5BAEAAAEALAAAAAAkAAoAAAI0jI+py+2fwAHUSFvD3RlvG4HIp4nX5JFSpnZUJ6LlrM52OE7uSWosBHScgkSZj7dDKnWAAgA7);\n}\n.mce-visualblocks figcaption {\n  border: 1px dashed #bbb;\n}\n.mce-visualblocks hgroup {\n  background-image: url(data:image/gif;base64,R0lGODlhJwAKAIABALu7uwAAACH5BAEAAAEALAAAAAAnAAoAAAI3jI+pywYNI3uB0gpsRtt5fFnfNZaVSYJil4Wo03Hv6Z62uOCgiXH1kZIIJ8NiIxRrAZNMZAtQAAA7);\n}\n.mce-visualblocks aside {\n  background-image: url(data:image/gif;base64,R0lGODlhHgAKAIABAKqqqv///yH5BAEAAAEALAAAAAAeAAoAAAItjI+pG8APjZOTzgtqy7I3f1yehmQcFY4WKZbqByutmW4aHUd6vfcVbgudgpYCADs=);\n}\n.mce-visualblocks ul {\n  background-image: url(data:image/gif;base64,R0lGODlhDQAKAIAAALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGuYnqUVSjvw26DzzXiqIDlVwAAOw==);\n}\n.mce-visualblocks ol {\n  background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybH6HHt0qourxC6CvzXieHyeWQAAOw==);\n}\n.mce-visualblocks dl {\n  background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybEOnmOvUoWznTqeuEjNSCqeGRUAOw==);\n}\n.mce-visualblocks:not([dir=rtl]) p,\n.mce-visualblocks:not([dir=rtl]) h1,\n.mce-visualblocks:not([dir=rtl]) h2,\n.mce-visualblocks:not([dir=rtl]) h3,\n.mce-visualblocks:not([dir=rtl]) h4,\n.mce-visualblocks:not([dir=rtl]) h5,\n.mce-visualblocks:not([dir=rtl]) h6,\n.mce-visualblocks:not([dir=rtl]) div:not([data-mce-bogus]),\n.mce-visualblocks:not([dir=rtl]) section,\n.mce-visualblocks:not([dir=rtl]) article,\n.mce-visualblocks:not([dir=rtl]) blockquote,\n.mce-visualblocks:not([dir=rtl]) address,\n.mce-visualblocks:not([dir=rtl]) pre,\n.mce-visualblocks:not([dir=rtl]) figure,\n.mce-visualblocks:not([dir=rtl]) figcaption,\n.mce-visualblocks:not([dir=rtl]) hgroup,\n.mce-visualblocks:not([dir=rtl]) aside,\n.mce-visualblocks:not([dir=rtl]) ul,\n.mce-visualblocks:not([dir=rtl]) ol,\n.mce-visualblocks:not([dir=rtl]) dl {\n  margin-left: 3px;\n}\n.mce-visualblocks[dir=rtl] p,\n.mce-visualblocks[dir=rtl] h1,\n.mce-visualblocks[dir=rtl] h2,\n.mce-visualblocks[dir=rtl] h3,\n.mce-visualblocks[dir=rtl] h4,\n.mce-visualblocks[dir=rtl] h5,\n.mce-visualblocks[dir=rtl] h6,\n.mce-visualblocks[dir=rtl] div:not([data-mce-bogus]),\n.mce-visualblocks[dir=rtl] section,\n.mce-visualblocks[dir=rtl] article,\n.mce-visualblocks[dir=rtl] blockquote,\n.mce-visualblocks[dir=rtl] address,\n.mce-visualblocks[dir=rtl] pre,\n.mce-visualblocks[dir=rtl] figure,\n.mce-visualblocks[dir=rtl] figcaption,\n.mce-visualblocks[dir=rtl] hgroup,\n.mce-visualblocks[dir=rtl] aside,\n.mce-visualblocks[dir=rtl] ul,\n.mce-visualblocks[dir=rtl] ol,\n.mce-visualblocks[dir=rtl] dl {\n  background-position-x: right;\n  margin-right: 3px;\n}\n.mce-nbsp,\n.mce-shy {\n  background: #aaa;\n}\n.mce-shy::after {\n  content: '-';\n}\nbody {\n  font-family: sans-serif;\n}\ntable {\n  border-collapse: collapse;\n}\n"
  },
  {
    "path": "minimalist-vue3/public/tinymce/skins/ui/tinymce-5/content.inline.css",
    "content": ".mce-content-body .mce-item-anchor {\n  background: transparent url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'8'%20height%3D'12'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20d%3D'M0%200L8%200%208%2012%204.09117821%209%200%2012z'%2F%3E%3C%2Fsvg%3E%0A\") no-repeat center;\n}\n.mce-content-body .mce-item-anchor:empty {\n  cursor: default;\n  display: inline-block;\n  height: 12px !important;\n  padding: 0 2px;\n  -webkit-user-modify: read-only;\n  -moz-user-modify: read-only;\n  -webkit-user-select: all;\n  -moz-user-select: all;\n  user-select: all;\n  width: 8px !important;\n}\n.mce-content-body .mce-item-anchor:not(:empty) {\n  background-position-x: 2px;\n  display: inline-block;\n  padding-left: 12px;\n}\n.mce-content-body .mce-item-anchor[data-mce-selected] {\n  outline-offset: 1px;\n}\n.tox-comments-visible .tox-comment[contenteditable=\"false\"]:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment img:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment > audio:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment > video:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment span.mce-preview-object:not([data-mce-selected]) {\n  outline: 3px solid #ffe89d;\n}\n.tox-comments-visible .tox-comment[contenteditable=\"false\"][data-mce-annotation-active=\"true\"]:not([data-mce-selected]) {\n  outline: 3px solid #fed635;\n}\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"] img:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"] > audio:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"] > video:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"] span.mce-preview-object:not([data-mce-selected]) {\n  outline: 3px solid #fed635;\n}\n.tox-comments-visible span.tox-comment:not([data-mce-selected]) {\n  background-color: #ffe89d;\n  outline: none;\n}\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"]:not([data-mce-selected=\"inline-boundary\"]) {\n  background-color: #fed635;\n}\n.tox-checklist > li:not(.tox-checklist--hidden) {\n  list-style: none;\n  margin: 0.25em 0;\n}\n.tox-checklist > li:not(.tox-checklist--hidden)::before {\n  content: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-unchecked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2215%22%20height%3D%2215%22%20x%3D%22.5%22%20y%3D%22.5%22%20fill-rule%3D%22nonzero%22%20stroke%3D%22%234C4C4C%22%20rx%3D%222%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A\");\n  cursor: pointer;\n  height: 1em;\n  margin-left: -1.5em;\n  margin-top: 0.125em;\n  position: absolute;\n  width: 1em;\n}\n.tox-checklist li:not(.tox-checklist--hidden).tox-checklist--checked::before {\n  content: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-checked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22%234099FF%22%20fill-rule%3D%22nonzero%22%20rx%3D%222%22%2F%3E%3Cpath%20id%3D%22Path%22%20fill%3D%22%23FFF%22%20fill-rule%3D%22nonzero%22%20d%3D%22M11.5703186%2C3.14417309%20C11.8516238%2C2.73724603%2012.4164781%2C2.62829933%2012.83558%2C2.89774797%20C13.260121%2C3.17069355%2013.3759736%2C3.72932262%2013.0909105%2C4.14168582%20L7.7580587%2C11.8560195%20C7.43776896%2C12.3193404%206.76483983%2C12.3852142%206.35607322%2C11.9948725%20L3.02491697%2C8.8138662%20C2.66090143%2C8.46625845%202.65798871%2C7.89594698%203.01850234%2C7.54483354%20C3.373942%2C7.19866177%203.94940006%2C7.19592841%204.30829608%2C7.5386474%20L6.85276923%2C9.9684299%20L11.5703186%2C3.14417309%20Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A\");\n}\n[dir=rtl] .tox-checklist > li:not(.tox-checklist--hidden)::before {\n  margin-left: 0;\n  margin-right: -1.5em;\n}\n/* stylelint-disable */\n/* http://prismjs.com/ */\n/**\n * prism.js default theme for JavaScript, CSS and HTML\n * Based on dabblet (http://dabblet.com)\n * @author Lea Verou\n */\ncode[class*=\"language-\"],\npre[class*=\"language-\"] {\n  color: black;\n  background: none;\n  text-shadow: 0 1px white;\n  font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;\n  font-size: 1em;\n  text-align: left;\n  white-space: pre;\n  word-spacing: normal;\n  word-break: normal;\n  word-wrap: normal;\n  line-height: 1.5;\n  -moz-tab-size: 4;\n  tab-size: 4;\n  -webkit-hyphens: none;\n  hyphens: none;\n}\npre[class*=\"language-\"]::-moz-selection,\npre[class*=\"language-\"] ::-moz-selection,\ncode[class*=\"language-\"]::-moz-selection,\ncode[class*=\"language-\"] ::-moz-selection {\n  text-shadow: none;\n  background: #b3d4fc;\n}\npre[class*=\"language-\"]::selection,\npre[class*=\"language-\"] ::selection,\ncode[class*=\"language-\"]::selection,\ncode[class*=\"language-\"] ::selection {\n  text-shadow: none;\n  background: #b3d4fc;\n}\n@media print {\n  code[class*=\"language-\"],\n  pre[class*=\"language-\"] {\n    text-shadow: none;\n  }\n}\n/* Code blocks */\npre[class*=\"language-\"] {\n  padding: 1em;\n  margin: 0.5em 0;\n  overflow: auto;\n}\n:not(pre) > code[class*=\"language-\"],\npre[class*=\"language-\"] {\n  background: #f5f2f0;\n}\n/* Inline code */\n:not(pre) > code[class*=\"language-\"] {\n  padding: 0.1em;\n  border-radius: 0.3em;\n  white-space: normal;\n}\n.token.comment,\n.token.prolog,\n.token.doctype,\n.token.cdata {\n  color: slategray;\n}\n.token.punctuation {\n  color: #999;\n}\n.token.namespace {\n  opacity: 0.7;\n}\n.token.property,\n.token.tag,\n.token.boolean,\n.token.number,\n.token.constant,\n.token.symbol,\n.token.deleted {\n  color: #905;\n}\n.token.selector,\n.token.attr-name,\n.token.string,\n.token.char,\n.token.builtin,\n.token.inserted {\n  color: #690;\n}\n.token.operator,\n.token.entity,\n.token.url,\n.language-css .token.string,\n.style .token.string {\n  color: #9a6e3a;\n  /* This background color was intended by the author of this theme. */\n  background: hsla(0, 0%, 100%, 0.5);\n}\n.token.atrule,\n.token.attr-value,\n.token.keyword {\n  color: #07a;\n}\n.token.function,\n.token.class-name {\n  color: #DD4A68;\n}\n.token.regex,\n.token.important,\n.token.variable {\n  color: #e90;\n}\n.token.important,\n.token.bold {\n  font-weight: bold;\n}\n.token.italic {\n  font-style: italic;\n}\n.token.entity {\n  cursor: help;\n}\n/* stylelint-enable */\n.mce-content-body {\n  overflow-wrap: break-word;\n  word-wrap: break-word;\n}\n.mce-content-body .mce-visual-caret {\n  background-color: black;\n  background-color: currentColor;\n  position: absolute;\n}\n.mce-content-body .mce-visual-caret-hidden {\n  display: none;\n}\n.mce-content-body *[data-mce-caret] {\n  left: -1000px;\n  margin: 0;\n  padding: 0;\n  position: absolute;\n  right: auto;\n  top: 0;\n}\n.mce-content-body .mce-offscreen-selection {\n  left: -2000000px;\n  max-width: 1000000px;\n  position: absolute;\n}\n.mce-content-body *[contentEditable=false] {\n  cursor: default;\n}\n.mce-content-body *[contentEditable=true] {\n  cursor: text;\n}\n.tox-cursor-format-painter {\n  cursor: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A\"), default;\n}\ndiv.mce-footnotes hr {\n  margin-inline-end: auto;\n  margin-inline-start: 0;\n  width: 25%;\n}\ndiv.mce-footnotes li > a.mce-footnotes-backlink {\n  text-decoration: none;\n}\n@media print {\n  sup.mce-footnote a {\n    color: black;\n    text-decoration: none;\n  }\n  div.mce-footnotes {\n    break-inside: avoid;\n    width: 100%;\n  }\n  div.mce-footnotes li > a.mce-footnotes-backlink {\n    display: none;\n  }\n}\n.mce-content-body figure.align-left {\n  float: left;\n}\n.mce-content-body figure.align-right {\n  float: right;\n}\n.mce-content-body figure.image.align-center {\n  display: table;\n  margin-left: auto;\n  margin-right: auto;\n}\n.mce-preview-object {\n  border: 1px solid gray;\n  display: inline-block;\n  line-height: 0;\n  margin: 0 2px 0 2px;\n  position: relative;\n}\n.mce-preview-object .mce-shim {\n  background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);\n  height: 100%;\n  left: 0;\n  position: absolute;\n  top: 0;\n  width: 100%;\n}\n.mce-preview-object[data-mce-selected=\"2\"] .mce-shim {\n  display: none;\n}\n.mce-content-body .mce-mergetag {\n  cursor: default !important;\n  -webkit-user-select: none;\n     -moz-user-select: none;\n          user-select: none;\n}\n.mce-content-body .mce-mergetag:hover {\n  background-color: rgba(0, 108, 231, 0.1);\n}\n.mce-content-body .mce-mergetag-affix {\n  background-color: rgba(0, 108, 231, 0.1);\n  color: #006ce7;\n}\n.mce-object {\n  background: transparent url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%2F%3E%3C%2Fsvg%3E%0A\") no-repeat center;\n  border: 1px dashed #aaa;\n}\n.mce-pagebreak {\n  border: 1px dashed #aaa;\n  cursor: default;\n  display: block;\n  height: 5px;\n  margin-top: 15px;\n  page-break-before: always;\n  width: 100%;\n}\n@media print {\n  .mce-pagebreak {\n    border: 0;\n  }\n}\n.tiny-pageembed .mce-shim {\n  background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);\n  height: 100%;\n  left: 0;\n  position: absolute;\n  top: 0;\n  width: 100%;\n}\n.tiny-pageembed[data-mce-selected=\"2\"] .mce-shim {\n  display: none;\n}\n.tiny-pageembed {\n  display: inline-block;\n  position: relative;\n}\n.tiny-pageembed--21by9,\n.tiny-pageembed--16by9,\n.tiny-pageembed--4by3,\n.tiny-pageembed--1by1 {\n  display: block;\n  overflow: hidden;\n  padding: 0;\n  position: relative;\n  width: 100%;\n}\n.tiny-pageembed--21by9 {\n  padding-top: 42.857143%;\n}\n.tiny-pageembed--16by9 {\n  padding-top: 56.25%;\n}\n.tiny-pageembed--4by3 {\n  padding-top: 75%;\n}\n.tiny-pageembed--1by1 {\n  padding-top: 100%;\n}\n.tiny-pageembed--21by9 iframe,\n.tiny-pageembed--16by9 iframe,\n.tiny-pageembed--4by3 iframe,\n.tiny-pageembed--1by1 iframe {\n  border: 0;\n  height: 100%;\n  left: 0;\n  position: absolute;\n  top: 0;\n  width: 100%;\n}\n.mce-content-body[data-mce-placeholder] {\n  position: relative;\n}\n.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before {\n  color: rgba(34, 47, 62, 0.7);\n  content: attr(data-mce-placeholder);\n  position: absolute;\n}\n.mce-content-body:not([dir=rtl])[data-mce-placeholder]:not(.mce-visualblocks)::before {\n  left: 1px;\n}\n.mce-content-body[dir=rtl][data-mce-placeholder]:not(.mce-visualblocks)::before {\n  right: 1px;\n}\n.mce-content-body div.mce-resizehandle {\n  background-color: #4099ff;\n  border-color: #4099ff;\n  border-style: solid;\n  border-width: 1px;\n  box-sizing: border-box;\n  height: 10px;\n  position: absolute;\n  width: 10px;\n  z-index: 1298;\n}\n.mce-content-body div.mce-resizehandle:hover {\n  background-color: #4099ff;\n}\n.mce-content-body div.mce-resizehandle:nth-of-type(1) {\n  cursor: nwse-resize;\n}\n.mce-content-body div.mce-resizehandle:nth-of-type(2) {\n  cursor: nesw-resize;\n}\n.mce-content-body div.mce-resizehandle:nth-of-type(3) {\n  cursor: nwse-resize;\n}\n.mce-content-body div.mce-resizehandle:nth-of-type(4) {\n  cursor: nesw-resize;\n}\n.mce-content-body .mce-resize-backdrop {\n  z-index: 10000;\n}\n.mce-content-body .mce-clonedresizable {\n  cursor: default;\n  opacity: 0.5;\n  outline: 1px dashed black;\n  position: absolute;\n  z-index: 10001;\n}\n.mce-content-body .mce-clonedresizable.mce-resizetable-columns th,\n.mce-content-body .mce-clonedresizable.mce-resizetable-columns td {\n  border: 0;\n}\n.mce-content-body .mce-resize-helper {\n  background: #555;\n  background: rgba(0, 0, 0, 0.75);\n  border: 1px;\n  border-radius: 3px;\n  color: white;\n  display: none;\n  font-family: sans-serif;\n  font-size: 12px;\n  line-height: 14px;\n  margin: 5px 10px;\n  padding: 5px;\n  position: absolute;\n  white-space: nowrap;\n  z-index: 10002;\n}\n.tox-rtc-user-selection {\n  position: relative;\n}\n.tox-rtc-user-cursor {\n  bottom: 0;\n  cursor: default;\n  position: absolute;\n  top: 0;\n  width: 2px;\n}\n.tox-rtc-user-cursor::before {\n  background-color: inherit;\n  border-radius: 50%;\n  content: '';\n  display: block;\n  height: 8px;\n  position: absolute;\n  right: -3px;\n  top: -3px;\n  width: 8px;\n}\n.tox-rtc-user-cursor:hover::after {\n  background-color: inherit;\n  border-radius: 100px;\n  box-sizing: border-box;\n  color: #fff;\n  content: attr(data-user);\n  display: block;\n  font-size: 12px;\n  font-weight: bold;\n  left: -5px;\n  min-height: 8px;\n  min-width: 8px;\n  padding: 0 12px;\n  position: absolute;\n  top: -11px;\n  white-space: nowrap;\n  z-index: 1000;\n}\n.tox-rtc-user-selection--1 .tox-rtc-user-cursor {\n  background-color: #2dc26b;\n}\n.tox-rtc-user-selection--2 .tox-rtc-user-cursor {\n  background-color: #e03e2d;\n}\n.tox-rtc-user-selection--3 .tox-rtc-user-cursor {\n  background-color: #f1c40f;\n}\n.tox-rtc-user-selection--4 .tox-rtc-user-cursor {\n  background-color: #3598db;\n}\n.tox-rtc-user-selection--5 .tox-rtc-user-cursor {\n  background-color: #b96ad9;\n}\n.tox-rtc-user-selection--6 .tox-rtc-user-cursor {\n  background-color: #e67e23;\n}\n.tox-rtc-user-selection--7 .tox-rtc-user-cursor {\n  background-color: #aaa69d;\n}\n.tox-rtc-user-selection--8 .tox-rtc-user-cursor {\n  background-color: #f368e0;\n}\n.tox-rtc-remote-image {\n  background: #eaeaea url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%2236%22%20height%3D%2212%22%20viewBox%3D%220%200%2036%2012%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%3Ccircle%20cx%3D%226%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2218%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.33s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2230%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.66s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%3C%2Fsvg%3E%0A\") no-repeat center center;\n  border: 1px solid #ccc;\n  min-height: 240px;\n  min-width: 320px;\n}\n.mce-match-marker {\n  background: #aaa;\n  color: #fff;\n}\n.mce-match-marker-selected {\n  background: #39f;\n  color: #fff;\n}\n.mce-match-marker-selected::-moz-selection {\n  background: #39f;\n  color: #fff;\n}\n.mce-match-marker-selected::selection {\n  background: #39f;\n  color: #fff;\n}\n.mce-content-body img[data-mce-selected],\n.mce-content-body video[data-mce-selected],\n.mce-content-body audio[data-mce-selected],\n.mce-content-body object[data-mce-selected],\n.mce-content-body embed[data-mce-selected],\n.mce-content-body table[data-mce-selected],\n.mce-content-body details[data-mce-selected] {\n  outline: 3px solid #b4d7ff;\n}\n.mce-content-body hr[data-mce-selected] {\n  outline: 3px solid #b4d7ff;\n  outline-offset: 1px;\n}\n.mce-content-body *[contentEditable=false] *[contentEditable=true]:focus {\n  outline: 3px solid #b4d7ff;\n}\n.mce-content-body *[contentEditable=false] *[contentEditable=true]:hover {\n  outline: 3px solid #b4d7ff;\n}\n.mce-content-body *[contentEditable=false][data-mce-selected] {\n  cursor: not-allowed;\n  outline: 3px solid #b4d7ff;\n}\n.mce-content-body.mce-content-readonly *[contentEditable=true]:focus,\n.mce-content-body.mce-content-readonly *[contentEditable=true]:hover {\n  outline: none;\n}\n.mce-content-body *[data-mce-selected=\"inline-boundary\"] {\n  background-color: #b4d7ff;\n}\n.mce-content-body .mce-edit-focus {\n  outline: 3px solid #b4d7ff;\n}\n.mce-content-body td[data-mce-selected],\n.mce-content-body th[data-mce-selected] {\n  position: relative;\n}\n.mce-content-body td[data-mce-selected]::-moz-selection,\n.mce-content-body th[data-mce-selected]::-moz-selection {\n  background: none;\n}\n.mce-content-body td[data-mce-selected]::selection,\n.mce-content-body th[data-mce-selected]::selection {\n  background: none;\n}\n.mce-content-body td[data-mce-selected] *,\n.mce-content-body th[data-mce-selected] * {\n  outline: none;\n  -webkit-touch-callout: none;\n  -webkit-user-select: none;\n     -moz-user-select: none;\n          user-select: none;\n}\n.mce-content-body td[data-mce-selected]::after,\n.mce-content-body th[data-mce-selected]::after {\n  background-color: rgba(180, 215, 255, 0.7);\n  border: 1px solid rgba(180, 215, 255, 0.7);\n  bottom: -1px;\n  content: '';\n  left: -1px;\n  mix-blend-mode: multiply;\n  position: absolute;\n  right: -1px;\n  top: -1px;\n}\n@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {\n  .mce-content-body td[data-mce-selected]::after,\n  .mce-content-body th[data-mce-selected]::after {\n    border-color: rgba(0, 84, 180, 0.7);\n  }\n}\n.mce-content-body img[data-mce-selected]::-moz-selection {\n  background: none;\n}\n.mce-content-body img[data-mce-selected]::selection {\n  background: none;\n}\n.ephox-snooker-resizer-bar {\n  background-color: #b4d7ff;\n  opacity: 0;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  user-select: none;\n}\n.ephox-snooker-resizer-cols {\n  cursor: col-resize;\n}\n.ephox-snooker-resizer-rows {\n  cursor: row-resize;\n}\n.ephox-snooker-resizer-bar.ephox-snooker-resizer-bar-dragging {\n  opacity: 1;\n}\n.mce-spellchecker-word {\n  background-image: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%23ff0000'%20fill%3D'none'%20stroke-linecap%3D'round'%20stroke-opacity%3D'.75'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A\");\n  background-position: 0 calc(100% + 1px);\n  background-repeat: repeat-x;\n  background-size: auto 6px;\n  cursor: default;\n  height: 2rem;\n}\n.mce-spellchecker-grammar {\n  background-image: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%2300A835'%20fill%3D'none'%20stroke-linecap%3D'round'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A\");\n  background-position: 0 calc(100% + 1px);\n  background-repeat: repeat-x;\n  background-size: auto 6px;\n  cursor: default;\n}\n.mce-toc {\n  border: 1px solid gray;\n}\n.mce-toc h2 {\n  margin: 4px;\n}\n.mce-toc li {\n  list-style-type: none;\n}\n[data-mce-block] {\n  display: block;\n}\ntable[style*=\"border-width: 0px\"],\n.mce-item-table:not([border]),\n.mce-item-table[border=\"0\"],\ntable[style*=\"border-width: 0px\"] td,\n.mce-item-table:not([border]) td,\n.mce-item-table[border=\"0\"] td,\ntable[style*=\"border-width: 0px\"] th,\n.mce-item-table:not([border]) th,\n.mce-item-table[border=\"0\"] th,\ntable[style*=\"border-width: 0px\"] caption,\n.mce-item-table:not([border]) caption,\n.mce-item-table[border=\"0\"] caption {\n  border: 1px dashed #bbb;\n}\n.mce-visualblocks p,\n.mce-visualblocks h1,\n.mce-visualblocks h2,\n.mce-visualblocks h3,\n.mce-visualblocks h4,\n.mce-visualblocks h5,\n.mce-visualblocks h6,\n.mce-visualblocks div:not([data-mce-bogus]),\n.mce-visualblocks section,\n.mce-visualblocks article,\n.mce-visualblocks blockquote,\n.mce-visualblocks address,\n.mce-visualblocks pre,\n.mce-visualblocks figure,\n.mce-visualblocks figcaption,\n.mce-visualblocks hgroup,\n.mce-visualblocks aside,\n.mce-visualblocks ul,\n.mce-visualblocks ol,\n.mce-visualblocks dl {\n  background-repeat: no-repeat;\n  border: 1px dashed #bbb;\n  margin-left: 3px;\n  padding-top: 10px;\n}\n.mce-visualblocks p {\n  background-image: url(data:image/gif;base64,R0lGODlhCQAJAJEAAAAAAP///7u7u////yH5BAEAAAMALAAAAAAJAAkAAAIQnG+CqCN/mlyvsRUpThG6AgA7);\n}\n.mce-visualblocks h1 {\n  background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGu1JuxHoAfRNRW3TWXyF2YiRUAOw==);\n}\n.mce-visualblocks h2 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8Hybbx4oOuqgTynJd6bGlWg3DkJzoaUAAAOw==);\n}\n.mce-visualblocks h3 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIZjI8Hybbx4oOuqgTynJf2Ln2NOHpQpmhAAQA7);\n}\n.mce-visualblocks h4 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxInR0zqeAdhtJlXwV1oCll2HaWgAAOw==);\n}\n.mce-visualblocks h5 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjane4iq5GlW05GgIkIZUAAAOw==);\n}\n.mce-visualblocks h6 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjan04jep1iZ1XRlAo5bVgAAOw==);\n}\n.mce-visualblocks div:not([data-mce-bogus]) {\n  background-image: url(data:image/gif;base64,R0lGODlhEgAKAIABALu7u////yH5BAEAAAEALAAAAAASAAoAAAIfjI9poI0cgDywrhuxfbrzDEbQM2Ei5aRjmoySW4pAAQA7);\n}\n.mce-visualblocks section {\n  background-image: url(data:image/gif;base64,R0lGODlhKAAKAIABALu7u////yH5BAEAAAEALAAAAAAoAAoAAAI5jI+pywcNY3sBWHdNrplytD2ellDeSVbp+GmWqaDqDMepc8t17Y4vBsK5hDyJMcI6KkuYU+jpjLoKADs=);\n}\n.mce-visualblocks article {\n  background-image: url(data:image/gif;base64,R0lGODlhKgAKAIABALu7u////yH5BAEAAAEALAAAAAAqAAoAAAI6jI+pywkNY3wG0GBvrsd2tXGYSGnfiF7ikpXemTpOiJScasYoDJJrjsG9gkCJ0ag6KhmaIe3pjDYBBQA7);\n}\n.mce-visualblocks blockquote {\n  background-image: url(data:image/gif;base64,R0lGODlhPgAKAIABALu7u////yH5BAEAAAEALAAAAAA+AAoAAAJPjI+py+0Knpz0xQDyuUhvfoGgIX5iSKZYgq5uNL5q69asZ8s5rrf0yZmpNkJZzFesBTu8TOlDVAabUyatguVhWduud3EyiUk45xhTTgMBBQA7);\n}\n.mce-visualblocks address {\n  background-image: url(data:image/gif;base64,R0lGODlhLQAKAIABALu7u////yH5BAEAAAEALAAAAAAtAAoAAAI/jI+pywwNozSP1gDyyZcjb3UaRpXkWaXmZW4OqKLhBmLs+K263DkJK7OJeifh7FicKD9A1/IpGdKkyFpNmCkAADs=);\n}\n.mce-visualblocks pre {\n  background-image: url(data:image/gif;base64,R0lGODlhFQAKAIABALu7uwAAACH5BAEAAAEALAAAAAAVAAoAAAIjjI+ZoN0cgDwSmnpz1NCueYERhnibZVKLNnbOq8IvKpJtVQAAOw==);\n}\n.mce-visualblocks figure {\n  background-image: url(data:image/gif;base64,R0lGODlhJAAKAIAAALu7u////yH5BAEAAAEALAAAAAAkAAoAAAI0jI+py+2fwAHUSFvD3RlvG4HIp4nX5JFSpnZUJ6LlrM52OE7uSWosBHScgkSZj7dDKnWAAgA7);\n}\n.mce-visualblocks figcaption {\n  border: 1px dashed #bbb;\n}\n.mce-visualblocks hgroup {\n  background-image: url(data:image/gif;base64,R0lGODlhJwAKAIABALu7uwAAACH5BAEAAAEALAAAAAAnAAoAAAI3jI+pywYNI3uB0gpsRtt5fFnfNZaVSYJil4Wo03Hv6Z62uOCgiXH1kZIIJ8NiIxRrAZNMZAtQAAA7);\n}\n.mce-visualblocks aside {\n  background-image: url(data:image/gif;base64,R0lGODlhHgAKAIABAKqqqv///yH5BAEAAAEALAAAAAAeAAoAAAItjI+pG8APjZOTzgtqy7I3f1yehmQcFY4WKZbqByutmW4aHUd6vfcVbgudgpYCADs=);\n}\n.mce-visualblocks ul {\n  background-image: url(data:image/gif;base64,R0lGODlhDQAKAIAAALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGuYnqUVSjvw26DzzXiqIDlVwAAOw==);\n}\n.mce-visualblocks ol {\n  background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybH6HHt0qourxC6CvzXieHyeWQAAOw==);\n}\n.mce-visualblocks dl {\n  background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybEOnmOvUoWznTqeuEjNSCqeGRUAOw==);\n}\n.mce-visualblocks:not([dir=rtl]) p,\n.mce-visualblocks:not([dir=rtl]) h1,\n.mce-visualblocks:not([dir=rtl]) h2,\n.mce-visualblocks:not([dir=rtl]) h3,\n.mce-visualblocks:not([dir=rtl]) h4,\n.mce-visualblocks:not([dir=rtl]) h5,\n.mce-visualblocks:not([dir=rtl]) h6,\n.mce-visualblocks:not([dir=rtl]) div:not([data-mce-bogus]),\n.mce-visualblocks:not([dir=rtl]) section,\n.mce-visualblocks:not([dir=rtl]) article,\n.mce-visualblocks:not([dir=rtl]) blockquote,\n.mce-visualblocks:not([dir=rtl]) address,\n.mce-visualblocks:not([dir=rtl]) pre,\n.mce-visualblocks:not([dir=rtl]) figure,\n.mce-visualblocks:not([dir=rtl]) figcaption,\n.mce-visualblocks:not([dir=rtl]) hgroup,\n.mce-visualblocks:not([dir=rtl]) aside,\n.mce-visualblocks:not([dir=rtl]) ul,\n.mce-visualblocks:not([dir=rtl]) ol,\n.mce-visualblocks:not([dir=rtl]) dl {\n  margin-left: 3px;\n}\n.mce-visualblocks[dir=rtl] p,\n.mce-visualblocks[dir=rtl] h1,\n.mce-visualblocks[dir=rtl] h2,\n.mce-visualblocks[dir=rtl] h3,\n.mce-visualblocks[dir=rtl] h4,\n.mce-visualblocks[dir=rtl] h5,\n.mce-visualblocks[dir=rtl] h6,\n.mce-visualblocks[dir=rtl] div:not([data-mce-bogus]),\n.mce-visualblocks[dir=rtl] section,\n.mce-visualblocks[dir=rtl] article,\n.mce-visualblocks[dir=rtl] blockquote,\n.mce-visualblocks[dir=rtl] address,\n.mce-visualblocks[dir=rtl] pre,\n.mce-visualblocks[dir=rtl] figure,\n.mce-visualblocks[dir=rtl] figcaption,\n.mce-visualblocks[dir=rtl] hgroup,\n.mce-visualblocks[dir=rtl] aside,\n.mce-visualblocks[dir=rtl] ul,\n.mce-visualblocks[dir=rtl] ol,\n.mce-visualblocks[dir=rtl] dl {\n  background-position-x: right;\n  margin-right: 3px;\n}\n.mce-nbsp,\n.mce-shy {\n  background: #aaa;\n}\n.mce-shy::after {\n  content: '-';\n}\n"
  },
  {
    "path": "minimalist-vue3/public/tinymce/skins/ui/tinymce-5/skin.css",
    "content": ".tox {\n  box-shadow: none;\n  box-sizing: content-box;\n  color: #222f3e;\n  cursor: auto;\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n  font-size: 16px;\n  font-style: normal;\n  font-weight: normal;\n  line-height: normal;\n  -webkit-tap-highlight-color: transparent;\n  text-decoration: none;\n  text-shadow: none;\n  text-transform: none;\n  vertical-align: initial;\n  white-space: normal;\n}\n.tox *:not(svg):not(rect) {\n  box-sizing: inherit;\n  color: inherit;\n  cursor: inherit;\n  direction: inherit;\n  font-family: inherit;\n  font-size: inherit;\n  font-style: inherit;\n  font-weight: inherit;\n  line-height: inherit;\n  -webkit-tap-highlight-color: inherit;\n  text-align: inherit;\n  text-decoration: inherit;\n  text-shadow: inherit;\n  text-transform: inherit;\n  vertical-align: inherit;\n  white-space: inherit;\n}\n.tox *:not(svg):not(rect) {\n  /* stylelint-disable-line no-duplicate-selectors */\n  background: transparent;\n  border: 0;\n  box-shadow: none;\n  float: none;\n  height: auto;\n  margin: 0;\n  max-width: none;\n  outline: 0;\n  padding: 0;\n  position: static;\n  width: auto;\n}\n.tox:not([dir=rtl]) {\n  direction: ltr;\n  text-align: left;\n}\n.tox[dir=rtl] {\n  direction: rtl;\n  text-align: right;\n}\n.tox-tinymce {\n  border: 1px solid #cccccc;\n  border-radius: 0;\n  box-shadow: none;\n  box-sizing: border-box;\n  display: flex;\n  flex-direction: column;\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n  overflow: hidden;\n  position: relative;\n  visibility: inherit !important;\n}\n.tox.tox-tinymce-inline {\n  border: none;\n  box-shadow: none;\n  overflow: initial;\n}\n.tox.tox-tinymce-inline .tox-editor-container {\n  overflow: initial;\n}\n.tox.tox-tinymce-inline .tox-editor-header {\n  background-color: #fff;\n  border: 1px solid #cccccc;\n  border-radius: 0;\n  box-shadow: none;\n  overflow: hidden;\n}\n.tox-tinymce-aux {\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n  z-index: 1300;\n}\n.tox-tinymce *:focus,\n.tox-tinymce-aux *:focus {\n  outline: none;\n}\nbutton::-moz-focus-inner {\n  border: 0;\n}\n.tox[dir=rtl] .tox-icon--flip svg {\n  transform: rotateY(180deg);\n}\n.tox .accessibility-issue__header {\n  align-items: center;\n  display: flex;\n  margin-bottom: 4px;\n}\n.tox .accessibility-issue__description {\n  align-items: stretch;\n  border-radius: 3px;\n  display: flex;\n  justify-content: space-between;\n}\n.tox .accessibility-issue__description > div {\n  padding-bottom: 4px;\n}\n.tox .accessibility-issue__description > div > div {\n  align-items: center;\n  display: flex;\n  margin-bottom: 4px;\n}\n.tox .accessibility-issue__description > div > div .tox-icon svg {\n  display: block;\n}\n.tox .accessibility-issue__repair {\n  margin-top: 16px;\n}\n.tox .tox-dialog__body-content .accessibility-issue--info .accessibility-issue__description {\n  background-color: rgba(30, 113, 170, 0.1);\n  color: #222f3e;\n}\n.tox .tox-dialog__body-content .accessibility-issue--info .tox-form__group h2 {\n  color: #207ab7;\n}\n.tox .tox-dialog__body-content .accessibility-issue--info .tox-icon svg {\n  fill: #207ab7;\n}\n.tox .tox-dialog__body-content .accessibility-issue--info a.tox-button--naked.tox-button--icon {\n  background-color: #207ab7;\n  color: #fff;\n}\n.tox .tox-dialog__body-content .accessibility-issue--info a.tox-button--naked.tox-button--icon:hover,\n.tox .tox-dialog__body-content .accessibility-issue--info a.tox-button--naked.tox-button--icon:focus {\n  background-color: #1c6ca1;\n}\n.tox .tox-dialog__body-content .accessibility-issue--info a.tox-button--naked.tox-button--icon:active {\n  background-color: #185d8c;\n}\n.tox .tox-dialog__body-content .accessibility-issue--warn {\n  /* stylelint-disable-next-line no-descending-specificity */\n}\n.tox .tox-dialog__body-content .accessibility-issue--warn .accessibility-issue__description {\n  background-color: rgba(255, 165, 0, 0.08);\n  color: #222f3e;\n}\n.tox .tox-dialog__body-content .accessibility-issue--warn .tox-form__group h2 {\n  color: #8f5d00;\n}\n.tox .tox-dialog__body-content .accessibility-issue--warn .tox-icon svg {\n  fill: #8f5d00;\n}\n.tox .tox-dialog__body-content .accessibility-issue--warn a.tox-button--naked.tox-button--icon {\n  background-color: #FFE89D;\n  color: #222f3e;\n}\n.tox .tox-dialog__body-content .accessibility-issue--warn a.tox-button--naked.tox-button--icon:hover,\n.tox .tox-dialog__body-content .accessibility-issue--warn a.tox-button--naked.tox-button--icon:focus {\n  background-color: #F2D574;\n  color: #222f3e;\n}\n.tox .tox-dialog__body-content .accessibility-issue--warn a.tox-button--naked.tox-button--icon:active {\n  background-color: #E8C657;\n  color: #222f3e;\n}\n.tox .tox-dialog__body-content .accessibility-issue--error {\n  /* stylelint-disable-next-line no-descending-specificity */\n}\n.tox .tox-dialog__body-content .accessibility-issue--error .accessibility-issue__description {\n  background-color: rgba(204, 0, 0, 0.1);\n  color: #222f3e;\n}\n.tox .tox-dialog__body-content .accessibility-issue--error .tox-form__group h2 {\n  color: #c00;\n}\n.tox .tox-dialog__body-content .accessibility-issue--error .tox-icon svg {\n  fill: #c00;\n}\n.tox .tox-dialog__body-content .accessibility-issue--error a.tox-button--naked.tox-button--icon {\n  background-color: #F2BFBF;\n  color: #222f3e;\n}\n.tox .tox-dialog__body-content .accessibility-issue--error a.tox-button--naked.tox-button--icon:hover,\n.tox .tox-dialog__body-content .accessibility-issue--error a.tox-button--naked.tox-button--icon:focus {\n  background-color: #E9A4A4;\n  color: #222f3e;\n}\n.tox .tox-dialog__body-content .accessibility-issue--error a.tox-button--naked.tox-button--icon:active {\n  background-color: #EE9494;\n  color: #222f3e;\n}\n.tox .tox-dialog__body-content .accessibility-issue--success .accessibility-issue__description {\n  background-color: rgba(120, 171, 70, 0.1);\n  color: #222f3e;\n}\n.tox .tox-dialog__body-content .accessibility-issue--success .accessibility-issue__description > *:last-child {\n  display: none;\n}\n.tox .tox-dialog__body-content .accessibility-issue--success .tox-form__group h2 {\n  color: #527530;\n}\n.tox .tox-dialog__body-content .accessibility-issue--success .tox-icon svg {\n  fill: #527530;\n}\n.tox .tox-dialog__body-content .accessibility-issue__header .tox-form__group h1,\n.tox .tox-dialog__body-content .tox-form__group .accessibility-issue__description h2 {\n  font-size: 14px;\n  margin-top: 0;\n}\n.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__header .tox-button {\n  margin-left: 4px;\n}\n.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__header > *:nth-last-child(2) {\n  margin-left: auto;\n}\n.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__description {\n  padding: 4px 4px 4px 8px;\n}\n.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__header .tox-button {\n  margin-right: 4px;\n}\n.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__header > *:nth-last-child(2) {\n  margin-right: auto;\n}\n.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__description {\n  padding: 4px 8px 4px 4px;\n}\n.tox .tox-advtemplate .tox-form__grid {\n  flex: 1;\n}\n.tox .tox-advtemplate .tox-form__grid > div:first-child {\n  display: flex;\n  flex-direction: column;\n  width: 30%;\n}\n.tox .tox-advtemplate .tox-form__grid > div:first-child > div:nth-child(2) {\n  flex-basis: 0;\n  flex-grow: 1;\n  overflow: auto;\n}\n@media only screen and (max-width: 767px ) {\n  body:not(.tox-force-desktop) .tox .tox-advtemplate .tox-form__grid > div:first-child {\n    width: 100%;\n  }\n}\n.tox .tox-advtemplate iframe {\n  border-color: #cccccc;\n  border-radius: 0;\n  border-style: solid;\n  border-width: 1px;\n  margin: 0 10px;\n}\n.tox .tox-anchorbar {\n  display: flex;\n  flex: 0 0 auto;\n}\n.tox .tox-bottom-anchorbar {\n  display: flex;\n  flex: 0 0 auto;\n}\n.tox .tox-bar {\n  display: flex;\n  flex: 0 0 auto;\n}\n.tox .tox-button {\n  background-color: #207ab7;\n  background-image: none;\n  background-position: 0 0;\n  background-repeat: repeat;\n  border-color: #207ab7;\n  border-radius: 3px;\n  border-style: solid;\n  border-width: 1px;\n  box-shadow: none;\n  box-sizing: border-box;\n  color: #fff;\n  cursor: pointer;\n  display: inline-block;\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: bold;\n  letter-spacing: normal;\n  line-height: 24px;\n  margin: 0;\n  outline: none;\n  padding: 4px 16px;\n  position: relative;\n  text-align: center;\n  text-decoration: none;\n  text-transform: none;\n  white-space: nowrap;\n}\n.tox .tox-button::before {\n  border-radius: 3px;\n  bottom: -1px;\n  box-shadow: inset 0 0 0 2px #fff, 0 0 0 1px #207ab7, 0 0 0 3px rgba(32, 122, 183, 0.25);\n  content: '';\n  left: -1px;\n  opacity: 0;\n  pointer-events: none;\n  position: absolute;\n  right: -1px;\n  top: -1px;\n}\n.tox .tox-button[disabled] {\n  background-color: #207ab7;\n  background-image: none;\n  border-color: #207ab7;\n  box-shadow: none;\n  color: rgba(255, 255, 255, 0.5);\n  cursor: not-allowed;\n}\n.tox .tox-button:focus:not(:disabled) {\n  background-color: #1c6ca1;\n  background-image: none;\n  border-color: #1c6ca1;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button:focus-visible:not(:disabled)::before {\n  opacity: 1;\n}\n.tox .tox-button:hover:not(:disabled) {\n  background-color: #1c6ca1;\n  background-image: none;\n  border-color: #1c6ca1;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button:active:not(:disabled) {\n  background-color: #185d8c;\n  background-image: none;\n  border-color: #185d8c;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button.tox-button--enabled {\n  background-color: #185d8c;\n  background-image: none;\n  border-color: #185d8c;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button.tox-button--enabled[disabled] {\n  background-color: #185d8c;\n  background-image: none;\n  border-color: #185d8c;\n  box-shadow: none;\n  color: rgba(255, 255, 255, 0.5);\n  cursor: not-allowed;\n}\n.tox .tox-button.tox-button--enabled:focus:not(:disabled) {\n  background-color: #154f76;\n  background-image: none;\n  border-color: #154f76;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button.tox-button--enabled:hover:not(:disabled) {\n  background-color: #154f76;\n  background-image: none;\n  border-color: #154f76;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button.tox-button--enabled:active:not(:disabled) {\n  background-color: #114060;\n  background-image: none;\n  border-color: #114060;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button--icon-and-text,\n.tox .tox-button.tox-button--icon-and-text,\n.tox .tox-button.tox-button--secondary.tox-button--icon-and-text {\n  display: flex;\n  padding: 5px 4px;\n}\n.tox .tox-button--icon-and-text .tox-icon svg,\n.tox .tox-button.tox-button--icon-and-text .tox-icon svg,\n.tox .tox-button.tox-button--secondary.tox-button--icon-and-text .tox-icon svg {\n  display: block;\n  fill: currentColor;\n}\n.tox .tox-button--secondary {\n  background-color: #f0f0f0;\n  background-image: none;\n  background-position: 0 0;\n  background-repeat: repeat;\n  border-color: #f0f0f0;\n  border-radius: 3px;\n  border-style: solid;\n  border-width: 1px;\n  box-shadow: none;\n  color: #222f3e;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: bold;\n  letter-spacing: normal;\n  outline: none;\n  padding: 4px 16px;\n  text-decoration: none;\n  text-transform: none;\n}\n.tox .tox-button--secondary[disabled] {\n  background-color: #f0f0f0;\n  background-image: none;\n  border-color: #f0f0f0;\n  box-shadow: none;\n  color: rgba(34, 47, 62, 0.5);\n}\n.tox .tox-button--secondary:focus:not(:disabled) {\n  background-color: #e3e3e3;\n  background-image: none;\n  border-color: #e3e3e3;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-button--secondary:hover:not(:disabled) {\n  background-color: #e3e3e3;\n  background-image: none;\n  border-color: #e3e3e3;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-button--secondary:active:not(:disabled) {\n  background-color: #d6d6d6;\n  background-image: none;\n  border-color: #d6d6d6;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-button--secondary.tox-button--enabled {\n  background-color: #b1ccdf;\n  background-image: none;\n  border-color: #b1ccdf;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-button--secondary.tox-button--enabled[disabled] {\n  background-color: #b1ccdf;\n  background-image: none;\n  border-color: #b1ccdf;\n  box-shadow: none;\n  color: rgba(34, 47, 62, 0.5);\n}\n.tox .tox-button--secondary.tox-button--enabled:focus:not(:disabled) {\n  background-color: #9fc1d7;\n  background-image: none;\n  border-color: #9fc1d7;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-button--secondary.tox-button--enabled:hover:not(:disabled) {\n  background-color: #9fc1d7;\n  background-image: none;\n  border-color: #9fc1d7;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-button--secondary.tox-button--enabled:active:not(:disabled) {\n  background-color: #8db5d0;\n  background-image: none;\n  border-color: #8db5d0;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-button--icon,\n.tox .tox-button.tox-button--icon,\n.tox .tox-button.tox-button--secondary.tox-button--icon {\n  padding: 4px;\n}\n.tox .tox-button--icon .tox-icon svg,\n.tox .tox-button.tox-button--icon .tox-icon svg,\n.tox .tox-button.tox-button--secondary.tox-button--icon .tox-icon svg {\n  display: block;\n  fill: currentColor;\n}\n.tox .tox-button-link {\n  background: 0;\n  border: none;\n  box-sizing: border-box;\n  cursor: pointer;\n  display: inline-block;\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n  font-size: 16px;\n  font-weight: normal;\n  line-height: 1.3;\n  margin: 0;\n  padding: 0;\n  white-space: nowrap;\n}\n.tox .tox-button-link--sm {\n  font-size: 14px;\n}\n.tox .tox-button--naked {\n  background-color: transparent;\n  border-color: transparent;\n  box-shadow: unset;\n  color: #222f3e;\n}\n.tox .tox-button--naked[disabled] {\n  background-color: #f0f0f0;\n  border-color: #f0f0f0;\n  box-shadow: none;\n  color: rgba(34, 47, 62, 0.5);\n}\n.tox .tox-button--naked:hover:not(:disabled) {\n  background-color: #e3e3e3;\n  border-color: #e3e3e3;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-button--naked:focus:not(:disabled) {\n  background-color: #e3e3e3;\n  border-color: #e3e3e3;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-button--naked:active:not(:disabled) {\n  background-color: #d6d6d6;\n  border-color: #d6d6d6;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-button--naked .tox-icon svg {\n  fill: currentColor;\n}\n.tox .tox-button--naked.tox-button--icon:hover:not(:disabled) {\n  color: #222f3e;\n}\n.tox .tox-checkbox {\n  align-items: center;\n  border-radius: 3px;\n  cursor: pointer;\n  display: flex;\n  height: 36px;\n  min-width: 36px;\n}\n.tox .tox-checkbox__input {\n  /* Hide from view but visible to screen readers */\n  height: 1px;\n  overflow: hidden;\n  position: absolute;\n  top: auto;\n  width: 1px;\n}\n.tox .tox-checkbox__icons {\n  align-items: center;\n  border-radius: 3px;\n  box-shadow: 0 0 0 2px transparent;\n  box-sizing: content-box;\n  display: flex;\n  height: 24px;\n  justify-content: center;\n  padding: calc(4px - 1px);\n  width: 24px;\n}\n.tox .tox-checkbox__icons .tox-checkbox-icon__unchecked svg {\n  display: block;\n  fill: rgba(34, 47, 62, 0.3);\n}\n.tox .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg {\n  display: none;\n  fill: #207ab7;\n}\n.tox .tox-checkbox__icons .tox-checkbox-icon__checked svg {\n  display: none;\n  fill: #207ab7;\n}\n.tox .tox-checkbox--disabled {\n  color: rgba(34, 47, 62, 0.5);\n  cursor: not-allowed;\n}\n.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__checked svg {\n  fill: rgba(34, 47, 62, 0.5);\n}\n.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__unchecked svg {\n  fill: rgba(34, 47, 62, 0.5);\n}\n.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg {\n  fill: rgba(34, 47, 62, 0.5);\n}\n.tox input.tox-checkbox__input:checked + .tox-checkbox__icons .tox-checkbox-icon__unchecked svg {\n  display: none;\n}\n.tox input.tox-checkbox__input:checked + .tox-checkbox__icons .tox-checkbox-icon__checked svg {\n  display: block;\n}\n.tox input.tox-checkbox__input:indeterminate + .tox-checkbox__icons .tox-checkbox-icon__unchecked svg {\n  display: none;\n}\n.tox input.tox-checkbox__input:indeterminate + .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg {\n  display: block;\n}\n.tox input.tox-checkbox__input:focus + .tox-checkbox__icons {\n  border-radius: 3px;\n  box-shadow: inset 0 0 0 1px #207ab7;\n  padding: calc(4px - 1px);\n}\n.tox:not([dir=rtl]) .tox-checkbox__label {\n  margin-left: 4px;\n}\n.tox:not([dir=rtl]) .tox-checkbox__input {\n  left: -10000px;\n}\n.tox:not([dir=rtl]) .tox-bar .tox-checkbox {\n  margin-left: 4px;\n}\n.tox[dir=rtl] .tox-checkbox__label {\n  margin-right: 4px;\n}\n.tox[dir=rtl] .tox-checkbox__input {\n  right: -10000px;\n}\n.tox[dir=rtl] .tox-bar .tox-checkbox {\n  margin-right: 4px;\n}\n.tox {\n  /* stylelint-disable-next-line no-descending-specificity */\n}\n.tox .tox-collection--toolbar .tox-collection__group {\n  display: flex;\n  padding: 0;\n}\n.tox .tox-collection--grid .tox-collection__group {\n  display: flex;\n  flex-wrap: wrap;\n  max-height: 208px;\n  overflow-x: hidden;\n  overflow-y: auto;\n  padding: 0;\n}\n.tox .tox-collection--list .tox-collection__group {\n  border-bottom-width: 0;\n  border-color: #cccccc;\n  border-left-width: 0;\n  border-right-width: 0;\n  border-style: solid;\n  border-top-width: 1px;\n  padding: 4px 0;\n}\n.tox .tox-collection--list .tox-collection__group:first-child {\n  border-top-width: 0;\n}\n.tox .tox-collection__group-heading {\n  background-color: #e6e6e6;\n  color: rgba(34, 47, 62, 0.7);\n  cursor: default;\n  font-size: 12px;\n  font-style: normal;\n  font-weight: normal;\n  margin-bottom: 4px;\n  margin-top: -4px;\n  padding: 4px 8px;\n  text-transform: none;\n  -webkit-touch-callout: none;\n  -webkit-user-select: none;\n     -moz-user-select: none;\n          user-select: none;\n}\n.tox .tox-collection__item {\n  align-items: center;\n  border-radius: 3px;\n  color: #222f3e;\n  display: flex;\n  -webkit-touch-callout: none;\n  -webkit-user-select: none;\n     -moz-user-select: none;\n          user-select: none;\n}\n.tox .tox-collection--list .tox-collection__item {\n  padding: 4px 8px;\n}\n.tox .tox-collection--toolbar .tox-collection__item {\n  border-radius: 3px;\n  padding: 4px;\n}\n.tox .tox-collection--grid .tox-collection__item {\n  border-radius: 3px;\n  padding: 4px;\n}\n.tox .tox-collection--list .tox-collection__item--enabled {\n  background-color: #fff;\n  color: #222f3e;\n}\n.tox .tox-collection--list .tox-collection__item--active {\n  background-color: #dee0e2;\n}\n.tox .tox-collection--toolbar .tox-collection__item--enabled {\n  background-color: #c8cbcf;\n  color: #222f3e;\n}\n.tox .tox-collection--toolbar .tox-collection__item--active {\n  background-color: #dee0e2;\n}\n.tox .tox-collection--grid .tox-collection__item--enabled {\n  background-color: #c8cbcf;\n  color: #222f3e;\n}\n.tox .tox-collection--grid .tox-collection__item--active:not(.tox-collection__item--state-disabled) {\n  background-color: #dee0e2;\n  color: #222f3e;\n}\n.tox .tox-collection--list .tox-collection__item--active:not(.tox-collection__item--state-disabled) {\n  color: #222f3e;\n}\n.tox .tox-collection--toolbar .tox-collection__item--active:not(.tox-collection__item--state-disabled) {\n  color: #222f3e;\n}\n.tox .tox-collection__item-icon,\n.tox .tox-collection__item-checkmark {\n  align-items: center;\n  display: flex;\n  height: 24px;\n  justify-content: center;\n  width: 24px;\n}\n.tox .tox-collection__item-icon svg,\n.tox .tox-collection__item-checkmark svg {\n  fill: currentColor;\n}\n.tox .tox-collection--toolbar-lg .tox-collection__item-icon {\n  height: 48px;\n  width: 48px;\n}\n.tox .tox-collection__item-label {\n  color: currentColor;\n  display: inline-block;\n  flex: 1;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: normal;\n  line-height: 24px;\n  text-transform: none;\n  word-break: break-all;\n}\n.tox .tox-collection__item-accessory {\n  color: rgba(34, 47, 62, 0.7);\n  display: inline-block;\n  font-size: 14px;\n  height: 24px;\n  line-height: 24px;\n  text-transform: none;\n}\n.tox .tox-collection__item-caret {\n  align-items: center;\n  display: flex;\n  min-height: 24px;\n}\n.tox .tox-collection__item-caret::after {\n  content: '';\n  font-size: 0;\n  min-height: inherit;\n}\n.tox .tox-collection__item-caret svg {\n  fill: #222f3e;\n}\n.tox .tox-collection__item--state-disabled {\n  background-color: transparent;\n  color: rgba(34, 47, 62, 0.5);\n  cursor: not-allowed;\n}\n.tox .tox-collection__item--state-disabled .tox-collection__item-caret svg {\n  fill: rgba(34, 47, 62, 0.5);\n}\n.tox .tox-collection--list .tox-collection__item:not(.tox-collection__item--enabled) .tox-collection__item-checkmark svg {\n  display: none;\n}\n.tox .tox-collection--list .tox-collection__item:not(.tox-collection__item--enabled) .tox-collection__item-accessory + .tox-collection__item-checkmark {\n  display: none;\n}\n.tox .tox-collection--horizontal {\n  background-color: #fff;\n  border: 1px solid #cccccc;\n  border-radius: 3px;\n  box-shadow: 0 0 2px 0 rgba(34, 47, 62, 0.2), 0 4px 8px 0 rgba(34, 47, 62, 0.15);\n  display: flex;\n  flex: 0 0 auto;\n  flex-shrink: 0;\n  flex-wrap: nowrap;\n  margin-bottom: 0;\n  overflow-x: auto;\n  padding: 0;\n}\n.tox .tox-collection--horizontal .tox-collection__group {\n  align-items: center;\n  display: flex;\n  flex-wrap: nowrap;\n  margin: 0;\n  padding: 0 4px;\n}\n.tox .tox-collection--horizontal .tox-collection__item {\n  height: 34px;\n  margin: 3px 0 2px 0;\n  padding: 0 4px;\n}\n.tox .tox-collection--horizontal .tox-collection__item-label {\n  white-space: nowrap;\n}\n.tox .tox-collection--horizontal .tox-collection__item-caret {\n  margin-left: 4px;\n}\n.tox .tox-collection__item-container {\n  display: flex;\n}\n.tox .tox-collection__item-container--row {\n  align-items: center;\n  flex: 1 1 auto;\n  flex-direction: row;\n}\n.tox .tox-collection__item-container--row.tox-collection__item-container--align-left {\n  margin-right: auto;\n}\n.tox .tox-collection__item-container--row.tox-collection__item-container--align-right {\n  justify-content: flex-end;\n  margin-left: auto;\n}\n.tox .tox-collection__item-container--row.tox-collection__item-container--valign-top {\n  align-items: flex-start;\n  margin-bottom: auto;\n}\n.tox .tox-collection__item-container--row.tox-collection__item-container--valign-middle {\n  align-items: center;\n}\n.tox .tox-collection__item-container--row.tox-collection__item-container--valign-bottom {\n  align-items: flex-end;\n  margin-top: auto;\n}\n.tox .tox-collection__item-container--column {\n  align-self: center;\n  flex: 1 1 auto;\n  flex-direction: column;\n}\n.tox .tox-collection__item-container--column.tox-collection__item-container--align-left {\n  align-items: flex-start;\n}\n.tox .tox-collection__item-container--column.tox-collection__item-container--align-right {\n  align-items: flex-end;\n}\n.tox .tox-collection__item-container--column.tox-collection__item-container--valign-top {\n  align-self: flex-start;\n}\n.tox .tox-collection__item-container--column.tox-collection__item-container--valign-middle {\n  align-self: center;\n}\n.tox .tox-collection__item-container--column.tox-collection__item-container--valign-bottom {\n  align-self: flex-end;\n}\n.tox:not([dir=rtl]) .tox-collection--horizontal .tox-collection__group:not(:last-of-type) {\n  border-right: 1px solid #cccccc;\n}\n.tox:not([dir=rtl]) .tox-collection--list .tox-collection__item > *:not(:first-child) {\n  margin-left: 8px;\n}\n.tox:not([dir=rtl]) .tox-collection--list .tox-collection__item > .tox-collection__item-label:first-child {\n  margin-left: 4px;\n}\n.tox:not([dir=rtl]) .tox-collection__item-accessory {\n  margin-left: 16px;\n  text-align: right;\n}\n.tox:not([dir=rtl]) .tox-collection .tox-collection__item-caret {\n  margin-left: 16px;\n}\n.tox[dir=rtl] .tox-collection--horizontal .tox-collection__group:not(:last-of-type) {\n  border-left: 1px solid #cccccc;\n}\n.tox[dir=rtl] .tox-collection--list .tox-collection__item > *:not(:first-child) {\n  margin-right: 8px;\n}\n.tox[dir=rtl] .tox-collection--list .tox-collection__item > .tox-collection__item-label:first-child {\n  margin-right: 4px;\n}\n.tox[dir=rtl] .tox-collection__item-accessory {\n  margin-right: 16px;\n  text-align: left;\n}\n.tox[dir=rtl] .tox-collection .tox-collection__item-caret {\n  margin-right: 16px;\n  transform: rotateY(180deg);\n}\n.tox[dir=rtl] .tox-collection--horizontal .tox-collection__item-caret {\n  margin-right: 4px;\n}\n.tox .tox-color-picker-container {\n  display: flex;\n  flex-direction: row;\n  height: 225px;\n  margin: 0;\n}\n.tox .tox-sv-palette {\n  box-sizing: border-box;\n  display: flex;\n  height: 100%;\n}\n.tox .tox-sv-palette-spectrum {\n  height: 100%;\n}\n.tox .tox-sv-palette,\n.tox .tox-sv-palette-spectrum {\n  width: 225px;\n}\n.tox .tox-sv-palette-thumb {\n  background: none;\n  border: 1px solid black;\n  border-radius: 50%;\n  box-sizing: content-box;\n  height: 12px;\n  position: absolute;\n  width: 12px;\n}\n.tox .tox-sv-palette-inner-thumb {\n  border: 1px solid white;\n  border-radius: 50%;\n  height: 10px;\n  position: absolute;\n  width: 10px;\n}\n.tox .tox-hue-slider {\n  box-sizing: border-box;\n  height: 100%;\n  width: 25px;\n}\n.tox .tox-hue-slider-spectrum {\n  background: linear-gradient(to bottom, #f00, #ff0080, #f0f, #8000ff, #00f, #0080ff, #0ff, #00ff80, #0f0, #80ff00, #ff0, #ff8000, #f00);\n  height: 100%;\n  width: 100%;\n}\n.tox .tox-hue-slider,\n.tox .tox-hue-slider-spectrum {\n  width: 20px;\n}\n.tox .tox-hue-slider-thumb {\n  background: white;\n  border: 1px solid black;\n  box-sizing: content-box;\n  height: 4px;\n  width: 100%;\n}\n.tox .tox-rgb-form {\n  display: flex;\n  flex-direction: column;\n  justify-content: space-between;\n}\n.tox .tox-rgb-form div {\n  align-items: center;\n  display: flex;\n  justify-content: space-between;\n  margin-bottom: 5px;\n  width: inherit;\n}\n.tox .tox-rgb-form input {\n  width: 6em;\n}\n.tox .tox-rgb-form input.tox-invalid {\n  /* Need !important to override Chrome's focus styling unfortunately */\n  border: 1px solid red !important;\n}\n.tox .tox-rgb-form .tox-rgba-preview {\n  border: 1px solid black;\n  flex-grow: 2;\n  margin-bottom: 0;\n}\n.tox:not([dir=rtl]) .tox-sv-palette {\n  margin-right: 15px;\n}\n.tox:not([dir=rtl]) .tox-hue-slider {\n  margin-right: 15px;\n}\n.tox:not([dir=rtl]) .tox-hue-slider-thumb {\n  margin-left: -1px;\n}\n.tox:not([dir=rtl]) .tox-rgb-form label {\n  margin-right: 0.5em;\n}\n.tox[dir=rtl] .tox-sv-palette {\n  margin-left: 15px;\n}\n.tox[dir=rtl] .tox-hue-slider {\n  margin-left: 15px;\n}\n.tox[dir=rtl] .tox-hue-slider-thumb {\n  margin-right: -1px;\n}\n.tox[dir=rtl] .tox-rgb-form label {\n  margin-left: 0.5em;\n}\n.tox .tox-toolbar .tox-swatches,\n.tox .tox-toolbar__primary .tox-swatches,\n.tox .tox-toolbar__overflow .tox-swatches {\n  margin: 2px 0 3px 4px;\n}\n.tox .tox-collection--list .tox-collection__group .tox-swatches-menu {\n  border: 0;\n  margin: -4px 0;\n}\n.tox .tox-swatches__row {\n  display: flex;\n}\n.tox .tox-swatch {\n  height: 30px;\n  transition: transform 0.15s, box-shadow 0.15s;\n  width: 30px;\n}\n.tox .tox-swatch:hover,\n.tox .tox-swatch:focus {\n  box-shadow: 0 0 0 1px rgba(127, 127, 127, 0.3) inset;\n  transform: scale(0.8);\n}\n.tox .tox-swatch--remove {\n  align-items: center;\n  display: flex;\n  justify-content: center;\n}\n.tox .tox-swatch--remove svg path {\n  stroke: #e74c3c;\n}\n.tox .tox-swatches__picker-btn {\n  align-items: center;\n  background-color: transparent;\n  border: 0;\n  cursor: pointer;\n  display: flex;\n  height: 30px;\n  justify-content: center;\n  outline: none;\n  padding: 0;\n  width: 30px;\n}\n.tox .tox-swatches__picker-btn svg {\n  fill: #222f3e;\n  height: 24px;\n  width: 24px;\n}\n.tox .tox-swatches__picker-btn:hover {\n  background: #dee0e2;\n}\n.tox div.tox-swatch:not(.tox-swatch--remove) svg {\n  display: none;\n  fill: #222f3e;\n  height: 24px;\n  margin: calc((30px - 24px) / 2) calc((30px - 24px) / 2);\n  width: 24px;\n}\n.tox div.tox-swatch:not(.tox-swatch--remove) svg path {\n  fill: #fff;\n  paint-order: stroke;\n  stroke: #222f3e;\n  stroke-width: 2px;\n}\n.tox div.tox-swatch:not(.tox-swatch--remove).tox-collection__item--enabled svg {\n  display: block;\n}\n.tox:not([dir=rtl]) .tox-swatches__picker-btn {\n  margin-left: auto;\n}\n.tox[dir=rtl] .tox-swatches__picker-btn {\n  margin-right: auto;\n}\n.tox .tox-comment-thread {\n  background: #fff;\n  position: relative;\n}\n.tox .tox-comment-thread > *:not(:first-child) {\n  margin-top: 8px;\n}\n.tox .tox-comment {\n  background: #fff;\n  border: 1px solid #cccccc;\n  border-radius: 3px;\n  box-shadow: 0 4px 8px 0 rgba(34, 47, 62, 0.1);\n  padding: 8px 8px 16px 8px;\n  position: relative;\n}\n.tox .tox-comment__header {\n  align-items: center;\n  color: #222f3e;\n  display: flex;\n  justify-content: space-between;\n}\n.tox .tox-comment__date {\n  color: #222f3e;\n  font-size: 12px;\n  line-height: 18px;\n}\n.tox .tox-comment__body {\n  color: #222f3e;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: normal;\n  line-height: 1.3;\n  margin-top: 8px;\n  position: relative;\n  text-transform: initial;\n}\n.tox .tox-comment__body textarea {\n  resize: none;\n  white-space: normal;\n  width: 100%;\n}\n.tox .tox-comment__expander {\n  padding-top: 8px;\n}\n.tox .tox-comment__expander p {\n  color: rgba(34, 47, 62, 0.7);\n  font-size: 14px;\n  font-style: normal;\n}\n.tox .tox-comment__body p {\n  margin: 0;\n}\n.tox .tox-comment__buttonspacing {\n  padding-top: 16px;\n  text-align: center;\n}\n.tox .tox-comment-thread__overlay::after {\n  background: #fff;\n  bottom: 0;\n  content: \"\";\n  display: flex;\n  left: 0;\n  opacity: 0.9;\n  position: absolute;\n  right: 0;\n  top: 0;\n  z-index: 5;\n}\n.tox .tox-comment__reply {\n  display: flex;\n  flex-shrink: 0;\n  flex-wrap: wrap;\n  justify-content: flex-end;\n  margin-top: 8px;\n}\n.tox .tox-comment__reply > *:first-child {\n  margin-bottom: 8px;\n  width: 100%;\n}\n.tox .tox-comment__edit {\n  display: flex;\n  flex-wrap: wrap;\n  justify-content: flex-end;\n  margin-top: 16px;\n}\n.tox .tox-comment__gradient::after {\n  background: linear-gradient(rgba(255, 255, 255, 0), #fff);\n  bottom: 0;\n  content: \"\";\n  display: block;\n  height: 5em;\n  margin-top: -40px;\n  position: absolute;\n  width: 100%;\n}\n.tox .tox-comment__overlay {\n  background: #fff;\n  bottom: 0;\n  display: flex;\n  flex-direction: column;\n  flex-grow: 1;\n  left: 0;\n  opacity: 0.9;\n  position: absolute;\n  right: 0;\n  text-align: center;\n  top: 0;\n  z-index: 5;\n}\n.tox .tox-comment__loading-text {\n  align-items: center;\n  color: #222f3e;\n  display: flex;\n  flex-direction: column;\n  position: relative;\n}\n.tox .tox-comment__loading-text > div {\n  padding-bottom: 16px;\n}\n.tox .tox-comment__overlaytext {\n  bottom: 0;\n  flex-direction: column;\n  font-size: 14px;\n  left: 0;\n  padding: 1em;\n  position: absolute;\n  right: 0;\n  top: 0;\n  z-index: 10;\n}\n.tox .tox-comment__overlaytext p {\n  background-color: #fff;\n  box-shadow: 0 0 8px 8px #fff;\n  color: #222f3e;\n  text-align: center;\n}\n.tox .tox-comment__overlaytext div:nth-of-type(2) {\n  font-size: 0.8em;\n}\n.tox .tox-comment__busy-spinner {\n  align-items: center;\n  background-color: #fff;\n  bottom: 0;\n  display: flex;\n  justify-content: center;\n  left: 0;\n  position: absolute;\n  right: 0;\n  top: 0;\n  z-index: 20;\n}\n.tox .tox-comment__scroll {\n  display: flex;\n  flex-direction: column;\n  flex-shrink: 1;\n  overflow: auto;\n}\n.tox .tox-conversations {\n  margin: 8px;\n}\n.tox:not([dir=rtl]) .tox-comment__edit {\n  margin-left: 8px;\n}\n.tox:not([dir=rtl]) .tox-comment__buttonspacing > *:last-child,\n.tox:not([dir=rtl]) .tox-comment__edit > *:last-child,\n.tox:not([dir=rtl]) .tox-comment__reply > *:last-child {\n  margin-left: 8px;\n}\n.tox[dir=rtl] .tox-comment__edit {\n  margin-right: 8px;\n}\n.tox[dir=rtl] .tox-comment__buttonspacing > *:last-child,\n.tox[dir=rtl] .tox-comment__edit > *:last-child,\n.tox[dir=rtl] .tox-comment__reply > *:last-child {\n  margin-right: 8px;\n}\n.tox .tox-user {\n  align-items: center;\n  display: flex;\n}\n.tox .tox-user__avatar svg {\n  fill: rgba(34, 47, 62, 0.7);\n}\n.tox .tox-user__avatar img {\n  border-radius: 50%;\n  height: 36px;\n  object-fit: cover;\n  vertical-align: middle;\n  width: 36px;\n}\n.tox .tox-user__name {\n  color: #222f3e;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: bold;\n  line-height: 18px;\n  text-transform: none;\n}\n.tox:not([dir=rtl]) .tox-user__avatar svg,\n.tox:not([dir=rtl]) .tox-user__avatar img {\n  margin-right: 8px;\n}\n.tox:not([dir=rtl]) .tox-user__avatar + .tox-user__name {\n  margin-left: 8px;\n}\n.tox[dir=rtl] .tox-user__avatar svg,\n.tox[dir=rtl] .tox-user__avatar img {\n  margin-left: 8px;\n}\n.tox[dir=rtl] .tox-user__avatar + .tox-user__name {\n  margin-right: 8px;\n}\n.tox .tox-dialog-wrap {\n  align-items: center;\n  bottom: 0;\n  display: flex;\n  justify-content: center;\n  left: 0;\n  position: fixed;\n  right: 0;\n  top: 0;\n  z-index: 1100;\n}\n.tox .tox-dialog-wrap__backdrop {\n  background-color: rgba(255, 255, 255, 0.75);\n  bottom: 0;\n  left: 0;\n  position: absolute;\n  right: 0;\n  top: 0;\n  z-index: 1;\n}\n.tox .tox-dialog-wrap__backdrop--opaque {\n  background-color: #fff;\n}\n.tox .tox-dialog {\n  background-color: #fff;\n  border-color: #cccccc;\n  border-radius: 3px;\n  border-style: solid;\n  border-width: 1px;\n  box-shadow: 0 16px 16px -10px rgba(34, 47, 62, 0.15), 0 0 40px 1px rgba(34, 47, 62, 0.15);\n  display: flex;\n  flex-direction: column;\n  max-height: 100%;\n  max-width: 480px;\n  overflow: hidden;\n  position: relative;\n  width: 95vw;\n  z-index: 2;\n}\n@media only screen and (max-width: 767px ) {\n  body:not(.tox-force-desktop) .tox .tox-dialog {\n    align-self: flex-start;\n    margin: 8px auto;\n    max-height: calc(100vh - 8px * 2);\n    width: calc(100vw - 16px);\n  }\n}\n.tox .tox-dialog-inline {\n  z-index: 1100;\n}\n.tox .tox-dialog__header {\n  align-items: center;\n  background-color: #fff;\n  border-bottom: none;\n  color: #222f3e;\n  display: flex;\n  font-size: 16px;\n  justify-content: space-between;\n  padding: 8px 16px 0 16px;\n  position: relative;\n}\n.tox .tox-dialog__header .tox-button {\n  z-index: 1;\n}\n.tox .tox-dialog__draghandle {\n  cursor: grab;\n  height: 100%;\n  left: 0;\n  position: absolute;\n  top: 0;\n  width: 100%;\n}\n.tox .tox-dialog__draghandle:active {\n  cursor: grabbing;\n}\n.tox .tox-dialog__dismiss {\n  margin-left: auto;\n}\n.tox .tox-dialog__title {\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n  font-size: 20px;\n  font-style: normal;\n  font-weight: normal;\n  line-height: 1.3;\n  margin: 0;\n  text-transform: none;\n}\n.tox .tox-dialog__body {\n  color: #222f3e;\n  display: flex;\n  flex: 1;\n  font-size: 16px;\n  font-style: normal;\n  font-weight: normal;\n  line-height: 1.3;\n  min-width: 0;\n  text-align: left;\n  text-transform: none;\n}\n@media only screen and (max-width: 767px ) {\n  body:not(.tox-force-desktop) .tox .tox-dialog__body {\n    flex-direction: column;\n  }\n}\n.tox .tox-dialog__body-nav {\n  align-items: flex-start;\n  display: flex;\n  flex-direction: column;\n  flex-shrink: 0;\n  padding: 16px 16px;\n}\n@media only screen and (min-width: 768px ) {\n  .tox .tox-dialog__body-nav {\n    max-width: 11em;\n  }\n}\n@media only screen and (max-width: 767px ) {\n  body:not(.tox-force-desktop) .tox .tox-dialog__body-nav {\n    flex-direction: row;\n    -webkit-overflow-scrolling: touch;\n    overflow-x: auto;\n    padding-bottom: 0;\n  }\n}\n.tox .tox-dialog__body-nav-item {\n  border-bottom: 2px solid transparent;\n  color: rgba(34, 47, 62, 0.7);\n  display: inline-block;\n  flex-shrink: 0;\n  font-size: 14px;\n  line-height: 1.3;\n  margin-bottom: 8px;\n  max-width: 13em;\n  text-decoration: none;\n}\n.tox .tox-dialog__body-nav-item:focus {\n  background-color: rgba(32, 122, 183, 0.1);\n}\n.tox .tox-dialog__body-nav-item--active {\n  border-bottom: 2px solid #207ab7;\n  color: #207ab7;\n}\n.tox .tox-dialog__body-content {\n  box-sizing: border-box;\n  display: flex;\n  flex: 1;\n  flex-direction: column;\n  max-height: min(650px, calc(100vh - 110px));\n  overflow: auto;\n  -webkit-overflow-scrolling: touch;\n  padding: 16px 16px;\n}\n.tox .tox-dialog__body-content > * {\n  margin-bottom: 0;\n  margin-top: 16px;\n}\n.tox .tox-dialog__body-content > *:first-child {\n  margin-top: 0;\n}\n.tox .tox-dialog__body-content > *:last-child {\n  margin-bottom: 0;\n}\n.tox .tox-dialog__body-content > *:only-child {\n  margin-bottom: 0;\n  margin-top: 0;\n}\n.tox .tox-dialog__body-content a {\n  color: #207ab7;\n  cursor: pointer;\n  text-decoration: none;\n}\n.tox .tox-dialog__body-content a:hover,\n.tox .tox-dialog__body-content a:focus {\n  color: #185d8c;\n  text-decoration: none;\n}\n.tox .tox-dialog__body-content a:active {\n  color: #185d8c;\n  text-decoration: none;\n}\n.tox .tox-dialog__body-content svg {\n  fill: #222f3e;\n}\n.tox .tox-dialog__body-content strong {\n  font-weight: bold;\n}\n.tox .tox-dialog__body-content ul {\n  list-style-type: disc;\n}\n.tox .tox-dialog__body-content ul,\n.tox .tox-dialog__body-content ol,\n.tox .tox-dialog__body-content dd {\n  padding-inline-start: 2.5rem;\n}\n.tox .tox-dialog__body-content ul,\n.tox .tox-dialog__body-content ol,\n.tox .tox-dialog__body-content dl {\n  margin-bottom: 16px;\n}\n.tox .tox-dialog__body-content ul,\n.tox .tox-dialog__body-content ol,\n.tox .tox-dialog__body-content dl,\n.tox .tox-dialog__body-content dd,\n.tox .tox-dialog__body-content dt {\n  display: block;\n  margin-inline-end: 0;\n  margin-inline-start: 0;\n}\n.tox .tox-dialog__body-content .tox-form__group h1 {\n  color: #222f3e;\n  font-size: 20px;\n  font-style: normal;\n  font-weight: bold;\n  letter-spacing: normal;\n  margin-bottom: 16px;\n  margin-top: 2rem;\n  text-transform: none;\n}\n.tox .tox-dialog__body-content .tox-form__group h2 {\n  color: #222f3e;\n  font-size: 16px;\n  font-style: normal;\n  font-weight: bold;\n  letter-spacing: normal;\n  margin-bottom: 16px;\n  margin-top: 2rem;\n  text-transform: none;\n}\n.tox .tox-dialog__body-content .tox-form__group p {\n  margin-bottom: 16px;\n}\n.tox .tox-dialog__body-content .tox-form__group h1:first-child,\n.tox .tox-dialog__body-content .tox-form__group h2:first-child,\n.tox .tox-dialog__body-content .tox-form__group p:first-child {\n  margin-top: 0;\n}\n.tox .tox-dialog__body-content .tox-form__group h1:last-child,\n.tox .tox-dialog__body-content .tox-form__group h2:last-child,\n.tox .tox-dialog__body-content .tox-form__group p:last-child {\n  margin-bottom: 0;\n}\n.tox .tox-dialog__body-content .tox-form__group h1:only-child,\n.tox .tox-dialog__body-content .tox-form__group h2:only-child,\n.tox .tox-dialog__body-content .tox-form__group p:only-child {\n  margin-bottom: 0;\n  margin-top: 0;\n}\n.tox .tox-dialog__body-content .tox-form__group .tox-label.tox-label--center {\n  text-align: center;\n}\n.tox .tox-dialog__body-content .tox-form__group .tox-label.tox-label--end {\n  text-align: end;\n}\n.tox .tox-dialog--width-lg {\n  height: 650px;\n  max-width: 1200px;\n}\n.tox .tox-dialog--fullscreen {\n  height: 100%;\n  max-width: 100%;\n}\n.tox .tox-dialog--fullscreen .tox-dialog__body-content {\n  max-height: 100%;\n}\n.tox .tox-dialog--width-md {\n  max-width: 800px;\n}\n.tox .tox-dialog--width-md .tox-dialog__body-content {\n  overflow: auto;\n}\n.tox .tox-dialog__body-content--centered {\n  text-align: center;\n}\n.tox .tox-dialog__footer {\n  align-items: center;\n  background-color: #fff;\n  border-top: 1px solid #cccccc;\n  display: flex;\n  justify-content: space-between;\n  padding: 8px 16px;\n}\n.tox .tox-dialog__footer-start,\n.tox .tox-dialog__footer-end {\n  display: flex;\n}\n.tox .tox-dialog__busy-spinner {\n  align-items: center;\n  background-color: rgba(255, 255, 255, 0.75);\n  bottom: 0;\n  display: flex;\n  justify-content: center;\n  left: 0;\n  position: absolute;\n  right: 0;\n  top: 0;\n  z-index: 3;\n}\n.tox .tox-dialog__table {\n  border-collapse: collapse;\n  width: 100%;\n}\n.tox .tox-dialog__table thead th {\n  font-weight: bold;\n  padding-bottom: 8px;\n}\n.tox .tox-dialog__table thead th:first-child {\n  padding-right: 8px;\n}\n.tox .tox-dialog__table tbody tr {\n  border-bottom: 1px solid #404040;\n}\n.tox .tox-dialog__table tbody tr:last-child {\n  border-bottom: none;\n}\n.tox .tox-dialog__table td {\n  padding-bottom: 8px;\n  padding-top: 8px;\n}\n.tox .tox-dialog__table td:first-child {\n  padding-right: 8px;\n}\n.tox .tox-dialog__iframe {\n  min-height: 200px;\n}\n.tox .tox-dialog__iframe.tox-dialog__iframe--opaque {\n  background: #fff;\n}\n.tox .tox-dialog__iframe.tox-dialog__iframe--bordered {\n  border: 1px solid #cccccc;\n  border-radius: 3px;\n}\n.tox .tox-dialog__popups {\n  position: absolute;\n  width: 100%;\n  z-index: 1100;\n}\n.tox .tox-dialog__body-iframe {\n  display: flex;\n  flex: 1;\n  flex-direction: column;\n}\n.tox .tox-dialog__body-iframe .tox-navobj {\n  display: flex;\n  flex: 1;\n}\n.tox .tox-dialog__body-iframe .tox-navobj :nth-child(2) {\n  flex: 1;\n  height: 100%;\n}\n.tox .tox-dialog-dock-fadeout {\n  opacity: 0;\n  visibility: hidden;\n}\n.tox .tox-dialog-dock-fadein {\n  opacity: 1;\n  visibility: visible;\n}\n.tox .tox-dialog-dock-transition {\n  transition: visibility 0s linear 0.3s, opacity 0.3s ease;\n}\n.tox .tox-dialog-dock-transition.tox-dialog-dock-fadein {\n  transition-delay: 0s;\n}\n@media only screen and (max-width: 767px ) {\n  body:not(.tox-force-desktop) .tox:not([dir=rtl]) .tox-dialog__body-nav {\n    margin-right: 0;\n  }\n}\n@media only screen and (max-width: 767px ) {\n  body:not(.tox-force-desktop) .tox:not([dir=rtl]) .tox-dialog__body-nav-item:not(:first-child) {\n    margin-left: 8px;\n  }\n}\n.tox:not([dir=rtl]) .tox-dialog__footer .tox-dialog__footer-start > *,\n.tox:not([dir=rtl]) .tox-dialog__footer .tox-dialog__footer-end > * {\n  margin-left: 8px;\n}\n.tox[dir=rtl] .tox-dialog__body {\n  text-align: right;\n}\n@media only screen and (max-width: 767px ) {\n  body:not(.tox-force-desktop) .tox[dir=rtl] .tox-dialog__body-nav {\n    margin-left: 0;\n  }\n}\n@media only screen and (max-width: 767px ) {\n  body:not(.tox-force-desktop) .tox[dir=rtl] .tox-dialog__body-nav-item:not(:first-child) {\n    margin-right: 8px;\n  }\n}\n.tox[dir=rtl] .tox-dialog__footer .tox-dialog__footer-start > *,\n.tox[dir=rtl] .tox-dialog__footer .tox-dialog__footer-end > * {\n  margin-right: 8px;\n}\nbody.tox-dialog__disable-scroll {\n  overflow: hidden;\n}\n.tox .tox-dropzone-container {\n  display: flex;\n  flex: 1;\n}\n.tox .tox-dropzone {\n  align-items: center;\n  background: #fff;\n  border: 2px dashed #cccccc;\n  box-sizing: border-box;\n  display: flex;\n  flex-direction: column;\n  flex-grow: 1;\n  justify-content: center;\n  min-height: 100px;\n  padding: 10px;\n}\n.tox .tox-dropzone p {\n  color: rgba(34, 47, 62, 0.7);\n  margin: 0 0 16px 0;\n}\n.tox .tox-edit-area {\n  display: flex;\n  flex: 1;\n  overflow: hidden;\n  position: relative;\n}\n.tox .tox-edit-area::before {\n  border: 2px solid #2D6ADF;\n  border-radius: 4px;\n  content: '';\n  inset: 0;\n  opacity: 0;\n  pointer-events: none;\n  position: absolute;\n  transition: opacity 0.15s;\n  z-index: 1;\n}\n.tox .tox-edit-area__iframe {\n  background-color: #fff;\n  border: 0;\n  box-sizing: border-box;\n  flex: 1;\n  height: 100%;\n  position: absolute;\n  width: 100%;\n}\n.tox.tox-edit-focus .tox-edit-area::before {\n  opacity: 1;\n}\n.tox.tox-inline-edit-area {\n  border: 1px dotted #cccccc;\n}\n.tox .tox-editor-container {\n  display: flex;\n  flex: 1 1 auto;\n  flex-direction: column;\n  overflow: hidden;\n}\n.tox .tox-editor-header {\n  display: grid;\n  grid-template-columns: 1fr min-content;\n  z-index: 2;\n}\n.tox:not(.tox-tinymce-inline) .tox-editor-header {\n  background-color: #fff;\n  border-bottom: none;\n  box-shadow: none;\n  padding: 4px 0;\n}\n.tox:not(.tox-tinymce-inline) .tox-editor-header:not(.tox-editor-dock-transition) {\n  transition: box-shadow 0.5s;\n}\n.tox:not(.tox-tinymce-inline).tox-tinymce--toolbar-bottom .tox-editor-header {\n  border-top: 1px solid #cccccc;\n  box-shadow: none;\n}\n.tox:not(.tox-tinymce-inline).tox-tinymce--toolbar-sticky-on .tox-editor-header {\n  background-color: #fff;\n  box-shadow: 0 4px 4px -3px rgba(0, 0, 0, 0.25);\n  padding: 4px 0;\n}\n.tox:not(.tox-tinymce-inline).tox-tinymce--toolbar-sticky-on.tox-tinymce--toolbar-bottom .tox-editor-header {\n  box-shadow: 0 4px 4px -3px rgba(0, 0, 0, 0.25);\n}\n.tox.tox:not(.tox-tinymce-inline) .tox-editor-header.tox-editor-header--empty {\n  background: none;\n  border: none;\n  box-shadow: none;\n  padding: 0;\n}\n.tox-editor-dock-fadeout {\n  opacity: 0;\n  visibility: hidden;\n}\n.tox-editor-dock-fadein {\n  opacity: 1;\n  visibility: visible;\n}\n.tox-editor-dock-transition {\n  transition: visibility 0s linear 0.25s, opacity 0.25s ease;\n}\n.tox-editor-dock-transition.tox-editor-dock-fadein {\n  transition-delay: 0s;\n}\n.tox .tox-control-wrap {\n  flex: 1;\n  position: relative;\n}\n.tox .tox-control-wrap:not(.tox-control-wrap--status-invalid) .tox-control-wrap__status-icon-invalid,\n.tox .tox-control-wrap:not(.tox-control-wrap--status-unknown) .tox-control-wrap__status-icon-unknown,\n.tox .tox-control-wrap:not(.tox-control-wrap--status-valid) .tox-control-wrap__status-icon-valid {\n  display: none;\n}\n.tox .tox-control-wrap svg {\n  display: block;\n}\n.tox .tox-control-wrap__status-icon-wrap {\n  position: absolute;\n  top: 50%;\n  transform: translateY(-50%);\n}\n.tox .tox-control-wrap__status-icon-invalid svg {\n  fill: #c00;\n}\n.tox .tox-control-wrap__status-icon-unknown svg {\n  fill: orange;\n}\n.tox .tox-control-wrap__status-icon-valid svg {\n  fill: green;\n}\n.tox:not([dir=rtl]) .tox-control-wrap--status-invalid .tox-textfield,\n.tox:not([dir=rtl]) .tox-control-wrap--status-unknown .tox-textfield,\n.tox:not([dir=rtl]) .tox-control-wrap--status-valid .tox-textfield {\n  padding-right: 32px;\n}\n.tox:not([dir=rtl]) .tox-control-wrap__status-icon-wrap {\n  right: 4px;\n}\n.tox[dir=rtl] .tox-control-wrap--status-invalid .tox-textfield,\n.tox[dir=rtl] .tox-control-wrap--status-unknown .tox-textfield,\n.tox[dir=rtl] .tox-control-wrap--status-valid .tox-textfield {\n  padding-left: 32px;\n}\n.tox[dir=rtl] .tox-control-wrap__status-icon-wrap {\n  left: 4px;\n}\n.tox .tox-autocompleter {\n  max-width: 25em;\n}\n.tox .tox-autocompleter .tox-menu {\n  box-sizing: border-box;\n  max-width: 25em;\n}\n.tox .tox-autocompleter .tox-autocompleter-highlight {\n  font-weight: bold;\n}\n.tox .tox-color-input {\n  display: flex;\n  position: relative;\n  z-index: 1;\n}\n.tox .tox-color-input .tox-textfield {\n  z-index: -1;\n}\n.tox .tox-color-input span {\n  border-color: rgba(34, 47, 62, 0.2);\n  border-radius: 3px;\n  border-style: solid;\n  border-width: 1px;\n  box-shadow: none;\n  box-sizing: border-box;\n  height: 24px;\n  position: absolute;\n  top: 6px;\n  width: 24px;\n}\n.tox .tox-color-input span:hover:not([aria-disabled=true]),\n.tox .tox-color-input span:focus:not([aria-disabled=true]) {\n  border-color: #207ab7;\n  cursor: pointer;\n}\n.tox .tox-color-input span::before {\n  background-image: linear-gradient(45deg, rgba(0, 0, 0, 0.25) 25%, transparent 25%), linear-gradient(-45deg, rgba(0, 0, 0, 0.25) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, rgba(0, 0, 0, 0.25) 75%), linear-gradient(-45deg, transparent 75%, rgba(0, 0, 0, 0.25) 75%);\n  background-position: 0 0, 0 6px, 6px -6px, -6px 0;\n  background-size: 12px 12px;\n  border: 1px solid #fff;\n  border-radius: 3px;\n  box-sizing: border-box;\n  content: '';\n  height: 24px;\n  left: -1px;\n  position: absolute;\n  top: -1px;\n  width: 24px;\n  z-index: -1;\n}\n.tox .tox-color-input span[aria-disabled=true] {\n  cursor: not-allowed;\n}\n.tox:not([dir=rtl]) .tox-color-input {\n  /* stylelint-disable-next-line no-descending-specificity */\n}\n.tox:not([dir=rtl]) .tox-color-input .tox-textfield {\n  padding-left: 36px;\n}\n.tox:not([dir=rtl]) .tox-color-input span {\n  left: 6px;\n}\n.tox[dir=\"rtl\"] .tox-color-input {\n  /* stylelint-disable-next-line no-descending-specificity */\n}\n.tox[dir=\"rtl\"] .tox-color-input .tox-textfield {\n  padding-right: 36px;\n}\n.tox[dir=\"rtl\"] .tox-color-input span {\n  right: 6px;\n}\n.tox .tox-label,\n.tox .tox-toolbar-label {\n  color: rgba(34, 47, 62, 0.7);\n  display: block;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: normal;\n  line-height: 1.3;\n  padding: 0 8px 0 0;\n  text-transform: none;\n  white-space: nowrap;\n}\n.tox .tox-toolbar-label {\n  padding: 0 8px;\n}\n.tox[dir=rtl] .tox-label {\n  padding: 0 0 0 8px;\n}\n.tox .tox-form {\n  display: flex;\n  flex: 1;\n  flex-direction: column;\n}\n.tox .tox-form__group {\n  box-sizing: border-box;\n  margin-bottom: 4px;\n}\n.tox .tox-form-group--maximize {\n  flex: 1;\n}\n.tox .tox-form__group--error {\n  color: #c00;\n}\n.tox .tox-form__group--collection {\n  display: flex;\n}\n.tox .tox-form__grid {\n  display: flex;\n  flex-direction: row;\n  flex-wrap: wrap;\n  justify-content: space-between;\n}\n.tox .tox-form__grid--2col > .tox-form__group {\n  width: calc(50% - (8px / 2));\n}\n.tox .tox-form__grid--3col > .tox-form__group {\n  width: calc(100% / 3 - (8px / 2));\n}\n.tox .tox-form__grid--4col > .tox-form__group {\n  width: calc(25% - (8px / 2));\n}\n.tox .tox-form__controls-h-stack {\n  align-items: center;\n  display: flex;\n}\n.tox .tox-form__group--inline {\n  align-items: center;\n  display: flex;\n}\n.tox .tox-form__group--stretched {\n  display: flex;\n  flex: 1;\n  flex-direction: column;\n}\n.tox .tox-form__group--stretched .tox-textarea {\n  flex: 1;\n}\n.tox .tox-form__group--stretched .tox-navobj {\n  display: flex;\n  flex: 1;\n}\n.tox .tox-form__group--stretched .tox-navobj :nth-child(2) {\n  flex: 1;\n  height: 100%;\n}\n.tox:not([dir=rtl]) .tox-form__controls-h-stack > *:not(:first-child) {\n  margin-left: 4px;\n}\n.tox[dir=rtl] .tox-form__controls-h-stack > *:not(:first-child) {\n  margin-right: 4px;\n}\n.tox .tox-lock.tox-locked .tox-lock-icon__unlock,\n.tox .tox-lock:not(.tox-locked) .tox-lock-icon__lock {\n  display: none;\n}\n.tox .tox-textfield,\n.tox .tox-toolbar-textfield,\n.tox .tox-listboxfield .tox-listbox--select,\n.tox .tox-textarea,\n.tox .tox-textarea-wrap .tox-textarea:focus {\n  -webkit-appearance: none;\n     -moz-appearance: none;\n          appearance: none;\n  background-color: #fff;\n  border-color: #cccccc;\n  border-radius: 3px;\n  border-style: solid;\n  border-width: 1px;\n  box-shadow: none;\n  box-sizing: border-box;\n  color: #222f3e;\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n  font-size: 16px;\n  line-height: 24px;\n  margin: 0;\n  min-height: 34px;\n  outline: none;\n  padding: 5px 4.75px;\n  resize: none;\n  width: 100%;\n}\n.tox .tox-textfield[disabled],\n.tox .tox-textarea[disabled] {\n  background-color: #f2f2f2;\n  color: rgba(34, 47, 62, 0.85);\n  cursor: not-allowed;\n}\n.tox .tox-textfield:focus,\n.tox .tox-dialog__iframe.tox-dialog__iframe--bordered:focus-within,\n.tox .tox-listboxfield .tox-listbox--select:focus,\n.tox .tox-textarea-wrap:focus-within,\n.tox .tox-textarea:focus,\n.tox .tox-custom-editor:focus-within {\n  background-color: #fff;\n  border-color: #207ab7;\n  box-shadow: none;\n  outline: 2px solid rgba(32, 122, 183, 0.25);\n}\n.tox .tox-toolbar-textfield {\n  border-width: 0;\n  margin-bottom: 3px;\n  margin-top: 2px;\n  max-width: 250px;\n}\n.tox .tox-naked-btn {\n  background-color: transparent;\n  border: 0;\n  border-color: transparent;\n  box-shadow: unset;\n  color: #207ab7;\n  cursor: pointer;\n  display: block;\n  margin: 0;\n  padding: 0;\n}\n.tox .tox-naked-btn svg {\n  display: block;\n  fill: #222f3e;\n}\n.tox:not([dir=rtl]) .tox-toolbar-textfield + * {\n  margin-left: 4px;\n}\n.tox[dir=rtl] .tox-toolbar-textfield + * {\n  margin-right: 4px;\n}\n.tox .tox-listboxfield {\n  cursor: pointer;\n  position: relative;\n}\n.tox .tox-listboxfield .tox-listbox--select[disabled] {\n  background-color: #f2f2f2;\n  color: rgba(34, 47, 62, 0.85);\n  cursor: not-allowed;\n}\n.tox .tox-listbox__select-label {\n  cursor: default;\n  flex: 1;\n  margin: 0 4px;\n}\n.tox .tox-listbox__select-chevron {\n  align-items: center;\n  display: flex;\n  justify-content: center;\n  width: 16px;\n}\n.tox .tox-listbox__select-chevron svg {\n  fill: #222f3e;\n}\n.tox .tox-listboxfield .tox-listbox--select {\n  align-items: center;\n  display: flex;\n}\n.tox:not([dir=rtl]) .tox-listboxfield svg {\n  right: 8px;\n}\n.tox[dir=rtl] .tox-listboxfield svg {\n  left: 8px;\n}\n.tox .tox-selectfield {\n  cursor: pointer;\n  position: relative;\n}\n.tox .tox-selectfield select {\n  -webkit-appearance: none;\n     -moz-appearance: none;\n          appearance: none;\n  background-color: #fff;\n  border-color: #cccccc;\n  border-radius: 3px;\n  border-style: solid;\n  border-width: 1px;\n  box-shadow: none;\n  box-sizing: border-box;\n  color: #222f3e;\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n  font-size: 16px;\n  line-height: 24px;\n  margin: 0;\n  min-height: 34px;\n  outline: none;\n  padding: 5px 4.75px;\n  resize: none;\n  width: 100%;\n}\n.tox .tox-selectfield select[disabled] {\n  background-color: #f2f2f2;\n  color: rgba(34, 47, 62, 0.85);\n  cursor: not-allowed;\n}\n.tox .tox-selectfield select::-ms-expand {\n  display: none;\n}\n.tox .tox-selectfield select:focus {\n  background-color: #fff;\n  border-color: #207ab7;\n  box-shadow: none;\n  outline: 2px solid rgba(32, 122, 183, 0.25);\n}\n.tox .tox-selectfield svg {\n  pointer-events: none;\n  position: absolute;\n  top: 50%;\n  transform: translateY(-50%);\n}\n.tox:not([dir=rtl]) .tox-selectfield select[size=\"0\"],\n.tox:not([dir=rtl]) .tox-selectfield select[size=\"1\"] {\n  padding-right: 24px;\n}\n.tox:not([dir=rtl]) .tox-selectfield svg {\n  right: 8px;\n}\n.tox[dir=rtl] .tox-selectfield select[size=\"0\"],\n.tox[dir=rtl] .tox-selectfield select[size=\"1\"] {\n  padding-left: 24px;\n}\n.tox[dir=rtl] .tox-selectfield svg {\n  left: 8px;\n}\n.tox .tox-textarea-wrap {\n  border-color: #cccccc;\n  border-radius: 3px;\n  border-style: solid;\n  border-width: 1px;\n  display: flex;\n  flex: 1;\n  overflow: hidden;\n}\n.tox .tox-textarea {\n  -webkit-appearance: textarea;\n     -moz-appearance: textarea;\n          appearance: textarea;\n  white-space: pre-wrap;\n}\n.tox .tox-textarea-wrap .tox-textarea {\n  border: none;\n}\n.tox .tox-textarea-wrap .tox-textarea:focus {\n  border: none;\n}\n.tox-fullscreen {\n  border: 0;\n  height: 100%;\n  margin: 0;\n  overflow: hidden;\n  overscroll-behavior: none;\n  padding: 0;\n  touch-action: pinch-zoom;\n  width: 100%;\n}\n.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle {\n  display: none;\n}\n.tox.tox-tinymce.tox-fullscreen,\n.tox-shadowhost.tox-fullscreen {\n  left: 0;\n  position: fixed;\n  top: 0;\n  z-index: 1200;\n}\n.tox.tox-tinymce.tox-fullscreen {\n  background-color: transparent;\n}\n.tox-fullscreen .tox.tox-tinymce-aux,\n.tox-fullscreen ~ .tox.tox-tinymce-aux {\n  z-index: 1201;\n}\n.tox .tox-help__more-link {\n  list-style: none;\n  margin-top: 1em;\n}\n.tox .tox-imagepreview {\n  background-color: #666;\n  height: 380px;\n  overflow: hidden;\n  position: relative;\n  width: 100%;\n}\n.tox .tox-imagepreview.tox-imagepreview__loaded {\n  overflow: auto;\n}\n.tox .tox-imagepreview__container {\n  display: flex;\n  left: 100vw;\n  position: absolute;\n  top: 100vw;\n}\n.tox .tox-imagepreview__image {\n  background: url(data:image/gif;base64,R0lGODdhDAAMAIABAMzMzP///ywAAAAADAAMAAACFoQfqYeabNyDMkBQb81Uat85nxguUAEAOw==);\n}\n.tox .tox-image-tools .tox-spacer {\n  flex: 1;\n}\n.tox .tox-image-tools .tox-bar {\n  align-items: center;\n  display: flex;\n  height: 60px;\n  justify-content: center;\n}\n.tox .tox-image-tools .tox-imagepreview,\n.tox .tox-image-tools .tox-imagepreview + .tox-bar {\n  margin-top: 8px;\n}\n.tox .tox-image-tools .tox-croprect-block {\n  background: black;\n  filter: alpha(opacity=50);\n  opacity: 0.5;\n  position: absolute;\n  zoom: 1;\n}\n.tox .tox-image-tools .tox-croprect-handle {\n  border: 2px solid white;\n  height: 20px;\n  left: 0;\n  position: absolute;\n  top: 0;\n  width: 20px;\n}\n.tox .tox-image-tools .tox-croprect-handle-move {\n  border: 0;\n  cursor: move;\n  position: absolute;\n}\n.tox .tox-image-tools .tox-croprect-handle-nw {\n  border-width: 2px 0 0 2px;\n  cursor: nw-resize;\n  left: 100px;\n  margin: -2px 0 0 -2px;\n  top: 100px;\n}\n.tox .tox-image-tools .tox-croprect-handle-ne {\n  border-width: 2px 2px 0 0;\n  cursor: ne-resize;\n  left: 200px;\n  margin: -2px 0 0 -20px;\n  top: 100px;\n}\n.tox .tox-image-tools .tox-croprect-handle-sw {\n  border-width: 0 0 2px 2px;\n  cursor: sw-resize;\n  left: 100px;\n  margin: -20px 2px 0 -2px;\n  top: 200px;\n}\n.tox .tox-image-tools .tox-croprect-handle-se {\n  border-width: 0 2px 2px 0;\n  cursor: se-resize;\n  left: 200px;\n  margin: -20px 0 0 -20px;\n  top: 200px;\n}\n.tox .tox-insert-table-picker {\n  display: flex;\n  flex-wrap: wrap;\n  width: 170px;\n}\n.tox .tox-insert-table-picker > div {\n  border-color: #cccccc;\n  border-style: solid;\n  border-width: 0 1px 1px 0;\n  box-sizing: border-box;\n  height: 17px;\n  width: 17px;\n}\n.tox .tox-collection--list .tox-collection__group .tox-insert-table-picker {\n  margin: 0 -4px;\n}\n.tox .tox-insert-table-picker .tox-insert-table-picker__selected {\n  background-color: rgba(32, 122, 183, 0.5);\n  border-color: rgba(32, 122, 183, 0.5);\n}\n.tox .tox-insert-table-picker__label {\n  color: rgba(34, 47, 62, 0.7);\n  display: block;\n  font-size: 14px;\n  padding: 4px;\n  text-align: center;\n  width: 100%;\n}\n.tox:not([dir=rtl]) {\n  /* stylelint-disable-next-line no-descending-specificity */\n}\n.tox:not([dir=rtl]) .tox-insert-table-picker > div:nth-child(10n) {\n  border-right: 0;\n}\n.tox[dir=rtl] {\n  /* stylelint-disable-next-line no-descending-specificity */\n}\n.tox[dir=rtl] .tox-insert-table-picker > div:nth-child(10n+1) {\n  border-right: 0;\n}\n.tox {\n  /* stylelint-disable */\n  /* stylelint-enable */\n}\n.tox .tox-menu {\n  background-color: #fff;\n  border: 1px solid #cccccc;\n  border-radius: 3px;\n  box-shadow: 0 4px 8px 0 rgba(34, 47, 62, 0.1);\n  display: inline-block;\n  overflow: hidden;\n  vertical-align: top;\n  z-index: 1150;\n}\n.tox .tox-menu.tox-collection.tox-collection--list {\n  padding: 0 0;\n}\n.tox .tox-menu.tox-collection.tox-collection--toolbar {\n  padding: 4px;\n}\n.tox .tox-menu.tox-collection.tox-collection--grid {\n  padding: 4px;\n}\n@media only screen and (min-width: 768px ) {\n  .tox .tox-menu .tox-collection__item-label {\n    overflow-wrap: break-word;\n    word-break: normal;\n  }\n}\n.tox .tox-menu__label h1,\n.tox .tox-menu__label h2,\n.tox .tox-menu__label h3,\n.tox .tox-menu__label h4,\n.tox .tox-menu__label h5,\n.tox .tox-menu__label h6,\n.tox .tox-menu__label p,\n.tox .tox-menu__label blockquote,\n.tox .tox-menu__label code {\n  margin: 0;\n}\n.tox .tox-menubar {\n  background: url(\"data:image/svg+xml;charset=utf8,%3Csvg height='39px' viewBox='0 0 40 39px' width='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='38px' width='100' height='1' fill='%23cccccc'/%3E%3C/svg%3E\") left 0 top 0 #fff;\n  background-color: #fff;\n  display: flex;\n  flex: 0 0 auto;\n  flex-shrink: 0;\n  flex-wrap: wrap;\n  grid-column: 1 / -1;\n  grid-row: 1;\n  padding: 0 4px 0 4px;\n}\n.tox .tox-promotion + .tox-menubar {\n  grid-column: 1;\n}\n.tox .tox-promotion {\n  background: url(\"data:image/svg+xml;charset=utf8,%3Csvg height='39px' viewBox='0 0 40 39px' width='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='38px' width='100' height='1' fill='%23cccccc'/%3E%3C/svg%3E\") left 0 top 0 #fff;\n  background-color: #fff;\n  grid-column: 2;\n  grid-row: 1;\n  padding-inline-end: 8px;\n  padding-inline-start: 4px;\n  padding-top: 5px;\n}\n.tox .tox-promotion-link {\n  align-items: unsafe center;\n  background-color: #E8F1F8;\n  border-radius: 5px;\n  color: #086BE6;\n  cursor: pointer;\n  display: flex;\n  font-size: 14px;\n  height: 26.6px;\n  padding: 4px 8px;\n  white-space: nowrap;\n}\n.tox .tox-promotion-link:hover {\n  background-color: #B4D7FF;\n}\n.tox .tox-promotion-link:focus {\n  background-color: #D9EDF7;\n}\n/* Deprecated. Remove in next major release */\n.tox .tox-mbtn {\n  align-items: center;\n  background: transparent;\n  border: 0;\n  border-radius: 3px;\n  box-shadow: none;\n  color: #222f3e;\n  display: flex;\n  flex: 0 0 auto;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: normal;\n  height: 34px;\n  justify-content: center;\n  margin: 2px 0 3px 0;\n  outline: none;\n  overflow: hidden;\n  padding: 0 4px;\n  text-transform: none;\n  width: auto;\n}\n.tox .tox-mbtn[disabled] {\n  background-color: transparent;\n  border: 0;\n  box-shadow: none;\n  color: rgba(34, 47, 62, 0.5);\n  cursor: not-allowed;\n}\n.tox .tox-mbtn:focus:not(:disabled) {\n  background: #dee0e2;\n  border: 0;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-mbtn--active {\n  background: #c8cbcf;\n  border: 0;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-mbtn:hover:not(:disabled):not(.tox-mbtn--active) {\n  background: #dee0e2;\n  border: 0;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-mbtn__select-label {\n  cursor: default;\n  font-weight: normal;\n  margin: 0 4px;\n}\n.tox .tox-mbtn[disabled] .tox-mbtn__select-label {\n  cursor: not-allowed;\n}\n.tox .tox-mbtn__select-chevron {\n  align-items: center;\n  display: flex;\n  justify-content: center;\n  width: 16px;\n  display: none;\n}\n.tox .tox-notification {\n  border-radius: 3px;\n  border-style: solid;\n  border-width: 1px;\n  box-shadow: none;\n  box-sizing: border-box;\n  display: grid;\n  font-size: 14px;\n  font-weight: normal;\n  grid-template-columns: minmax(40px, 1fr) auto minmax(40px, 1fr);\n  margin-top: 4px;\n  opacity: 0;\n  padding: 4px;\n  transition: transform 100ms ease-in, opacity 150ms ease-in;\n}\n.tox .tox-notification p {\n  font-size: 14px;\n  font-weight: normal;\n}\n.tox .tox-notification a {\n  cursor: pointer;\n  text-decoration: underline;\n}\n.tox .tox-notification--in {\n  opacity: 1;\n}\n.tox .tox-notification--success {\n  background-color: #e4eeda;\n  border-color: #d7e6c8;\n  color: #222f3e;\n}\n.tox .tox-notification--success p {\n  color: #222f3e;\n}\n.tox .tox-notification--success a {\n  color: #517342;\n}\n.tox .tox-notification--success svg {\n  fill: #222f3e;\n}\n.tox .tox-notification--error {\n  background-color: #f5cccc;\n  border-color: #f0b3b3;\n  color: #222f3e;\n}\n.tox .tox-notification--error p {\n  color: #222f3e;\n}\n.tox .tox-notification--error a {\n  color: #77181f;\n}\n.tox .tox-notification--error svg {\n  fill: #222f3e;\n}\n.tox .tox-notification--warn,\n.tox .tox-notification--warning {\n  background-color: #fff5cc;\n  border-color: #fff0b3;\n  color: #222f3e;\n}\n.tox .tox-notification--warn p,\n.tox .tox-notification--warning p {\n  color: #222f3e;\n}\n.tox .tox-notification--warn a,\n.tox .tox-notification--warning a {\n  color: #7a6e25;\n}\n.tox .tox-notification--warn svg,\n.tox .tox-notification--warning svg {\n  fill: #222f3e;\n}\n.tox .tox-notification--info {\n  background-color: #d6e7fb;\n  border-color: #c1dbf9;\n  color: #222f3e;\n}\n.tox .tox-notification--info p {\n  color: #222f3e;\n}\n.tox .tox-notification--info a {\n  color: #2a64a6;\n}\n.tox .tox-notification--info svg {\n  fill: #222f3e;\n}\n.tox .tox-notification__body {\n  align-self: center;\n  color: #222f3e;\n  font-size: 14px;\n  grid-column-end: 3;\n  grid-column-start: 2;\n  grid-row-end: 2;\n  grid-row-start: 1;\n  text-align: center;\n  white-space: normal;\n  word-break: break-all;\n  word-break: break-word;\n}\n.tox .tox-notification__body > * {\n  margin: 0;\n}\n.tox .tox-notification__body > * + * {\n  margin-top: 1rem;\n}\n.tox .tox-notification__icon {\n  align-self: center;\n  grid-column-end: 2;\n  grid-column-start: 1;\n  grid-row-end: 2;\n  grid-row-start: 1;\n  justify-self: end;\n}\n.tox .tox-notification__icon svg {\n  display: block;\n}\n.tox .tox-notification__dismiss {\n  align-self: start;\n  grid-column-end: 4;\n  grid-column-start: 3;\n  grid-row-end: 2;\n  grid-row-start: 1;\n  justify-self: end;\n}\n.tox .tox-notification .tox-progress-bar {\n  grid-column-end: 4;\n  grid-column-start: 1;\n  grid-row-end: 3;\n  grid-row-start: 2;\n  justify-self: center;\n}\n.tox .tox-pop {\n  display: inline-block;\n  position: relative;\n}\n.tox .tox-pop--resizing {\n  transition: width 0.1s ease;\n}\n.tox .tox-pop--resizing .tox-toolbar,\n.tox .tox-pop--resizing .tox-toolbar__group {\n  flex-wrap: nowrap;\n}\n.tox .tox-pop--transition {\n  transition: 0.15s ease;\n  transition-property: left, right, top, bottom;\n}\n.tox .tox-pop--transition::before,\n.tox .tox-pop--transition::after {\n  transition: all 0.15s, visibility 0s, opacity 0.075s ease 0.075s;\n}\n.tox .tox-pop__dialog {\n  background-color: #fff;\n  border: 1px solid #cccccc;\n  border-radius: 3px;\n  box-shadow: 0 0 2px 0 rgba(34, 47, 62, 0.2), 0 4px 8px 0 rgba(34, 47, 62, 0.15);\n  min-width: 0;\n  overflow: hidden;\n}\n.tox .tox-pop__dialog > *:not(.tox-toolbar) {\n  margin: 4px 4px 4px 8px;\n}\n.tox .tox-pop__dialog .tox-toolbar {\n  background-color: transparent;\n  margin-bottom: -1px;\n}\n.tox .tox-pop::before,\n.tox .tox-pop::after {\n  border-style: solid;\n  content: '';\n  display: block;\n  height: 0;\n  opacity: 1;\n  position: absolute;\n  width: 0;\n}\n.tox .tox-pop.tox-pop--inset::before,\n.tox .tox-pop.tox-pop--inset::after {\n  opacity: 0;\n  transition: all 0s 0.15s, visibility 0s, opacity 0.075s ease;\n}\n.tox .tox-pop.tox-pop--bottom::before,\n.tox .tox-pop.tox-pop--bottom::after {\n  left: 50%;\n  top: 100%;\n}\n.tox .tox-pop.tox-pop--bottom::after {\n  border-color: #fff transparent transparent transparent;\n  border-width: 8px;\n  margin-left: -8px;\n  margin-top: -1px;\n}\n.tox .tox-pop.tox-pop--bottom::before {\n  border-color: #cccccc transparent transparent transparent;\n  border-width: 9px;\n  margin-left: -9px;\n}\n.tox .tox-pop.tox-pop--top::before,\n.tox .tox-pop.tox-pop--top::after {\n  left: 50%;\n  top: 0;\n  transform: translateY(-100%);\n}\n.tox .tox-pop.tox-pop--top::after {\n  border-color: transparent transparent #fff transparent;\n  border-width: 8px;\n  margin-left: -8px;\n  margin-top: 1px;\n}\n.tox .tox-pop.tox-pop--top::before {\n  border-color: transparent transparent #cccccc transparent;\n  border-width: 9px;\n  margin-left: -9px;\n}\n.tox .tox-pop.tox-pop--left::before,\n.tox .tox-pop.tox-pop--left::after {\n  left: 0;\n  top: calc(50% - 1px);\n  transform: translateY(-50%);\n}\n.tox .tox-pop.tox-pop--left::after {\n  border-color: transparent #fff transparent transparent;\n  border-width: 8px;\n  margin-left: -15px;\n}\n.tox .tox-pop.tox-pop--left::before {\n  border-color: transparent #cccccc transparent transparent;\n  border-width: 10px;\n  margin-left: -19px;\n}\n.tox .tox-pop.tox-pop--right::before,\n.tox .tox-pop.tox-pop--right::after {\n  left: 100%;\n  top: calc(50% + 1px);\n  transform: translateY(-50%);\n}\n.tox .tox-pop.tox-pop--right::after {\n  border-color: transparent transparent transparent #fff;\n  border-width: 8px;\n  margin-left: -1px;\n}\n.tox .tox-pop.tox-pop--right::before {\n  border-color: transparent transparent transparent #cccccc;\n  border-width: 10px;\n  margin-left: -1px;\n}\n.tox .tox-pop.tox-pop--align-left::before,\n.tox .tox-pop.tox-pop--align-left::after {\n  left: 20px;\n}\n.tox .tox-pop.tox-pop--align-right::before,\n.tox .tox-pop.tox-pop--align-right::after {\n  left: calc(100% - 20px);\n}\n.tox .tox-sidebar-wrap {\n  display: flex;\n  flex-direction: row;\n  flex-grow: 1;\n  min-height: 0;\n}\n.tox .tox-sidebar {\n  background-color: #fff;\n  display: flex;\n  flex-direction: row;\n  justify-content: flex-end;\n}\n.tox .tox-sidebar__slider {\n  display: flex;\n  overflow: hidden;\n}\n.tox .tox-sidebar__pane-container {\n  display: flex;\n}\n.tox .tox-sidebar__pane {\n  display: flex;\n}\n.tox .tox-sidebar--sliding-closed {\n  opacity: 0;\n}\n.tox .tox-sidebar--sliding-open {\n  opacity: 1;\n}\n.tox .tox-sidebar--sliding-growing,\n.tox .tox-sidebar--sliding-shrinking {\n  transition: width 0.5s ease, opacity 0.5s ease;\n}\n.tox .tox-selector {\n  background-color: #4099ff;\n  border-color: #4099ff;\n  border-style: solid;\n  border-width: 1px;\n  box-sizing: border-box;\n  display: inline-block;\n  height: 10px;\n  position: absolute;\n  width: 10px;\n}\n.tox.tox-platform-touch .tox-selector {\n  height: 12px;\n  width: 12px;\n}\n.tox .tox-slider {\n  align-items: center;\n  display: flex;\n  flex: 1;\n  height: 24px;\n  justify-content: center;\n  position: relative;\n}\n.tox .tox-slider__rail {\n  background-color: transparent;\n  border: 1px solid #cccccc;\n  border-radius: 3px;\n  height: 10px;\n  min-width: 120px;\n  width: 100%;\n}\n.tox .tox-slider__handle {\n  background-color: #207ab7;\n  border: 2px solid #185d8c;\n  border-radius: 3px;\n  box-shadow: none;\n  height: 24px;\n  left: 50%;\n  position: absolute;\n  top: 50%;\n  transform: translateX(-50%) translateY(-50%);\n  width: 14px;\n}\n.tox .tox-form__controls-h-stack > .tox-slider:not(:first-of-type) {\n  margin-inline-start: 8px;\n}\n.tox .tox-form__controls-h-stack > .tox-form__group + .tox-slider {\n  margin-inline-start: 32px;\n}\n.tox .tox-form__controls-h-stack > .tox-slider + .tox-form__group {\n  margin-inline-start: 32px;\n}\n.tox .tox-source-code {\n  overflow: auto;\n}\n.tox .tox-spinner {\n  display: flex;\n}\n.tox .tox-spinner > div {\n  animation: tam-bouncing-dots 1.5s ease-in-out 0s infinite both;\n  background-color: rgba(34, 47, 62, 0.7);\n  border-radius: 100%;\n  height: 8px;\n  width: 8px;\n}\n.tox .tox-spinner > div:nth-child(1) {\n  animation-delay: -0.32s;\n}\n.tox .tox-spinner > div:nth-child(2) {\n  animation-delay: -0.16s;\n}\n@keyframes tam-bouncing-dots {\n  0%,\n  80%,\n  100% {\n    transform: scale(0);\n  }\n  40% {\n    transform: scale(1);\n  }\n}\n.tox:not([dir=rtl]) .tox-spinner > div:not(:first-child) {\n  margin-left: 4px;\n}\n.tox[dir=rtl] .tox-spinner > div:not(:first-child) {\n  margin-right: 4px;\n}\n.tox .tox-statusbar {\n  align-items: center;\n  background-color: #fff;\n  border-top: 1px solid #cccccc;\n  color: rgba(34, 47, 62, 0.7);\n  display: flex;\n  flex: 0 0 auto;\n  font-size: 12px;\n  font-weight: normal;\n  height: 18px;\n  overflow: hidden;\n  padding: 0 8px;\n  position: relative;\n  text-transform: uppercase;\n}\n.tox .tox-statusbar__text-container {\n  display: flex;\n  flex: 1 1 auto;\n  justify-content: flex-end;\n  overflow: hidden;\n}\n.tox .tox-statusbar__path {\n  display: flex;\n  flex: 1 1 auto;\n  margin-right: auto;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n.tox .tox-statusbar__path > * {\n  display: inline;\n  white-space: nowrap;\n}\n.tox .tox-statusbar__wordcount {\n  flex: 0 0 auto;\n  margin-left: 1ch;\n}\n.tox .tox-statusbar a,\n.tox .tox-statusbar__path-item,\n.tox .tox-statusbar__wordcount {\n  color: rgba(34, 47, 62, 0.7);\n  text-decoration: none;\n}\n.tox .tox-statusbar a:hover:not(:disabled):not([aria-disabled=true]),\n.tox .tox-statusbar__path-item:hover:not(:disabled):not([aria-disabled=true]),\n.tox .tox-statusbar__wordcount:hover:not(:disabled):not([aria-disabled=true]),\n.tox .tox-statusbar a:focus:not(:disabled):not([aria-disabled=true]),\n.tox .tox-statusbar__path-item:focus:not(:disabled):not([aria-disabled=true]),\n.tox .tox-statusbar__wordcount:focus:not(:disabled):not([aria-disabled=true]) {\n  color: #222f3e;\n  cursor: pointer;\n}\n.tox .tox-statusbar__branding svg {\n  fill: rgba(34, 47, 62, 0.8);\n  height: 1.14em;\n  vertical-align: -0.28em;\n  width: 3.6em;\n}\n.tox .tox-statusbar__branding a:hover:not(:disabled):not([aria-disabled=true]) svg,\n.tox .tox-statusbar__branding a:focus:not(:disabled):not([aria-disabled=true]) svg {\n  fill: #222f3e;\n}\n.tox .tox-statusbar__resize-handle {\n  align-items: flex-end;\n  align-self: stretch;\n  cursor: nwse-resize;\n  display: flex;\n  flex: 0 0 auto;\n  justify-content: flex-end;\n  margin-left: auto;\n  margin-right: -8px;\n  padding-bottom: 3px;\n  padding-left: 1ch;\n  padding-right: 3px;\n}\n.tox .tox-statusbar__resize-handle svg {\n  display: block;\n  fill: rgba(34, 47, 62, 0.5);\n}\n.tox .tox-statusbar__resize-handle:focus svg {\n  background-color: #dee0e2;\n  border-radius: 1px 1px -4px 1px;\n  box-shadow: 0 0 0 2px #dee0e2;\n}\n.tox:not([dir=rtl]) .tox-statusbar__path > * {\n  margin-right: 4px;\n}\n.tox:not([dir=rtl]) .tox-statusbar__branding {\n  margin-left: 2ch;\n}\n.tox[dir=rtl] .tox-statusbar {\n  flex-direction: row-reverse;\n}\n.tox[dir=rtl] .tox-statusbar__path > * {\n  margin-left: 4px;\n}\n.tox .tox-throbber {\n  z-index: 1299;\n}\n.tox .tox-throbber__busy-spinner {\n  align-items: center;\n  background-color: rgba(255, 255, 255, 0.6);\n  bottom: 0;\n  display: flex;\n  justify-content: center;\n  left: 0;\n  position: absolute;\n  right: 0;\n  top: 0;\n}\n.tox .tox-tbtn {\n  align-items: center;\n  background: transparent;\n  border: 0;\n  border-radius: 3px;\n  box-shadow: none;\n  color: #222f3e;\n  display: flex;\n  flex: 0 0 auto;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: normal;\n  height: 34px;\n  justify-content: center;\n  margin: 3px 0 2px 0;\n  outline: none;\n  overflow: hidden;\n  padding: 0;\n  text-transform: none;\n  width: 34px;\n}\n.tox .tox-tbtn svg {\n  display: block;\n  fill: #222f3e;\n}\n.tox .tox-tbtn.tox-tbtn-more {\n  padding-left: 5px;\n  padding-right: 5px;\n  width: inherit;\n}\n.tox .tox-tbtn:focus {\n  background: #dee0e2;\n  border: 0;\n  box-shadow: none;\n}\n.tox .tox-tbtn:hover {\n  background: #dee0e2;\n  border: 0;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-tbtn:hover svg {\n  fill: #222f3e;\n}\n.tox .tox-tbtn:active {\n  background: #c8cbcf;\n  border: 0;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-tbtn:active svg {\n  fill: #222f3e;\n}\n.tox .tox-tbtn--disabled .tox-tbtn--enabled svg {\n  fill: rgba(34, 47, 62, 0.5);\n}\n.tox .tox-tbtn--disabled,\n.tox .tox-tbtn--disabled:hover,\n.tox .tox-tbtn:disabled,\n.tox .tox-tbtn:disabled:hover {\n  background: transparent;\n  border: 0;\n  box-shadow: none;\n  color: rgba(34, 47, 62, 0.5);\n  cursor: not-allowed;\n}\n.tox .tox-tbtn--disabled svg,\n.tox .tox-tbtn--disabled:hover svg,\n.tox .tox-tbtn:disabled svg,\n.tox .tox-tbtn:disabled:hover svg {\n  /* stylelint-disable-line no-descending-specificity */\n  fill: rgba(34, 47, 62, 0.5);\n}\n.tox .tox-tbtn--enabled,\n.tox .tox-tbtn--enabled:hover {\n  background: #c8cbcf;\n  border: 0;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-tbtn--enabled > *,\n.tox .tox-tbtn--enabled:hover > * {\n  transform: none;\n}\n.tox .tox-tbtn--enabled svg,\n.tox .tox-tbtn--enabled:hover svg {\n  /* stylelint-disable-line no-descending-specificity */\n  fill: #222f3e;\n}\n.tox .tox-tbtn--enabled.tox-tbtn--disabled svg,\n.tox .tox-tbtn--enabled:hover.tox-tbtn--disabled svg {\n  fill: rgba(34, 47, 62, 0.5);\n}\n.tox .tox-tbtn:focus:not(.tox-tbtn--disabled) {\n  color: #222f3e;\n}\n.tox .tox-tbtn:focus:not(.tox-tbtn--disabled) svg {\n  fill: #222f3e;\n}\n.tox .tox-tbtn:active > * {\n  transform: none;\n}\n.tox .tox-tbtn--md {\n  height: 51px;\n  width: 51px;\n}\n.tox .tox-tbtn--lg {\n  flex-direction: column;\n  height: 68px;\n  width: 68px;\n}\n.tox .tox-tbtn--return {\n  align-self: stretch;\n  height: unset;\n  width: 16px;\n}\n.tox .tox-tbtn--labeled {\n  padding: 0 4px;\n  width: unset;\n}\n.tox .tox-tbtn__vlabel {\n  display: block;\n  font-size: 10px;\n  font-weight: normal;\n  letter-spacing: -0.025em;\n  margin-bottom: 4px;\n  white-space: nowrap;\n}\n.tox .tox-number-input {\n  border-radius: 3px;\n  display: flex;\n  margin: 3px 0 2px 0;\n  padding: 0 4px;\n  width: auto;\n}\n.tox .tox-number-input .tox-input-wrapper {\n  background: transparent;\n  display: flex;\n  pointer-events: none;\n  text-align: center;\n}\n.tox .tox-number-input .tox-input-wrapper:focus {\n  background: #dee0e2;\n}\n.tox .tox-number-input input {\n  border-radius: 3px;\n  color: #222f3e;\n  font-size: 14px;\n  margin: 2px 0;\n  pointer-events: all;\n  width: 60px;\n}\n.tox .tox-number-input input:hover {\n  background: #dee0e2;\n  color: #222f3e;\n}\n.tox .tox-number-input input:focus {\n  background: #fff;\n  color: #222f3e;\n}\n.tox .tox-number-input input:disabled {\n  background: transparent;\n  border: 0;\n  box-shadow: none;\n  color: rgba(34, 47, 62, 0.5);\n  cursor: not-allowed;\n}\n.tox .tox-number-input button {\n  background: transparent;\n  color: #222f3e;\n  height: 34px;\n  text-align: center;\n  width: 24px;\n}\n.tox .tox-number-input button svg {\n  display: block;\n  fill: #222f3e;\n  margin: 0 auto;\n  transform: scale(0.67);\n}\n.tox .tox-number-input button:focus {\n  background: #dee0e2;\n}\n.tox .tox-number-input button:hover {\n  background: #dee0e2;\n  border: 0;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-number-input button:hover svg {\n  fill: #222f3e;\n}\n.tox .tox-number-input button:active {\n  background: #c8cbcf;\n  border: 0;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-number-input button:active svg {\n  fill: #222f3e;\n}\n.tox .tox-number-input button:disabled {\n  background: transparent;\n  border: 0;\n  box-shadow: none;\n  color: rgba(34, 47, 62, 0.5);\n  cursor: not-allowed;\n}\n.tox .tox-number-input button:disabled svg {\n  fill: rgba(34, 47, 62, 0.5);\n}\n.tox .tox-number-input button.minus {\n  border-radius: 3px 0 0 3px;\n}\n.tox .tox-number-input button.plus {\n  border-radius: 0 3px 3px 0;\n}\n.tox .tox-number-input:focus:not(:active) > button,\n.tox .tox-number-input:focus:not(:active) > .tox-input-wrapper {\n  background: #dee0e2;\n}\n.tox .tox-tbtn--select {\n  margin: 3px 0 2px 0;\n  padding: 0 4px;\n  width: auto;\n}\n.tox .tox-tbtn__select-label {\n  cursor: default;\n  font-weight: normal;\n  height: initial;\n  margin: 0 4px;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n.tox .tox-tbtn__select-chevron {\n  align-items: center;\n  display: flex;\n  justify-content: center;\n  width: 16px;\n}\n.tox .tox-tbtn__select-chevron svg {\n  fill: rgba(34, 47, 62, 0.5);\n}\n.tox .tox-tbtn--bespoke {\n  background: transparent;\n}\n.tox .tox-tbtn--bespoke + .tox-tbtn--bespoke {\n  margin-inline-start: 0;\n}\n.tox .tox-tbtn--bespoke .tox-tbtn__select-label {\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  width: 7em;\n}\n.tox .tox-tbtn--disabled .tox-tbtn__select-label,\n.tox .tox-tbtn--select:disabled .tox-tbtn__select-label {\n  cursor: not-allowed;\n}\n.tox .tox-split-button {\n  border: 0;\n  border-radius: 3px;\n  box-sizing: border-box;\n  display: flex;\n  margin: 3px 0 2px 0;\n  overflow: hidden;\n}\n.tox .tox-split-button:hover {\n  box-shadow: 0 0 0 1px #dee0e2 inset;\n}\n.tox .tox-split-button:focus {\n  background: #dee0e2;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-split-button > * {\n  border-radius: 0;\n}\n.tox .tox-split-button__chevron {\n  width: 16px;\n}\n.tox .tox-split-button__chevron svg {\n  fill: rgba(34, 47, 62, 0.5);\n}\n.tox .tox-split-button .tox-tbtn {\n  margin: 0;\n}\n.tox .tox-split-button.tox-tbtn--disabled:hover,\n.tox .tox-split-button.tox-tbtn--disabled:focus,\n.tox .tox-split-button.tox-tbtn--disabled .tox-tbtn:hover,\n.tox .tox-split-button.tox-tbtn--disabled .tox-tbtn:focus {\n  background: transparent;\n  box-shadow: none;\n  color: rgba(34, 47, 62, 0.5);\n}\n.tox.tox-platform-touch .tox-split-button .tox-tbtn--select {\n  padding: 0 0px;\n}\n.tox.tox-platform-touch .tox-split-button .tox-tbtn:not(.tox-tbtn--select):first-child {\n  width: 30px;\n}\n.tox.tox-platform-touch .tox-split-button__chevron {\n  width: 20px;\n}\n.tox .tox-split-button.tox-tbtn--disabled svg #tox-icon-text-color__color,\n.tox .tox-split-button.tox-tbtn--disabled svg #tox-icon-highlight-bg-color__color {\n  opacity: 0.6;\n}\n.tox .tox-toolbar-overlord {\n  background-color: #fff;\n}\n.tox .tox-toolbar,\n.tox .tox-toolbar__primary,\n.tox .tox-toolbar__overflow {\n  background-attachment: local;\n  background-color: #fff;\n  background-image: repeating-linear-gradient(#cccccc 0px 1px, transparent 1px 39px);\n  background-position: center top 39px;\n  background-repeat: no-repeat;\n  background-size: calc(100% - 4px * 2) calc(100% - 39px);\n  display: flex;\n  flex: 0 0 auto;\n  flex-shrink: 0;\n  flex-wrap: wrap;\n  padding: 0 0px;\n  transform: perspective(1px);\n}\n.tox .tox-toolbar-overlord > .tox-toolbar,\n.tox .tox-toolbar-overlord > .tox-toolbar__primary,\n.tox .tox-toolbar-overlord > .tox-toolbar__overflow {\n  background-position: center top 0px;\n  background-size: calc(100% - 4px * 2) calc(100% - 0px);\n}\n.tox .tox-toolbar__overflow.tox-toolbar__overflow--closed {\n  height: 0;\n  opacity: 0;\n  padding-bottom: 0;\n  padding-top: 0;\n  visibility: hidden;\n}\n.tox .tox-toolbar__overflow--growing {\n  transition: height 0.3s ease, opacity 0.2s linear 0.1s;\n}\n.tox .tox-toolbar__overflow--shrinking {\n  transition: opacity 0.3s ease, height 0.2s linear 0.1s, visibility 0s linear 0.3s;\n}\n.tox .tox-toolbar-overlord,\n.tox .tox-anchorbar {\n  grid-column: 1 / -1;\n}\n.tox .tox-menubar + .tox-toolbar,\n.tox .tox-menubar + .tox-toolbar-overlord {\n  border-top: 1px solid #cccccc;\n  margin-top: -1px;\n  padding-bottom: 0px;\n  padding-top: 0px;\n}\n.tox .tox-toolbar--scrolling {\n  flex-wrap: nowrap;\n  overflow-x: auto;\n}\n.tox .tox-pop .tox-toolbar {\n  border-width: 0;\n}\n.tox .tox-toolbar--no-divider {\n  background-image: none;\n}\n.tox .tox-toolbar-overlord .tox-toolbar:not(.tox-toolbar--scrolling):first-child,\n.tox .tox-toolbar-overlord .tox-toolbar__primary {\n  background-position: center top 39px;\n}\n.tox .tox-editor-header > .tox-toolbar--scrolling,\n.tox .tox-toolbar-overlord .tox-toolbar--scrolling:first-child {\n  background-image: none;\n}\n.tox.tox-tinymce-aux .tox-toolbar__overflow {\n  background-color: #fff;\n  background-position: center top 43px;\n  background-size: calc(100% - 8px * 2) calc(100% - 51px);\n  border: none;\n  border-radius: 3px;\n  box-shadow: 0 0 2px 0 rgba(34, 47, 62, 0.2), 0 4px 8px 0 rgba(34, 47, 62, 0.15);\n  overscroll-behavior: none;\n  padding: 4px 0;\n}\n.tox-pop .tox-pop__dialog {\n  /* stylelint-disable-next-line no-descending-specificity */\n}\n.tox-pop .tox-pop__dialog .tox-toolbar {\n  background-position: center top 43px;\n  background-size: calc(100% - 4px * 2) calc(100% - 51px);\n  padding: 4px 0;\n}\n.tox .tox-toolbar__group {\n  align-items: center;\n  display: flex;\n  flex-wrap: wrap;\n  margin: 0 0;\n  padding: 0 4px 0 4px;\n}\n.tox .tox-toolbar__group--pull-right {\n  margin-left: auto;\n}\n.tox .tox-toolbar--scrolling .tox-toolbar__group {\n  flex-shrink: 0;\n  flex-wrap: nowrap;\n}\n.tox:not([dir=rtl]) .tox-toolbar__group:not(:last-of-type) {\n  border-right: 1px solid #cccccc;\n}\n.tox[dir=rtl] .tox-toolbar__group:not(:last-of-type) {\n  border-left: 1px solid #cccccc;\n}\n.tox .tox-tooltip {\n  display: inline-block;\n  padding: 8px;\n  position: relative;\n}\n.tox .tox-tooltip__body {\n  background-color: #222f3e;\n  border-radius: 3px;\n  box-shadow: 0 2px 4px rgba(34, 47, 62, 0.3);\n  color: rgba(255, 255, 255, 0.75);\n  font-size: 14px;\n  font-style: normal;\n  font-weight: normal;\n  padding: 4px 8px;\n  text-transform: none;\n}\n.tox .tox-tooltip__arrow {\n  position: absolute;\n}\n.tox .tox-tooltip--down .tox-tooltip__arrow {\n  border-left: 8px solid transparent;\n  border-right: 8px solid transparent;\n  border-top: 8px solid #222f3e;\n  bottom: 0;\n  left: 50%;\n  position: absolute;\n  transform: translateX(-50%);\n}\n.tox .tox-tooltip--up .tox-tooltip__arrow {\n  border-bottom: 8px solid #222f3e;\n  border-left: 8px solid transparent;\n  border-right: 8px solid transparent;\n  left: 50%;\n  position: absolute;\n  top: 0;\n  transform: translateX(-50%);\n}\n.tox .tox-tooltip--right .tox-tooltip__arrow {\n  border-bottom: 8px solid transparent;\n  border-left: 8px solid #222f3e;\n  border-top: 8px solid transparent;\n  position: absolute;\n  right: 0;\n  top: 50%;\n  transform: translateY(-50%);\n}\n.tox .tox-tooltip--left .tox-tooltip__arrow {\n  border-bottom: 8px solid transparent;\n  border-right: 8px solid #222f3e;\n  border-top: 8px solid transparent;\n  left: 0;\n  position: absolute;\n  top: 50%;\n  transform: translateY(-50%);\n}\n.tox .tox-tree {\n  display: flex;\n  flex-direction: column;\n}\n.tox .tox-tree .tox-trbtn {\n  align-items: center;\n  background: transparent;\n  border: 0;\n  border-radius: 4px;\n  box-shadow: none;\n  color: #222f3e;\n  display: flex;\n  flex: 0 0 auto;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: normal;\n  height: 28px;\n  margin-bottom: 4px;\n  margin-top: 4px;\n  outline: none;\n  overflow: hidden;\n  padding: 0;\n  padding-left: 8px;\n  text-transform: none;\n}\n.tox .tox-tree .tox-trbtn .tox-tree__label {\n  cursor: default;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n.tox .tox-tree .tox-trbtn svg {\n  display: block;\n  fill: #222f3e;\n}\n.tox .tox-tree .tox-trbtn:focus {\n  background: #dee0e2;\n  border: 0;\n  box-shadow: none;\n}\n.tox .tox-tree .tox-trbtn:hover {\n  background: #dee0e2;\n  border: 0;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-tree .tox-trbtn:hover svg {\n  fill: #222f3e;\n}\n.tox .tox-tree .tox-trbtn:active {\n  background: #b1d0e6;\n  border: 0;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-tree .tox-trbtn:active svg {\n  fill: #222f3e;\n}\n.tox .tox-tree .tox-trbtn--disabled,\n.tox .tox-tree .tox-trbtn--disabled:hover,\n.tox .tox-tree .tox-trbtn:disabled,\n.tox .tox-tree .tox-trbtn:disabled:hover {\n  background: transparent;\n  border: 0;\n  box-shadow: none;\n  color: rgba(34, 47, 62, 0.5);\n  cursor: not-allowed;\n}\n.tox .tox-tree .tox-trbtn--disabled svg,\n.tox .tox-tree .tox-trbtn--disabled:hover svg,\n.tox .tox-tree .tox-trbtn:disabled svg,\n.tox .tox-tree .tox-trbtn:disabled:hover svg {\n  /* stylelint-disable-line no-descending-specificity */\n  fill: rgba(34, 47, 62, 0.5);\n}\n.tox .tox-tree .tox-trbtn--enabled,\n.tox .tox-tree .tox-trbtn--enabled:hover {\n  background: #b1d0e6;\n  border: 0;\n  box-shadow: none;\n  color: #222f3e;\n}\n.tox .tox-tree .tox-trbtn--enabled > *,\n.tox .tox-tree .tox-trbtn--enabled:hover > * {\n  transform: none;\n}\n.tox .tox-tree .tox-trbtn--enabled svg,\n.tox .tox-tree .tox-trbtn--enabled:hover svg {\n  /* stylelint-disable-line no-descending-specificity */\n  fill: #222f3e;\n}\n.tox .tox-tree .tox-trbtn:focus:not(.tox-trbtn--disabled) {\n  color: #222f3e;\n}\n.tox .tox-tree .tox-trbtn:focus:not(.tox-trbtn--disabled) svg {\n  fill: #222f3e;\n}\n.tox .tox-tree .tox-trbtn:active > * {\n  transform: none;\n}\n.tox .tox-tree .tox-trbtn--return {\n  align-self: stretch;\n  height: unset;\n  width: 16px;\n}\n.tox .tox-tree .tox-trbtn--labeled {\n  padding: 0 4px;\n  width: unset;\n}\n.tox .tox-tree .tox-trbtn__vlabel {\n  display: block;\n  font-size: 10px;\n  font-weight: normal;\n  letter-spacing: -0.025em;\n  margin-bottom: 4px;\n  white-space: nowrap;\n}\n.tox .tox-tree .tox-tree--directory {\n  display: flex;\n  flex-direction: column;\n  /* stylelint-disable no-descending-specificity */\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label {\n  font-weight: bold;\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-mbtn {\n  margin-left: auto;\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-mbtn svg {\n  fill: transparent;\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-mbtn.tox-mbtn--active svg,\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-mbtn:focus svg {\n  fill: #222f3e;\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:hover .tox-mbtn svg,\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:focus .tox-mbtn svg {\n  fill: #222f3e;\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:hover:has(.tox-mbtn:hover) {\n  background-color: transparent;\n  color: #222f3e;\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:hover:has(.tox-mbtn:hover) .tox-chevron svg {\n  fill: #222f3e;\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-chevron {\n  margin-right: 6px;\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:has(+ .tox-tree--directory__children--growing) .tox-chevron,\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:has(+ .tox-tree--directory__children--shrinking) .tox-chevron {\n  transition: transform 0.5s ease-in-out;\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:has(+ .tox-tree--directory__children--growing) .tox-chevron,\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:has(+ .tox-tree--directory__children--open) .tox-chevron {\n  transform: rotate(90deg);\n}\n.tox .tox-tree .tox-tree--leaf__label {\n  font-weight: normal;\n}\n.tox .tox-tree .tox-tree--leaf__label .tox-mbtn {\n  margin-left: auto;\n}\n.tox .tox-tree .tox-tree--leaf__label .tox-mbtn svg {\n  fill: transparent;\n}\n.tox .tox-tree .tox-tree--leaf__label .tox-mbtn.tox-mbtn--active svg,\n.tox .tox-tree .tox-tree--leaf__label .tox-mbtn:focus svg {\n  fill: #222f3e;\n}\n.tox .tox-tree .tox-tree--leaf__label:hover .tox-mbtn svg {\n  fill: #222f3e;\n}\n.tox .tox-tree .tox-tree--leaf__label:hover:has(.tox-mbtn:hover) {\n  background-color: transparent;\n  color: #222f3e;\n}\n.tox .tox-tree .tox-tree--leaf__label:hover:has(.tox-mbtn:hover) .tox-chevron svg {\n  fill: #222f3e;\n}\n.tox .tox-tree .tox-tree--directory__children {\n  overflow: hidden;\n  padding-left: 16px;\n}\n.tox .tox-tree .tox-tree--directory__children.tox-tree--directory__children--growing,\n.tox .tox-tree .tox-tree--directory__children.tox-tree--directory__children--shrinking {\n  transition: height 0.5s ease-in-out;\n}\n.tox .tox-tree .tox-trbtn.tox-tree--leaf__label {\n  display: flex;\n  justify-content: space-between;\n}\n.tox .tox-view-wrap,\n.tox .tox-view-wrap__slot-container {\n  background-color: #fff;\n  display: flex;\n  flex: 1;\n  flex-direction: column;\n}\n.tox .tox-view {\n  display: flex;\n  flex: 1 1 auto;\n  flex-direction: column;\n  overflow: hidden;\n}\n.tox .tox-view__header {\n  align-items: center;\n  display: flex;\n  font-size: 16px;\n  justify-content: space-between;\n  padding: 8px 8px 0 8px;\n  position: relative;\n}\n.tox .tox-view--mobile.tox-view__header,\n.tox .tox-view--mobile.tox-view__toolbar {\n  padding: 8px;\n}\n.tox .tox-view--scrolling {\n  flex-wrap: nowrap;\n  overflow-x: auto;\n}\n.tox .tox-view__toolbar {\n  display: flex;\n  flex-direction: row;\n  gap: 8px;\n  justify-content: space-between;\n  padding: 8px 8px 0 8px;\n}\n.tox .tox-view__toolbar__group {\n  display: flex;\n  flex-direction: row;\n  gap: 12px;\n}\n.tox .tox-view__header-start,\n.tox .tox-view__header-end {\n  display: flex;\n}\n.tox .tox-view__pane {\n  height: 100%;\n  padding: 8px;\n  width: 100%;\n}\n.tox .tox-view__pane_panel {\n  border: 1px solid #cccccc;\n  border-radius: 3px;\n}\n.tox:not([dir=rtl]) .tox-view__header .tox-view__header-start > *,\n.tox:not([dir=rtl]) .tox-view__header .tox-view__header-end > * {\n  margin-left: 8px;\n}\n.tox[dir=rtl] .tox-view__header .tox-view__header-start > *,\n.tox[dir=rtl] .tox-view__header .tox-view__header-end > * {\n  margin-right: 8px;\n}\n.tox .tox-well {\n  border: 1px solid #cccccc;\n  border-radius: 3px;\n  padding: 8px;\n  width: 100%;\n}\n.tox .tox-well > *:first-child {\n  margin-top: 0;\n}\n.tox .tox-well > *:last-child {\n  margin-bottom: 0;\n}\n.tox .tox-well > *:only-child {\n  margin: 0;\n}\n.tox .tox-custom-editor {\n  border: 1px solid #cccccc;\n  border-radius: 3px;\n  display: flex;\n  flex: 1;\n  overflow: hidden;\n  position: relative;\n}\n/* stylelint-disable */\n.tox {\n  /* stylelint-enable */\n}\n.tox .tox-dialog-loading::before {\n  background-color: rgba(0, 0, 0, 0.5);\n  content: \"\";\n  height: 100%;\n  position: absolute;\n  width: 100%;\n  z-index: 1000;\n}\n.tox .tox-tab {\n  cursor: pointer;\n}\n.tox .tox-dialog__content-js {\n  display: flex;\n  flex: 1;\n}\n.tox .tox-dialog__body-content .tox-collection {\n  display: flex;\n  flex: 1;\n}\n.tox:not(.tox-tinymce-inline) .tox-editor-header {\n  background-color: none;\n  padding: 0;\n}\n.tox.tox-tinymce--toolbar-bottom .tox-editor-header,\n.tox.tox-tinymce-inline .tox-editor-header {\n  margin-bottom: -1px;\n}\n.tox.tox-tinymce-inline .tox-editor-container {\n  overflow: hidden;\n}\n.tox:not(.tox-tinymce-inline).tox-tinymce--toolbar-bottom .tox-editor-header {\n  border-top: none;\n  box-shadow: none;\n}\n.tox.tox.tox-tinymce--toolbar-sticky-on .tox-editor-header {\n  background-color: transparent;\n  box-shadow: 0 4px 4px -3px rgba(0, 0, 0, 0.25);\n  padding: 0;\n}\n.tox.tox.tox-tinymce--toolbar-sticky-on.tox-tinymce--toolbar-bottom .tox-editor-header {\n  box-shadow: 0 4px 4px -3px rgba(0, 0, 0, 0.25);\n}\n.tox .tox-collection--list .tox-collection__group .tox-insert-table-picker {\n  margin: -4px 0;\n}\n.tox .tox-menu.tox-collection.tox-collection--list {\n  padding: 0;\n}\n.tox .tox-pop {\n  box-shadow: none;\n}\n.tox .tox-tbtn,\n.tox .tox-number-input,\n.tox .tox-tbtn--select,\n.tox .tox-split-button {\n  margin: 2px 0 3px 0;\n}\n.tox .tox-toolbar,\n.tox .tox-toolbar__primary,\n.tox .tox-toolbar__overflow {\n  background: url(\"data:image/svg+xml;charset=utf8,%3Csvg height='39px' viewBox='0 0 40 39px' width='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='38px' width='100' height='1' fill='%23cccccc'/%3E%3C/svg%3E\") left 0 top 0px #fff !important;\n}\n.tox .tox-menubar + .tox-toolbar-overlord {\n  border-top: none;\n}\n.tox .tox-menubar + .tox-toolbar,\n.tox .tox-menubar + .tox-toolbar-overlord .tox-toolbar__primary {\n  border-top: 1px solid #cccccc;\n  margin-top: -1px;\n}\n.tox.tox-tinymce-aux .tox-toolbar__overflow {\n  border: 1px solid #cccccc;\n  padding: 0;\n}\n.tox .tox-pop .tox-pop__dialog .tox-toolbar {\n  padding: 0;\n}\n.tox:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-menubar {\n  border-top: 1px solid #cccccc;\n}\n.tox:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-toolbar-overlord:first-child .tox-toolbar__primary,\n.tox:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-toolbar:first-child {\n  border-top: 1px solid #cccccc;\n}\n.tox .tox-toolbar__group {\n  padding: 0 4px 0 4px;\n}\n.tox .tox-collection__item {\n  border-radius: 0;\n  cursor: pointer;\n}\n.tox .tox-statusbar a:hover:not(:disabled):not([aria-disabled=true]),\n.tox .tox-statusbar__path-item:hover:not(:disabled):not([aria-disabled=true]),\n.tox .tox-statusbar__wordcount:hover:not(:disabled):not([aria-disabled=true]),\n.tox .tox-statusbar a:focus:not(:disabled):not([aria-disabled=true]),\n.tox .tox-statusbar__path-item:focus:not(:disabled):not([aria-disabled=true]),\n.tox .tox-statusbar__wordcount:focus:not(:disabled):not([aria-disabled=true]) {\n  color: rgba(34, 47, 62, 0.7);\n  text-decoration: underline;\n}\n.tox .tox-statusbar__branding svg {\n  vertical-align: -0.25em;\n}\n.tox:not([dir=rtl]) .tox-statusbar__branding {\n  margin-left: 1ch;\n}\n.tox .tox-statusbar__resize-handle {\n  padding-bottom: 0;\n  padding-right: 0;\n}\n.tox .tox-button::before {\n  display: none;\n}\n"
  },
  {
    "path": "minimalist-vue3/public/tinymce/skins/ui/tinymce-5/skin.shadowdom.css",
    "content": "body.tox-dialog__disable-scroll {\n  overflow: hidden;\n}\n.tox-fullscreen {\n  border: 0;\n  height: 100%;\n  margin: 0;\n  overflow: hidden;\n  overscroll-behavior: none;\n  padding: 0;\n  touch-action: pinch-zoom;\n  width: 100%;\n}\n.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle {\n  display: none;\n}\n.tox.tox-tinymce.tox-fullscreen,\n.tox-shadowhost.tox-fullscreen {\n  left: 0;\n  position: fixed;\n  top: 0;\n  z-index: 1200;\n}\n.tox.tox-tinymce.tox-fullscreen {\n  background-color: transparent;\n}\n.tox-fullscreen .tox.tox-tinymce-aux,\n.tox-fullscreen ~ .tox.tox-tinymce-aux {\n  z-index: 1201;\n}\n"
  },
  {
    "path": "minimalist-vue3/public/tinymce/skins/ui/tinymce-5-dark/content.css",
    "content": ".mce-content-body .mce-item-anchor {\n  background: transparent url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'8'%20height%3D'12'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20d%3D'M0%200L8%200%208%2012%204.09117821%209%200%2012z'%20fill%3D%22%23cccccc%22%2F%3E%3C%2Fsvg%3E%0A\") no-repeat center;\n}\n.mce-content-body .mce-item-anchor:empty {\n  cursor: default;\n  display: inline-block;\n  height: 12px !important;\n  padding: 0 2px;\n  -webkit-user-modify: read-only;\n  -moz-user-modify: read-only;\n  -webkit-user-select: all;\n  -moz-user-select: all;\n  user-select: all;\n  width: 8px !important;\n}\n.mce-content-body .mce-item-anchor:not(:empty) {\n  background-position-x: 2px;\n  display: inline-block;\n  padding-left: 12px;\n}\n.mce-content-body .mce-item-anchor[data-mce-selected] {\n  outline-offset: 1px;\n}\n.tox-comments-visible .tox-comment[contenteditable=\"false\"]:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment img:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment > audio:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment > video:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment span.mce-preview-object:not([data-mce-selected]) {\n  outline: 3px solid #ffe89d;\n}\n.tox-comments-visible .tox-comment[contenteditable=\"false\"][data-mce-annotation-active=\"true\"]:not([data-mce-selected]) {\n  outline: 3px solid #fed635;\n}\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"] img:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"] > audio:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"] > video:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"] span.mce-preview-object:not([data-mce-selected]) {\n  outline: 3px solid #fed635;\n}\n.tox-comments-visible span.tox-comment:not([data-mce-selected]) {\n  background-color: #ffe89d;\n  outline: none;\n}\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"]:not([data-mce-selected=\"inline-boundary\"]) {\n  background-color: #fed635;\n}\n.tox-checklist > li:not(.tox-checklist--hidden) {\n  list-style: none;\n  margin: 0.25em 0;\n}\n.tox-checklist > li:not(.tox-checklist--hidden)::before {\n  content: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-unchecked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2215%22%20height%3D%2215%22%20x%3D%22.5%22%20y%3D%22.5%22%20fill-rule%3D%22nonzero%22%20stroke%3D%22%236d737b%22%20rx%3D%222%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A\");\n  cursor: pointer;\n  height: 1em;\n  margin-left: -1.5em;\n  margin-top: 0.125em;\n  position: absolute;\n  width: 1em;\n}\n.tox-checklist li:not(.tox-checklist--hidden).tox-checklist--checked::before {\n  content: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-checked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22%234099FF%22%20fill-rule%3D%22nonzero%22%20rx%3D%222%22%2F%3E%3Cpath%20id%3D%22Path%22%20fill%3D%22%23FFF%22%20fill-rule%3D%22nonzero%22%20d%3D%22M11.5703186%2C3.14417309%20C11.8516238%2C2.73724603%2012.4164781%2C2.62829933%2012.83558%2C2.89774797%20C13.260121%2C3.17069355%2013.3759736%2C3.72932262%2013.0909105%2C4.14168582%20L7.7580587%2C11.8560195%20C7.43776896%2C12.3193404%206.76483983%2C12.3852142%206.35607322%2C11.9948725%20L3.02491697%2C8.8138662%20C2.66090143%2C8.46625845%202.65798871%2C7.89594698%203.01850234%2C7.54483354%20C3.373942%2C7.19866177%203.94940006%2C7.19592841%204.30829608%2C7.5386474%20L6.85276923%2C9.9684299%20L11.5703186%2C3.14417309%20Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A\");\n}\n[dir=rtl] .tox-checklist > li:not(.tox-checklist--hidden)::before {\n  margin-left: 0;\n  margin-right: -1.5em;\n}\n/* stylelint-disable */\n/* http://prismjs.com/ */\n/**\n * Dracula Theme originally by Zeno Rocha [@zenorocha]\n * https://draculatheme.com/\n *\n * Ported for PrismJS by Albert Vallverdu [@byverdu]\n */\ncode[class*=\"language-\"],\npre[class*=\"language-\"] {\n  color: #f8f8f2;\n  background: none;\n  text-shadow: 0 1px rgba(0, 0, 0, 0.3);\n  font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;\n  text-align: left;\n  white-space: pre;\n  word-spacing: normal;\n  word-break: normal;\n  word-wrap: normal;\n  line-height: 1.5;\n  -moz-tab-size: 4;\n  tab-size: 4;\n  -webkit-hyphens: none;\n  hyphens: none;\n}\n/* Code blocks */\npre[class*=\"language-\"] {\n  padding: 1em;\n  margin: 0.5em 0;\n  overflow: auto;\n  border-radius: 0.3em;\n}\n:not(pre) > code[class*=\"language-\"],\npre[class*=\"language-\"] {\n  background: #282a36;\n}\n/* Inline code */\n:not(pre) > code[class*=\"language-\"] {\n  padding: 0.1em;\n  border-radius: 0.3em;\n  white-space: normal;\n}\n.token.comment,\n.token.prolog,\n.token.doctype,\n.token.cdata {\n  color: #6272a4;\n}\n.token.punctuation {\n  color: #f8f8f2;\n}\n.namespace {\n  opacity: 0.7;\n}\n.token.property,\n.token.tag,\n.token.constant,\n.token.symbol,\n.token.deleted {\n  color: #ff79c6;\n}\n.token.boolean,\n.token.number {\n  color: #bd93f9;\n}\n.token.selector,\n.token.attr-name,\n.token.string,\n.token.char,\n.token.builtin,\n.token.inserted {\n  color: #50fa7b;\n}\n.token.operator,\n.token.entity,\n.token.url,\n.language-css .token.string,\n.style .token.string,\n.token.variable {\n  color: #f8f8f2;\n}\n.token.atrule,\n.token.attr-value,\n.token.function,\n.token.class-name {\n  color: #f1fa8c;\n}\n.token.keyword {\n  color: #8be9fd;\n}\n.token.regex,\n.token.important {\n  color: #ffb86c;\n}\n.token.important,\n.token.bold {\n  font-weight: bold;\n}\n.token.italic {\n  font-style: italic;\n}\n.token.entity {\n  cursor: help;\n}\n/* stylelint-enable */\n.mce-content-body {\n  overflow-wrap: break-word;\n  word-wrap: break-word;\n}\n.mce-content-body .mce-visual-caret {\n  background-color: black;\n  background-color: currentColor;\n  position: absolute;\n}\n.mce-content-body .mce-visual-caret-hidden {\n  display: none;\n}\n.mce-content-body *[data-mce-caret] {\n  left: -1000px;\n  margin: 0;\n  padding: 0;\n  position: absolute;\n  right: auto;\n  top: 0;\n}\n.mce-content-body .mce-offscreen-selection {\n  left: -2000000px;\n  max-width: 1000000px;\n  position: absolute;\n}\n.mce-content-body *[contentEditable=false] {\n  cursor: default;\n}\n.mce-content-body *[contentEditable=true] {\n  cursor: text;\n}\n.tox-cursor-format-painter {\n  cursor: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A\"), default;\n}\ndiv.mce-footnotes hr {\n  margin-inline-end: auto;\n  margin-inline-start: 0;\n  width: 25%;\n}\ndiv.mce-footnotes li > a.mce-footnotes-backlink {\n  text-decoration: none;\n}\n@media print {\n  sup.mce-footnote a {\n    color: black;\n    text-decoration: none;\n  }\n  div.mce-footnotes {\n    break-inside: avoid;\n    width: 100%;\n  }\n  div.mce-footnotes li > a.mce-footnotes-backlink {\n    display: none;\n  }\n}\n.mce-content-body figure.align-left {\n  float: left;\n}\n.mce-content-body figure.align-right {\n  float: right;\n}\n.mce-content-body figure.image.align-center {\n  display: table;\n  margin-left: auto;\n  margin-right: auto;\n}\n.mce-preview-object {\n  border: 1px solid gray;\n  display: inline-block;\n  line-height: 0;\n  margin: 0 2px 0 2px;\n  position: relative;\n}\n.mce-preview-object .mce-shim {\n  background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);\n  height: 100%;\n  left: 0;\n  position: absolute;\n  top: 0;\n  width: 100%;\n}\n.mce-preview-object[data-mce-selected=\"2\"] .mce-shim {\n  display: none;\n}\n.mce-content-body .mce-mergetag {\n  cursor: default !important;\n  -webkit-user-select: none;\n     -moz-user-select: none;\n          user-select: none;\n}\n.mce-content-body .mce-mergetag:hover {\n  background-color: rgba(0, 108, 231, 0.3);\n}\n.mce-content-body .mce-mergetag-affix {\n  background-color: rgba(0, 108, 231, 0.3);\n  color: #006ce7;\n}\n.mce-object {\n  background: transparent url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%20fill%3D%22%23cccccc%22%2F%3E%3C%2Fsvg%3E%0A\") no-repeat center;\n  border: 1px dashed #aaa;\n}\n.mce-pagebreak {\n  border: 1px dashed #aaa;\n  cursor: default;\n  display: block;\n  height: 5px;\n  margin-top: 15px;\n  page-break-before: always;\n  width: 100%;\n}\n@media print {\n  .mce-pagebreak {\n    border: 0;\n  }\n}\n.tiny-pageembed .mce-shim {\n  background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);\n  height: 100%;\n  left: 0;\n  position: absolute;\n  top: 0;\n  width: 100%;\n}\n.tiny-pageembed[data-mce-selected=\"2\"] .mce-shim {\n  display: none;\n}\n.tiny-pageembed {\n  display: inline-block;\n  position: relative;\n}\n.tiny-pageembed--21by9,\n.tiny-pageembed--16by9,\n.tiny-pageembed--4by3,\n.tiny-pageembed--1by1 {\n  display: block;\n  overflow: hidden;\n  padding: 0;\n  position: relative;\n  width: 100%;\n}\n.tiny-pageembed--21by9 {\n  padding-top: 42.857143%;\n}\n.tiny-pageembed--16by9 {\n  padding-top: 56.25%;\n}\n.tiny-pageembed--4by3 {\n  padding-top: 75%;\n}\n.tiny-pageembed--1by1 {\n  padding-top: 100%;\n}\n.tiny-pageembed--21by9 iframe,\n.tiny-pageembed--16by9 iframe,\n.tiny-pageembed--4by3 iframe,\n.tiny-pageembed--1by1 iframe {\n  border: 0;\n  height: 100%;\n  left: 0;\n  position: absolute;\n  top: 0;\n  width: 100%;\n}\n.mce-content-body[data-mce-placeholder] {\n  position: relative;\n}\n.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before {\n  color: rgba(34, 47, 62, 0.7);\n  content: attr(data-mce-placeholder);\n  position: absolute;\n}\n.mce-content-body:not([dir=rtl])[data-mce-placeholder]:not(.mce-visualblocks)::before {\n  left: 1px;\n}\n.mce-content-body[dir=rtl][data-mce-placeholder]:not(.mce-visualblocks)::before {\n  right: 1px;\n}\n.mce-content-body div.mce-resizehandle {\n  background-color: #4099ff;\n  border-color: #4099ff;\n  border-style: solid;\n  border-width: 1px;\n  box-sizing: border-box;\n  height: 10px;\n  position: absolute;\n  width: 10px;\n  z-index: 1298;\n}\n.mce-content-body div.mce-resizehandle:hover {\n  background-color: #4099ff;\n}\n.mce-content-body div.mce-resizehandle:nth-of-type(1) {\n  cursor: nwse-resize;\n}\n.mce-content-body div.mce-resizehandle:nth-of-type(2) {\n  cursor: nesw-resize;\n}\n.mce-content-body div.mce-resizehandle:nth-of-type(3) {\n  cursor: nwse-resize;\n}\n.mce-content-body div.mce-resizehandle:nth-of-type(4) {\n  cursor: nesw-resize;\n}\n.mce-content-body .mce-resize-backdrop {\n  z-index: 10000;\n}\n.mce-content-body .mce-clonedresizable {\n  cursor: default;\n  opacity: 0.5;\n  outline: 1px dashed black;\n  position: absolute;\n  z-index: 10001;\n}\n.mce-content-body .mce-clonedresizable.mce-resizetable-columns th,\n.mce-content-body .mce-clonedresizable.mce-resizetable-columns td {\n  border: 0;\n}\n.mce-content-body .mce-resize-helper {\n  background: #555;\n  background: rgba(0, 0, 0, 0.75);\n  border: 1px;\n  border-radius: 3px;\n  color: white;\n  display: none;\n  font-family: sans-serif;\n  font-size: 12px;\n  line-height: 14px;\n  margin: 5px 10px;\n  padding: 5px;\n  position: absolute;\n  white-space: nowrap;\n  z-index: 10002;\n}\n.tox-rtc-user-selection {\n  position: relative;\n}\n.tox-rtc-user-cursor {\n  bottom: 0;\n  cursor: default;\n  position: absolute;\n  top: 0;\n  width: 2px;\n}\n.tox-rtc-user-cursor::before {\n  background-color: inherit;\n  border-radius: 50%;\n  content: '';\n  display: block;\n  height: 8px;\n  position: absolute;\n  right: -3px;\n  top: -3px;\n  width: 8px;\n}\n.tox-rtc-user-cursor:hover::after {\n  background-color: inherit;\n  border-radius: 100px;\n  box-sizing: border-box;\n  color: #fff;\n  content: attr(data-user);\n  display: block;\n  font-size: 12px;\n  font-weight: bold;\n  left: -5px;\n  min-height: 8px;\n  min-width: 8px;\n  padding: 0 12px;\n  position: absolute;\n  top: -11px;\n  white-space: nowrap;\n  z-index: 1000;\n}\n.tox-rtc-user-selection--1 .tox-rtc-user-cursor {\n  background-color: #2dc26b;\n}\n.tox-rtc-user-selection--2 .tox-rtc-user-cursor {\n  background-color: #e03e2d;\n}\n.tox-rtc-user-selection--3 .tox-rtc-user-cursor {\n  background-color: #f1c40f;\n}\n.tox-rtc-user-selection--4 .tox-rtc-user-cursor {\n  background-color: #3598db;\n}\n.tox-rtc-user-selection--5 .tox-rtc-user-cursor {\n  background-color: #b96ad9;\n}\n.tox-rtc-user-selection--6 .tox-rtc-user-cursor {\n  background-color: #e67e23;\n}\n.tox-rtc-user-selection--7 .tox-rtc-user-cursor {\n  background-color: #aaa69d;\n}\n.tox-rtc-user-selection--8 .tox-rtc-user-cursor {\n  background-color: #f368e0;\n}\n.tox-rtc-remote-image {\n  background: #eaeaea url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%2236%22%20height%3D%2212%22%20viewBox%3D%220%200%2036%2012%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%3Ccircle%20cx%3D%226%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2218%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.33s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2230%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.66s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%3C%2Fsvg%3E%0A\") no-repeat center center;\n  border: 1px solid #ccc;\n  min-height: 240px;\n  min-width: 320px;\n}\n.mce-match-marker {\n  background: #aaa;\n  color: #fff;\n}\n.mce-match-marker-selected {\n  background: #39f;\n  color: #fff;\n}\n.mce-match-marker-selected::-moz-selection {\n  background: #39f;\n  color: #fff;\n}\n.mce-match-marker-selected::selection {\n  background: #39f;\n  color: #fff;\n}\n.mce-content-body img[data-mce-selected],\n.mce-content-body video[data-mce-selected],\n.mce-content-body audio[data-mce-selected],\n.mce-content-body object[data-mce-selected],\n.mce-content-body embed[data-mce-selected],\n.mce-content-body table[data-mce-selected],\n.mce-content-body details[data-mce-selected] {\n  outline: 3px solid #4099ff;\n}\n.mce-content-body hr[data-mce-selected] {\n  outline: 3px solid #4099ff;\n  outline-offset: 1px;\n}\n.mce-content-body *[contentEditable=false] *[contentEditable=true]:focus {\n  outline: 3px solid #4099ff;\n}\n.mce-content-body *[contentEditable=false] *[contentEditable=true]:hover {\n  outline: 3px solid #4099ff;\n}\n.mce-content-body *[contentEditable=false][data-mce-selected] {\n  cursor: not-allowed;\n  outline: 3px solid #4099ff;\n}\n.mce-content-body.mce-content-readonly *[contentEditable=true]:focus,\n.mce-content-body.mce-content-readonly *[contentEditable=true]:hover {\n  outline: none;\n}\n.mce-content-body *[data-mce-selected=\"inline-boundary\"] {\n  background-color: #4099ff;\n}\n.mce-content-body .mce-edit-focus {\n  outline: 3px solid #4099ff;\n}\n.mce-content-body td[data-mce-selected],\n.mce-content-body th[data-mce-selected] {\n  position: relative;\n}\n.mce-content-body td[data-mce-selected]::-moz-selection,\n.mce-content-body th[data-mce-selected]::-moz-selection {\n  background: none;\n}\n.mce-content-body td[data-mce-selected]::selection,\n.mce-content-body th[data-mce-selected]::selection {\n  background: none;\n}\n.mce-content-body td[data-mce-selected] *,\n.mce-content-body th[data-mce-selected] * {\n  outline: none;\n  -webkit-touch-callout: none;\n  -webkit-user-select: none;\n     -moz-user-select: none;\n          user-select: none;\n}\n.mce-content-body td[data-mce-selected]::after,\n.mce-content-body th[data-mce-selected]::after {\n  background-color: rgba(180, 215, 255, 0.7);\n  border: 1px solid transparent;\n  bottom: -1px;\n  content: '';\n  left: -1px;\n  mix-blend-mode: lighten;\n  position: absolute;\n  right: -1px;\n  top: -1px;\n}\n@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {\n  .mce-content-body td[data-mce-selected]::after,\n  .mce-content-body th[data-mce-selected]::after {\n    border-color: rgba(0, 84, 180, 0.7);\n  }\n}\n.mce-content-body img[data-mce-selected]::-moz-selection {\n  background: none;\n}\n.mce-content-body img[data-mce-selected]::selection {\n  background: none;\n}\n.ephox-snooker-resizer-bar {\n  background-color: #4099ff;\n  opacity: 0;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  user-select: none;\n}\n.ephox-snooker-resizer-cols {\n  cursor: col-resize;\n}\n.ephox-snooker-resizer-rows {\n  cursor: row-resize;\n}\n.ephox-snooker-resizer-bar.ephox-snooker-resizer-bar-dragging {\n  opacity: 1;\n}\n.mce-spellchecker-word {\n  background-image: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%23ff0000'%20fill%3D'none'%20stroke-linecap%3D'round'%20stroke-opacity%3D'.75'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A\");\n  background-position: 0 calc(100% + 1px);\n  background-repeat: repeat-x;\n  background-size: auto 6px;\n  cursor: default;\n  height: 2rem;\n}\n.mce-spellchecker-grammar {\n  background-image: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%2300A835'%20fill%3D'none'%20stroke-linecap%3D'round'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A\");\n  background-position: 0 calc(100% + 1px);\n  background-repeat: repeat-x;\n  background-size: auto 6px;\n  cursor: default;\n}\n.mce-toc {\n  border: 1px solid gray;\n}\n.mce-toc h2 {\n  margin: 4px;\n}\n.mce-toc li {\n  list-style-type: none;\n}\n[data-mce-block] {\n  display: block;\n}\ntable[style*=\"border-width: 0px\"],\n.mce-item-table:not([border]),\n.mce-item-table[border=\"0\"],\ntable[style*=\"border-width: 0px\"] td,\n.mce-item-table:not([border]) td,\n.mce-item-table[border=\"0\"] td,\ntable[style*=\"border-width: 0px\"] th,\n.mce-item-table:not([border]) th,\n.mce-item-table[border=\"0\"] th,\ntable[style*=\"border-width: 0px\"] caption,\n.mce-item-table:not([border]) caption,\n.mce-item-table[border=\"0\"] caption {\n  border: 1px dashed #bbb;\n}\n.mce-visualblocks p,\n.mce-visualblocks h1,\n.mce-visualblocks h2,\n.mce-visualblocks h3,\n.mce-visualblocks h4,\n.mce-visualblocks h5,\n.mce-visualblocks h6,\n.mce-visualblocks div:not([data-mce-bogus]),\n.mce-visualblocks section,\n.mce-visualblocks article,\n.mce-visualblocks blockquote,\n.mce-visualblocks address,\n.mce-visualblocks pre,\n.mce-visualblocks figure,\n.mce-visualblocks figcaption,\n.mce-visualblocks hgroup,\n.mce-visualblocks aside,\n.mce-visualblocks ul,\n.mce-visualblocks ol,\n.mce-visualblocks dl {\n  background-repeat: no-repeat;\n  border: 1px dashed #bbb;\n  margin-left: 3px;\n  padding-top: 10px;\n}\n.mce-visualblocks p {\n  background-image: url(data:image/gif;base64,R0lGODlhCQAJAJEAAAAAAP///7u7u////yH5BAEAAAMALAAAAAAJAAkAAAIQnG+CqCN/mlyvsRUpThG6AgA7);\n}\n.mce-visualblocks h1 {\n  background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGu1JuxHoAfRNRW3TWXyF2YiRUAOw==);\n}\n.mce-visualblocks h2 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8Hybbx4oOuqgTynJd6bGlWg3DkJzoaUAAAOw==);\n}\n.mce-visualblocks h3 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIZjI8Hybbx4oOuqgTynJf2Ln2NOHpQpmhAAQA7);\n}\n.mce-visualblocks h4 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxInR0zqeAdhtJlXwV1oCll2HaWgAAOw==);\n}\n.mce-visualblocks h5 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjane4iq5GlW05GgIkIZUAAAOw==);\n}\n.mce-visualblocks h6 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjan04jep1iZ1XRlAo5bVgAAOw==);\n}\n.mce-visualblocks div:not([data-mce-bogus]) {\n  background-image: url(data:image/gif;base64,R0lGODlhEgAKAIABALu7u////yH5BAEAAAEALAAAAAASAAoAAAIfjI9poI0cgDywrhuxfbrzDEbQM2Ei5aRjmoySW4pAAQA7);\n}\n.mce-visualblocks section {\n  background-image: url(data:image/gif;base64,R0lGODlhKAAKAIABALu7u////yH5BAEAAAEALAAAAAAoAAoAAAI5jI+pywcNY3sBWHdNrplytD2ellDeSVbp+GmWqaDqDMepc8t17Y4vBsK5hDyJMcI6KkuYU+jpjLoKADs=);\n}\n.mce-visualblocks article {\n  background-image: url(data:image/gif;base64,R0lGODlhKgAKAIABALu7u////yH5BAEAAAEALAAAAAAqAAoAAAI6jI+pywkNY3wG0GBvrsd2tXGYSGnfiF7ikpXemTpOiJScasYoDJJrjsG9gkCJ0ag6KhmaIe3pjDYBBQA7);\n}\n.mce-visualblocks blockquote {\n  background-image: url(data:image/gif;base64,R0lGODlhPgAKAIABALu7u////yH5BAEAAAEALAAAAAA+AAoAAAJPjI+py+0Knpz0xQDyuUhvfoGgIX5iSKZYgq5uNL5q69asZ8s5rrf0yZmpNkJZzFesBTu8TOlDVAabUyatguVhWduud3EyiUk45xhTTgMBBQA7);\n}\n.mce-visualblocks address {\n  background-image: url(data:image/gif;base64,R0lGODlhLQAKAIABALu7u////yH5BAEAAAEALAAAAAAtAAoAAAI/jI+pywwNozSP1gDyyZcjb3UaRpXkWaXmZW4OqKLhBmLs+K263DkJK7OJeifh7FicKD9A1/IpGdKkyFpNmCkAADs=);\n}\n.mce-visualblocks pre {\n  background-image: url(data:image/gif;base64,R0lGODlhFQAKAIABALu7uwAAACH5BAEAAAEALAAAAAAVAAoAAAIjjI+ZoN0cgDwSmnpz1NCueYERhnibZVKLNnbOq8IvKpJtVQAAOw==);\n}\n.mce-visualblocks figure {\n  background-image: url(data:image/gif;base64,R0lGODlhJAAKAIAAALu7u////yH5BAEAAAEALAAAAAAkAAoAAAI0jI+py+2fwAHUSFvD3RlvG4HIp4nX5JFSpnZUJ6LlrM52OE7uSWosBHScgkSZj7dDKnWAAgA7);\n}\n.mce-visualblocks figcaption {\n  border: 1px dashed #bbb;\n}\n.mce-visualblocks hgroup {\n  background-image: url(data:image/gif;base64,R0lGODlhJwAKAIABALu7uwAAACH5BAEAAAEALAAAAAAnAAoAAAI3jI+pywYNI3uB0gpsRtt5fFnfNZaVSYJil4Wo03Hv6Z62uOCgiXH1kZIIJ8NiIxRrAZNMZAtQAAA7);\n}\n.mce-visualblocks aside {\n  background-image: url(data:image/gif;base64,R0lGODlhHgAKAIABAKqqqv///yH5BAEAAAEALAAAAAAeAAoAAAItjI+pG8APjZOTzgtqy7I3f1yehmQcFY4WKZbqByutmW4aHUd6vfcVbgudgpYCADs=);\n}\n.mce-visualblocks ul {\n  background-image: url(data:image/gif;base64,R0lGODlhDQAKAIAAALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGuYnqUVSjvw26DzzXiqIDlVwAAOw==);\n}\n.mce-visualblocks ol {\n  background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybH6HHt0qourxC6CvzXieHyeWQAAOw==);\n}\n.mce-visualblocks dl {\n  background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybEOnmOvUoWznTqeuEjNSCqeGRUAOw==);\n}\n.mce-visualblocks:not([dir=rtl]) p,\n.mce-visualblocks:not([dir=rtl]) h1,\n.mce-visualblocks:not([dir=rtl]) h2,\n.mce-visualblocks:not([dir=rtl]) h3,\n.mce-visualblocks:not([dir=rtl]) h4,\n.mce-visualblocks:not([dir=rtl]) h5,\n.mce-visualblocks:not([dir=rtl]) h6,\n.mce-visualblocks:not([dir=rtl]) div:not([data-mce-bogus]),\n.mce-visualblocks:not([dir=rtl]) section,\n.mce-visualblocks:not([dir=rtl]) article,\n.mce-visualblocks:not([dir=rtl]) blockquote,\n.mce-visualblocks:not([dir=rtl]) address,\n.mce-visualblocks:not([dir=rtl]) pre,\n.mce-visualblocks:not([dir=rtl]) figure,\n.mce-visualblocks:not([dir=rtl]) figcaption,\n.mce-visualblocks:not([dir=rtl]) hgroup,\n.mce-visualblocks:not([dir=rtl]) aside,\n.mce-visualblocks:not([dir=rtl]) ul,\n.mce-visualblocks:not([dir=rtl]) ol,\n.mce-visualblocks:not([dir=rtl]) dl {\n  margin-left: 3px;\n}\n.mce-visualblocks[dir=rtl] p,\n.mce-visualblocks[dir=rtl] h1,\n.mce-visualblocks[dir=rtl] h2,\n.mce-visualblocks[dir=rtl] h3,\n.mce-visualblocks[dir=rtl] h4,\n.mce-visualblocks[dir=rtl] h5,\n.mce-visualblocks[dir=rtl] h6,\n.mce-visualblocks[dir=rtl] div:not([data-mce-bogus]),\n.mce-visualblocks[dir=rtl] section,\n.mce-visualblocks[dir=rtl] article,\n.mce-visualblocks[dir=rtl] blockquote,\n.mce-visualblocks[dir=rtl] address,\n.mce-visualblocks[dir=rtl] pre,\n.mce-visualblocks[dir=rtl] figure,\n.mce-visualblocks[dir=rtl] figcaption,\n.mce-visualblocks[dir=rtl] hgroup,\n.mce-visualblocks[dir=rtl] aside,\n.mce-visualblocks[dir=rtl] ul,\n.mce-visualblocks[dir=rtl] ol,\n.mce-visualblocks[dir=rtl] dl {\n  background-position-x: right;\n  margin-right: 3px;\n}\n.mce-nbsp,\n.mce-shy {\n  background: #aaa;\n}\n.mce-shy::after {\n  content: '-';\n}\nbody {\n  font-family: sans-serif;\n}\ntable {\n  border-collapse: collapse;\n}\n"
  },
  {
    "path": "minimalist-vue3/public/tinymce/skins/ui/tinymce-5-dark/content.inline.css",
    "content": ".mce-content-body .mce-item-anchor {\n  background: transparent url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'8'%20height%3D'12'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20d%3D'M0%200L8%200%208%2012%204.09117821%209%200%2012z'%2F%3E%3C%2Fsvg%3E%0A\") no-repeat center;\n}\n.mce-content-body .mce-item-anchor:empty {\n  cursor: default;\n  display: inline-block;\n  height: 12px !important;\n  padding: 0 2px;\n  -webkit-user-modify: read-only;\n  -moz-user-modify: read-only;\n  -webkit-user-select: all;\n  -moz-user-select: all;\n  user-select: all;\n  width: 8px !important;\n}\n.mce-content-body .mce-item-anchor:not(:empty) {\n  background-position-x: 2px;\n  display: inline-block;\n  padding-left: 12px;\n}\n.mce-content-body .mce-item-anchor[data-mce-selected] {\n  outline-offset: 1px;\n}\n.tox-comments-visible .tox-comment[contenteditable=\"false\"]:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment img:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment > audio:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment > video:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment span.mce-preview-object:not([data-mce-selected]) {\n  outline: 3px solid #ffe89d;\n}\n.tox-comments-visible .tox-comment[contenteditable=\"false\"][data-mce-annotation-active=\"true\"]:not([data-mce-selected]) {\n  outline: 3px solid #fed635;\n}\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"] img:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"] > audio:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"] > video:not([data-mce-selected]),\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"] span.mce-preview-object:not([data-mce-selected]) {\n  outline: 3px solid #fed635;\n}\n.tox-comments-visible span.tox-comment:not([data-mce-selected]) {\n  background-color: #ffe89d;\n  outline: none;\n}\n.tox-comments-visible span.tox-comment[data-mce-annotation-active=\"true\"]:not([data-mce-selected=\"inline-boundary\"]) {\n  background-color: #fed635;\n}\n.tox-checklist > li:not(.tox-checklist--hidden) {\n  list-style: none;\n  margin: 0.25em 0;\n}\n.tox-checklist > li:not(.tox-checklist--hidden)::before {\n  content: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-unchecked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2215%22%20height%3D%2215%22%20x%3D%22.5%22%20y%3D%22.5%22%20fill-rule%3D%22nonzero%22%20stroke%3D%22%234C4C4C%22%20rx%3D%222%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A\");\n  cursor: pointer;\n  height: 1em;\n  margin-left: -1.5em;\n  margin-top: 0.125em;\n  position: absolute;\n  width: 1em;\n}\n.tox-checklist li:not(.tox-checklist--hidden).tox-checklist--checked::before {\n  content: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-checked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22%234099FF%22%20fill-rule%3D%22nonzero%22%20rx%3D%222%22%2F%3E%3Cpath%20id%3D%22Path%22%20fill%3D%22%23FFF%22%20fill-rule%3D%22nonzero%22%20d%3D%22M11.5703186%2C3.14417309%20C11.8516238%2C2.73724603%2012.4164781%2C2.62829933%2012.83558%2C2.89774797%20C13.260121%2C3.17069355%2013.3759736%2C3.72932262%2013.0909105%2C4.14168582%20L7.7580587%2C11.8560195%20C7.43776896%2C12.3193404%206.76483983%2C12.3852142%206.35607322%2C11.9948725%20L3.02491697%2C8.8138662%20C2.66090143%2C8.46625845%202.65798871%2C7.89594698%203.01850234%2C7.54483354%20C3.373942%2C7.19866177%203.94940006%2C7.19592841%204.30829608%2C7.5386474%20L6.85276923%2C9.9684299%20L11.5703186%2C3.14417309%20Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A\");\n}\n[dir=rtl] .tox-checklist > li:not(.tox-checklist--hidden)::before {\n  margin-left: 0;\n  margin-right: -1.5em;\n}\n/* stylelint-disable */\n/* http://prismjs.com/ */\n/**\n * prism.js default theme for JavaScript, CSS and HTML\n * Based on dabblet (http://dabblet.com)\n * @author Lea Verou\n */\ncode[class*=\"language-\"],\npre[class*=\"language-\"] {\n  color: black;\n  background: none;\n  text-shadow: 0 1px white;\n  font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;\n  font-size: 1em;\n  text-align: left;\n  white-space: pre;\n  word-spacing: normal;\n  word-break: normal;\n  word-wrap: normal;\n  line-height: 1.5;\n  -moz-tab-size: 4;\n  tab-size: 4;\n  -webkit-hyphens: none;\n  hyphens: none;\n}\npre[class*=\"language-\"]::-moz-selection,\npre[class*=\"language-\"] ::-moz-selection,\ncode[class*=\"language-\"]::-moz-selection,\ncode[class*=\"language-\"] ::-moz-selection {\n  text-shadow: none;\n  background: #b3d4fc;\n}\npre[class*=\"language-\"]::selection,\npre[class*=\"language-\"] ::selection,\ncode[class*=\"language-\"]::selection,\ncode[class*=\"language-\"] ::selection {\n  text-shadow: none;\n  background: #b3d4fc;\n}\n@media print {\n  code[class*=\"language-\"],\n  pre[class*=\"language-\"] {\n    text-shadow: none;\n  }\n}\n/* Code blocks */\npre[class*=\"language-\"] {\n  padding: 1em;\n  margin: 0.5em 0;\n  overflow: auto;\n}\n:not(pre) > code[class*=\"language-\"],\npre[class*=\"language-\"] {\n  background: #f5f2f0;\n}\n/* Inline code */\n:not(pre) > code[class*=\"language-\"] {\n  padding: 0.1em;\n  border-radius: 0.3em;\n  white-space: normal;\n}\n.token.comment,\n.token.prolog,\n.token.doctype,\n.token.cdata {\n  color: slategray;\n}\n.token.punctuation {\n  color: #999;\n}\n.token.namespace {\n  opacity: 0.7;\n}\n.token.property,\n.token.tag,\n.token.boolean,\n.token.number,\n.token.constant,\n.token.symbol,\n.token.deleted {\n  color: #905;\n}\n.token.selector,\n.token.attr-name,\n.token.string,\n.token.char,\n.token.builtin,\n.token.inserted {\n  color: #690;\n}\n.token.operator,\n.token.entity,\n.token.url,\n.language-css .token.string,\n.style .token.string {\n  color: #9a6e3a;\n  /* This background color was intended by the author of this theme. */\n  background: hsla(0, 0%, 100%, 0.5);\n}\n.token.atrule,\n.token.attr-value,\n.token.keyword {\n  color: #07a;\n}\n.token.function,\n.token.class-name {\n  color: #DD4A68;\n}\n.token.regex,\n.token.important,\n.token.variable {\n  color: #e90;\n}\n.token.important,\n.token.bold {\n  font-weight: bold;\n}\n.token.italic {\n  font-style: italic;\n}\n.token.entity {\n  cursor: help;\n}\n/* stylelint-enable */\n.mce-content-body {\n  overflow-wrap: break-word;\n  word-wrap: break-word;\n}\n.mce-content-body .mce-visual-caret {\n  background-color: black;\n  background-color: currentColor;\n  position: absolute;\n}\n.mce-content-body .mce-visual-caret-hidden {\n  display: none;\n}\n.mce-content-body *[data-mce-caret] {\n  left: -1000px;\n  margin: 0;\n  padding: 0;\n  position: absolute;\n  right: auto;\n  top: 0;\n}\n.mce-content-body .mce-offscreen-selection {\n  left: -2000000px;\n  max-width: 1000000px;\n  position: absolute;\n}\n.mce-content-body *[contentEditable=false] {\n  cursor: default;\n}\n.mce-content-body *[contentEditable=true] {\n  cursor: text;\n}\n.tox-cursor-format-painter {\n  cursor: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A\"), default;\n}\ndiv.mce-footnotes hr {\n  margin-inline-end: auto;\n  margin-inline-start: 0;\n  width: 25%;\n}\ndiv.mce-footnotes li > a.mce-footnotes-backlink {\n  text-decoration: none;\n}\n@media print {\n  sup.mce-footnote a {\n    color: black;\n    text-decoration: none;\n  }\n  div.mce-footnotes {\n    break-inside: avoid;\n    width: 100%;\n  }\n  div.mce-footnotes li > a.mce-footnotes-backlink {\n    display: none;\n  }\n}\n.mce-content-body figure.align-left {\n  float: left;\n}\n.mce-content-body figure.align-right {\n  float: right;\n}\n.mce-content-body figure.image.align-center {\n  display: table;\n  margin-left: auto;\n  margin-right: auto;\n}\n.mce-preview-object {\n  border: 1px solid gray;\n  display: inline-block;\n  line-height: 0;\n  margin: 0 2px 0 2px;\n  position: relative;\n}\n.mce-preview-object .mce-shim {\n  background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);\n  height: 100%;\n  left: 0;\n  position: absolute;\n  top: 0;\n  width: 100%;\n}\n.mce-preview-object[data-mce-selected=\"2\"] .mce-shim {\n  display: none;\n}\n.mce-content-body .mce-mergetag {\n  cursor: default !important;\n  -webkit-user-select: none;\n     -moz-user-select: none;\n          user-select: none;\n}\n.mce-content-body .mce-mergetag:hover {\n  background-color: rgba(0, 108, 231, 0.1);\n}\n.mce-content-body .mce-mergetag-affix {\n  background-color: rgba(0, 108, 231, 0.1);\n  color: #006ce7;\n}\n.mce-object {\n  background: transparent url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%2F%3E%3C%2Fsvg%3E%0A\") no-repeat center;\n  border: 1px dashed #aaa;\n}\n.mce-pagebreak {\n  border: 1px dashed #aaa;\n  cursor: default;\n  display: block;\n  height: 5px;\n  margin-top: 15px;\n  page-break-before: always;\n  width: 100%;\n}\n@media print {\n  .mce-pagebreak {\n    border: 0;\n  }\n}\n.tiny-pageembed .mce-shim {\n  background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);\n  height: 100%;\n  left: 0;\n  position: absolute;\n  top: 0;\n  width: 100%;\n}\n.tiny-pageembed[data-mce-selected=\"2\"] .mce-shim {\n  display: none;\n}\n.tiny-pageembed {\n  display: inline-block;\n  position: relative;\n}\n.tiny-pageembed--21by9,\n.tiny-pageembed--16by9,\n.tiny-pageembed--4by3,\n.tiny-pageembed--1by1 {\n  display: block;\n  overflow: hidden;\n  padding: 0;\n  position: relative;\n  width: 100%;\n}\n.tiny-pageembed--21by9 {\n  padding-top: 42.857143%;\n}\n.tiny-pageembed--16by9 {\n  padding-top: 56.25%;\n}\n.tiny-pageembed--4by3 {\n  padding-top: 75%;\n}\n.tiny-pageembed--1by1 {\n  padding-top: 100%;\n}\n.tiny-pageembed--21by9 iframe,\n.tiny-pageembed--16by9 iframe,\n.tiny-pageembed--4by3 iframe,\n.tiny-pageembed--1by1 iframe {\n  border: 0;\n  height: 100%;\n  left: 0;\n  position: absolute;\n  top: 0;\n  width: 100%;\n}\n.mce-content-body[data-mce-placeholder] {\n  position: relative;\n}\n.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before {\n  color: rgba(34, 47, 62, 0.7);\n  content: attr(data-mce-placeholder);\n  position: absolute;\n}\n.mce-content-body:not([dir=rtl])[data-mce-placeholder]:not(.mce-visualblocks)::before {\n  left: 1px;\n}\n.mce-content-body[dir=rtl][data-mce-placeholder]:not(.mce-visualblocks)::before {\n  right: 1px;\n}\n.mce-content-body div.mce-resizehandle {\n  background-color: #4099ff;\n  border-color: #4099ff;\n  border-style: solid;\n  border-width: 1px;\n  box-sizing: border-box;\n  height: 10px;\n  position: absolute;\n  width: 10px;\n  z-index: 1298;\n}\n.mce-content-body div.mce-resizehandle:hover {\n  background-color: #4099ff;\n}\n.mce-content-body div.mce-resizehandle:nth-of-type(1) {\n  cursor: nwse-resize;\n}\n.mce-content-body div.mce-resizehandle:nth-of-type(2) {\n  cursor: nesw-resize;\n}\n.mce-content-body div.mce-resizehandle:nth-of-type(3) {\n  cursor: nwse-resize;\n}\n.mce-content-body div.mce-resizehandle:nth-of-type(4) {\n  cursor: nesw-resize;\n}\n.mce-content-body .mce-resize-backdrop {\n  z-index: 10000;\n}\n.mce-content-body .mce-clonedresizable {\n  cursor: default;\n  opacity: 0.5;\n  outline: 1px dashed black;\n  position: absolute;\n  z-index: 10001;\n}\n.mce-content-body .mce-clonedresizable.mce-resizetable-columns th,\n.mce-content-body .mce-clonedresizable.mce-resizetable-columns td {\n  border: 0;\n}\n.mce-content-body .mce-resize-helper {\n  background: #555;\n  background: rgba(0, 0, 0, 0.75);\n  border: 1px;\n  border-radius: 3px;\n  color: white;\n  display: none;\n  font-family: sans-serif;\n  font-size: 12px;\n  line-height: 14px;\n  margin: 5px 10px;\n  padding: 5px;\n  position: absolute;\n  white-space: nowrap;\n  z-index: 10002;\n}\n.tox-rtc-user-selection {\n  position: relative;\n}\n.tox-rtc-user-cursor {\n  bottom: 0;\n  cursor: default;\n  position: absolute;\n  top: 0;\n  width: 2px;\n}\n.tox-rtc-user-cursor::before {\n  background-color: inherit;\n  border-radius: 50%;\n  content: '';\n  display: block;\n  height: 8px;\n  position: absolute;\n  right: -3px;\n  top: -3px;\n  width: 8px;\n}\n.tox-rtc-user-cursor:hover::after {\n  background-color: inherit;\n  border-radius: 100px;\n  box-sizing: border-box;\n  color: #fff;\n  content: attr(data-user);\n  display: block;\n  font-size: 12px;\n  font-weight: bold;\n  left: -5px;\n  min-height: 8px;\n  min-width: 8px;\n  padding: 0 12px;\n  position: absolute;\n  top: -11px;\n  white-space: nowrap;\n  z-index: 1000;\n}\n.tox-rtc-user-selection--1 .tox-rtc-user-cursor {\n  background-color: #2dc26b;\n}\n.tox-rtc-user-selection--2 .tox-rtc-user-cursor {\n  background-color: #e03e2d;\n}\n.tox-rtc-user-selection--3 .tox-rtc-user-cursor {\n  background-color: #f1c40f;\n}\n.tox-rtc-user-selection--4 .tox-rtc-user-cursor {\n  background-color: #3598db;\n}\n.tox-rtc-user-selection--5 .tox-rtc-user-cursor {\n  background-color: #b96ad9;\n}\n.tox-rtc-user-selection--6 .tox-rtc-user-cursor {\n  background-color: #e67e23;\n}\n.tox-rtc-user-selection--7 .tox-rtc-user-cursor {\n  background-color: #aaa69d;\n}\n.tox-rtc-user-selection--8 .tox-rtc-user-cursor {\n  background-color: #f368e0;\n}\n.tox-rtc-remote-image {\n  background: #eaeaea url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%2236%22%20height%3D%2212%22%20viewBox%3D%220%200%2036%2012%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%3Ccircle%20cx%3D%226%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2218%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.33s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2230%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.66s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%3C%2Fsvg%3E%0A\") no-repeat center center;\n  border: 1px solid #ccc;\n  min-height: 240px;\n  min-width: 320px;\n}\n.mce-match-marker {\n  background: #aaa;\n  color: #fff;\n}\n.mce-match-marker-selected {\n  background: #39f;\n  color: #fff;\n}\n.mce-match-marker-selected::-moz-selection {\n  background: #39f;\n  color: #fff;\n}\n.mce-match-marker-selected::selection {\n  background: #39f;\n  color: #fff;\n}\n.mce-content-body img[data-mce-selected],\n.mce-content-body video[data-mce-selected],\n.mce-content-body audio[data-mce-selected],\n.mce-content-body object[data-mce-selected],\n.mce-content-body embed[data-mce-selected],\n.mce-content-body table[data-mce-selected],\n.mce-content-body details[data-mce-selected] {\n  outline: 3px solid #b4d7ff;\n}\n.mce-content-body hr[data-mce-selected] {\n  outline: 3px solid #b4d7ff;\n  outline-offset: 1px;\n}\n.mce-content-body *[contentEditable=false] *[contentEditable=true]:focus {\n  outline: 3px solid #b4d7ff;\n}\n.mce-content-body *[contentEditable=false] *[contentEditable=true]:hover {\n  outline: 3px solid #b4d7ff;\n}\n.mce-content-body *[contentEditable=false][data-mce-selected] {\n  cursor: not-allowed;\n  outline: 3px solid #b4d7ff;\n}\n.mce-content-body.mce-content-readonly *[contentEditable=true]:focus,\n.mce-content-body.mce-content-readonly *[contentEditable=true]:hover {\n  outline: none;\n}\n.mce-content-body *[data-mce-selected=\"inline-boundary\"] {\n  background-color: #b4d7ff;\n}\n.mce-content-body .mce-edit-focus {\n  outline: 3px solid #b4d7ff;\n}\n.mce-content-body td[data-mce-selected],\n.mce-content-body th[data-mce-selected] {\n  position: relative;\n}\n.mce-content-body td[data-mce-selected]::-moz-selection,\n.mce-content-body th[data-mce-selected]::-moz-selection {\n  background: none;\n}\n.mce-content-body td[data-mce-selected]::selection,\n.mce-content-body th[data-mce-selected]::selection {\n  background: none;\n}\n.mce-content-body td[data-mce-selected] *,\n.mce-content-body th[data-mce-selected] * {\n  outline: none;\n  -webkit-touch-callout: none;\n  -webkit-user-select: none;\n     -moz-user-select: none;\n          user-select: none;\n}\n.mce-content-body td[data-mce-selected]::after,\n.mce-content-body th[data-mce-selected]::after {\n  background-color: rgba(180, 215, 255, 0.7);\n  border: 1px solid rgba(180, 215, 255, 0.7);\n  bottom: -1px;\n  content: '';\n  left: -1px;\n  mix-blend-mode: multiply;\n  position: absolute;\n  right: -1px;\n  top: -1px;\n}\n@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {\n  .mce-content-body td[data-mce-selected]::after,\n  .mce-content-body th[data-mce-selected]::after {\n    border-color: rgba(0, 84, 180, 0.7);\n  }\n}\n.mce-content-body img[data-mce-selected]::-moz-selection {\n  background: none;\n}\n.mce-content-body img[data-mce-selected]::selection {\n  background: none;\n}\n.ephox-snooker-resizer-bar {\n  background-color: #b4d7ff;\n  opacity: 0;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  user-select: none;\n}\n.ephox-snooker-resizer-cols {\n  cursor: col-resize;\n}\n.ephox-snooker-resizer-rows {\n  cursor: row-resize;\n}\n.ephox-snooker-resizer-bar.ephox-snooker-resizer-bar-dragging {\n  opacity: 1;\n}\n.mce-spellchecker-word {\n  background-image: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%23ff0000'%20fill%3D'none'%20stroke-linecap%3D'round'%20stroke-opacity%3D'.75'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A\");\n  background-position: 0 calc(100% + 1px);\n  background-repeat: repeat-x;\n  background-size: auto 6px;\n  cursor: default;\n  height: 2rem;\n}\n.mce-spellchecker-grammar {\n  background-image: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%2300A835'%20fill%3D'none'%20stroke-linecap%3D'round'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A\");\n  background-position: 0 calc(100% + 1px);\n  background-repeat: repeat-x;\n  background-size: auto 6px;\n  cursor: default;\n}\n.mce-toc {\n  border: 1px solid gray;\n}\n.mce-toc h2 {\n  margin: 4px;\n}\n.mce-toc li {\n  list-style-type: none;\n}\n[data-mce-block] {\n  display: block;\n}\ntable[style*=\"border-width: 0px\"],\n.mce-item-table:not([border]),\n.mce-item-table[border=\"0\"],\ntable[style*=\"border-width: 0px\"] td,\n.mce-item-table:not([border]) td,\n.mce-item-table[border=\"0\"] td,\ntable[style*=\"border-width: 0px\"] th,\n.mce-item-table:not([border]) th,\n.mce-item-table[border=\"0\"] th,\ntable[style*=\"border-width: 0px\"] caption,\n.mce-item-table:not([border]) caption,\n.mce-item-table[border=\"0\"] caption {\n  border: 1px dashed #bbb;\n}\n.mce-visualblocks p,\n.mce-visualblocks h1,\n.mce-visualblocks h2,\n.mce-visualblocks h3,\n.mce-visualblocks h4,\n.mce-visualblocks h5,\n.mce-visualblocks h6,\n.mce-visualblocks div:not([data-mce-bogus]),\n.mce-visualblocks section,\n.mce-visualblocks article,\n.mce-visualblocks blockquote,\n.mce-visualblocks address,\n.mce-visualblocks pre,\n.mce-visualblocks figure,\n.mce-visualblocks figcaption,\n.mce-visualblocks hgroup,\n.mce-visualblocks aside,\n.mce-visualblocks ul,\n.mce-visualblocks ol,\n.mce-visualblocks dl {\n  background-repeat: no-repeat;\n  border: 1px dashed #bbb;\n  margin-left: 3px;\n  padding-top: 10px;\n}\n.mce-visualblocks p {\n  background-image: url(data:image/gif;base64,R0lGODlhCQAJAJEAAAAAAP///7u7u////yH5BAEAAAMALAAAAAAJAAkAAAIQnG+CqCN/mlyvsRUpThG6AgA7);\n}\n.mce-visualblocks h1 {\n  background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGu1JuxHoAfRNRW3TWXyF2YiRUAOw==);\n}\n.mce-visualblocks h2 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8Hybbx4oOuqgTynJd6bGlWg3DkJzoaUAAAOw==);\n}\n.mce-visualblocks h3 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIZjI8Hybbx4oOuqgTynJf2Ln2NOHpQpmhAAQA7);\n}\n.mce-visualblocks h4 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxInR0zqeAdhtJlXwV1oCll2HaWgAAOw==);\n}\n.mce-visualblocks h5 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjane4iq5GlW05GgIkIZUAAAOw==);\n}\n.mce-visualblocks h6 {\n  background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjan04jep1iZ1XRlAo5bVgAAOw==);\n}\n.mce-visualblocks div:not([data-mce-bogus]) {\n  background-image: url(data:image/gif;base64,R0lGODlhEgAKAIABALu7u////yH5BAEAAAEALAAAAAASAAoAAAIfjI9poI0cgDywrhuxfbrzDEbQM2Ei5aRjmoySW4pAAQA7);\n}\n.mce-visualblocks section {\n  background-image: url(data:image/gif;base64,R0lGODlhKAAKAIABALu7u////yH5BAEAAAEALAAAAAAoAAoAAAI5jI+pywcNY3sBWHdNrplytD2ellDeSVbp+GmWqaDqDMepc8t17Y4vBsK5hDyJMcI6KkuYU+jpjLoKADs=);\n}\n.mce-visualblocks article {\n  background-image: url(data:image/gif;base64,R0lGODlhKgAKAIABALu7u////yH5BAEAAAEALAAAAAAqAAoAAAI6jI+pywkNY3wG0GBvrsd2tXGYSGnfiF7ikpXemTpOiJScasYoDJJrjsG9gkCJ0ag6KhmaIe3pjDYBBQA7);\n}\n.mce-visualblocks blockquote {\n  background-image: url(data:image/gif;base64,R0lGODlhPgAKAIABALu7u////yH5BAEAAAEALAAAAAA+AAoAAAJPjI+py+0Knpz0xQDyuUhvfoGgIX5iSKZYgq5uNL5q69asZ8s5rrf0yZmpNkJZzFesBTu8TOlDVAabUyatguVhWduud3EyiUk45xhTTgMBBQA7);\n}\n.mce-visualblocks address {\n  background-image: url(data:image/gif;base64,R0lGODlhLQAKAIABALu7u////yH5BAEAAAEALAAAAAAtAAoAAAI/jI+pywwNozSP1gDyyZcjb3UaRpXkWaXmZW4OqKLhBmLs+K263DkJK7OJeifh7FicKD9A1/IpGdKkyFpNmCkAADs=);\n}\n.mce-visualblocks pre {\n  background-image: url(data:image/gif;base64,R0lGODlhFQAKAIABALu7uwAAACH5BAEAAAEALAAAAAAVAAoAAAIjjI+ZoN0cgDwSmnpz1NCueYERhnibZVKLNnbOq8IvKpJtVQAAOw==);\n}\n.mce-visualblocks figure {\n  background-image: url(data:image/gif;base64,R0lGODlhJAAKAIAAALu7u////yH5BAEAAAEALAAAAAAkAAoAAAI0jI+py+2fwAHUSFvD3RlvG4HIp4nX5JFSpnZUJ6LlrM52OE7uSWosBHScgkSZj7dDKnWAAgA7);\n}\n.mce-visualblocks figcaption {\n  border: 1px dashed #bbb;\n}\n.mce-visualblocks hgroup {\n  background-image: url(data:image/gif;base64,R0lGODlhJwAKAIABALu7uwAAACH5BAEAAAEALAAAAAAnAAoAAAI3jI+pywYNI3uB0gpsRtt5fFnfNZaVSYJil4Wo03Hv6Z62uOCgiXH1kZIIJ8NiIxRrAZNMZAtQAAA7);\n}\n.mce-visualblocks aside {\n  background-image: url(data:image/gif;base64,R0lGODlhHgAKAIABAKqqqv///yH5BAEAAAEALAAAAAAeAAoAAAItjI+pG8APjZOTzgtqy7I3f1yehmQcFY4WKZbqByutmW4aHUd6vfcVbgudgpYCADs=);\n}\n.mce-visualblocks ul {\n  background-image: url(data:image/gif;base64,R0lGODlhDQAKAIAAALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGuYnqUVSjvw26DzzXiqIDlVwAAOw==);\n}\n.mce-visualblocks ol {\n  background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybH6HHt0qourxC6CvzXieHyeWQAAOw==);\n}\n.mce-visualblocks dl {\n  background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybEOnmOvUoWznTqeuEjNSCqeGRUAOw==);\n}\n.mce-visualblocks:not([dir=rtl]) p,\n.mce-visualblocks:not([dir=rtl]) h1,\n.mce-visualblocks:not([dir=rtl]) h2,\n.mce-visualblocks:not([dir=rtl]) h3,\n.mce-visualblocks:not([dir=rtl]) h4,\n.mce-visualblocks:not([dir=rtl]) h5,\n.mce-visualblocks:not([dir=rtl]) h6,\n.mce-visualblocks:not([dir=rtl]) div:not([data-mce-bogus]),\n.mce-visualblocks:not([dir=rtl]) section,\n.mce-visualblocks:not([dir=rtl]) article,\n.mce-visualblocks:not([dir=rtl]) blockquote,\n.mce-visualblocks:not([dir=rtl]) address,\n.mce-visualblocks:not([dir=rtl]) pre,\n.mce-visualblocks:not([dir=rtl]) figure,\n.mce-visualblocks:not([dir=rtl]) figcaption,\n.mce-visualblocks:not([dir=rtl]) hgroup,\n.mce-visualblocks:not([dir=rtl]) aside,\n.mce-visualblocks:not([dir=rtl]) ul,\n.mce-visualblocks:not([dir=rtl]) ol,\n.mce-visualblocks:not([dir=rtl]) dl {\n  margin-left: 3px;\n}\n.mce-visualblocks[dir=rtl] p,\n.mce-visualblocks[dir=rtl] h1,\n.mce-visualblocks[dir=rtl] h2,\n.mce-visualblocks[dir=rtl] h3,\n.mce-visualblocks[dir=rtl] h4,\n.mce-visualblocks[dir=rtl] h5,\n.mce-visualblocks[dir=rtl] h6,\n.mce-visualblocks[dir=rtl] div:not([data-mce-bogus]),\n.mce-visualblocks[dir=rtl] section,\n.mce-visualblocks[dir=rtl] article,\n.mce-visualblocks[dir=rtl] blockquote,\n.mce-visualblocks[dir=rtl] address,\n.mce-visualblocks[dir=rtl] pre,\n.mce-visualblocks[dir=rtl] figure,\n.mce-visualblocks[dir=rtl] figcaption,\n.mce-visualblocks[dir=rtl] hgroup,\n.mce-visualblocks[dir=rtl] aside,\n.mce-visualblocks[dir=rtl] ul,\n.mce-visualblocks[dir=rtl] ol,\n.mce-visualblocks[dir=rtl] dl {\n  background-position-x: right;\n  margin-right: 3px;\n}\n.mce-nbsp,\n.mce-shy {\n  background: #aaa;\n}\n.mce-shy::after {\n  content: '-';\n}\n"
  },
  {
    "path": "minimalist-vue3/public/tinymce/skins/ui/tinymce-5-dark/skin.css",
    "content": ".tox {\n  box-shadow: none;\n  box-sizing: content-box;\n  color: #2A3746;\n  cursor: auto;\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n  font-size: 16px;\n  font-style: normal;\n  font-weight: normal;\n  line-height: normal;\n  -webkit-tap-highlight-color: transparent;\n  text-decoration: none;\n  text-shadow: none;\n  text-transform: none;\n  vertical-align: initial;\n  white-space: normal;\n}\n.tox *:not(svg):not(rect) {\n  box-sizing: inherit;\n  color: inherit;\n  cursor: inherit;\n  direction: inherit;\n  font-family: inherit;\n  font-size: inherit;\n  font-style: inherit;\n  font-weight: inherit;\n  line-height: inherit;\n  -webkit-tap-highlight-color: inherit;\n  text-align: inherit;\n  text-decoration: inherit;\n  text-shadow: inherit;\n  text-transform: inherit;\n  vertical-align: inherit;\n  white-space: inherit;\n}\n.tox *:not(svg):not(rect) {\n  /* stylelint-disable-line no-duplicate-selectors */\n  background: transparent;\n  border: 0;\n  box-shadow: none;\n  float: none;\n  height: auto;\n  margin: 0;\n  max-width: none;\n  outline: 0;\n  padding: 0;\n  position: static;\n  width: auto;\n}\n.tox:not([dir=rtl]) {\n  direction: ltr;\n  text-align: left;\n}\n.tox[dir=rtl] {\n  direction: rtl;\n  text-align: right;\n}\n.tox-tinymce {\n  border: 1px solid #000000;\n  border-radius: 0;\n  box-shadow: none;\n  box-sizing: border-box;\n  display: flex;\n  flex-direction: column;\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n  overflow: hidden;\n  position: relative;\n  visibility: inherit !important;\n}\n.tox.tox-tinymce-inline {\n  border: none;\n  box-shadow: none;\n  overflow: initial;\n}\n.tox.tox-tinymce-inline .tox-editor-container {\n  overflow: initial;\n}\n.tox.tox-tinymce-inline .tox-editor-header {\n  background-color: #222f3e;\n  border: 1px solid #000000;\n  border-radius: 0;\n  box-shadow: none;\n  overflow: hidden;\n}\n.tox-tinymce-aux {\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n  z-index: 1300;\n}\n.tox-tinymce *:focus,\n.tox-tinymce-aux *:focus {\n  outline: none;\n}\nbutton::-moz-focus-inner {\n  border: 0;\n}\n.tox[dir=rtl] .tox-icon--flip svg {\n  transform: rotateY(180deg);\n}\n.tox .accessibility-issue__header {\n  align-items: center;\n  display: flex;\n  margin-bottom: 4px;\n}\n.tox .accessibility-issue__description {\n  align-items: stretch;\n  border-radius: 3px;\n  display: flex;\n  justify-content: space-between;\n}\n.tox .accessibility-issue__description > div {\n  padding-bottom: 4px;\n}\n.tox .accessibility-issue__description > div > div {\n  align-items: center;\n  display: flex;\n  margin-bottom: 4px;\n}\n.tox .accessibility-issue__description > div > div .tox-icon svg {\n  display: block;\n}\n.tox .accessibility-issue__repair {\n  margin-top: 16px;\n}\n.tox .tox-dialog__body-content .accessibility-issue--info .accessibility-issue__description {\n  background-color: rgba(30, 113, 170, 0.4);\n  color: #fff;\n}\n.tox .tox-dialog__body-content .accessibility-issue--info .tox-form__group h2 {\n  color: #fff;\n}\n.tox .tox-dialog__body-content .accessibility-issue--info .tox-icon svg {\n  fill: #fff;\n}\n.tox .tox-dialog__body-content .accessibility-issue--info a.tox-button--naked.tox-button--icon {\n  background-color: #207ab7;\n  color: #fff;\n}\n.tox .tox-dialog__body-content .accessibility-issue--info a.tox-button--naked.tox-button--icon:hover,\n.tox .tox-dialog__body-content .accessibility-issue--info a.tox-button--naked.tox-button--icon:focus {\n  background-color: #1c6ca1;\n}\n.tox .tox-dialog__body-content .accessibility-issue--info a.tox-button--naked.tox-button--icon:active {\n  background-color: #185d8c;\n}\n.tox .tox-dialog__body-content .accessibility-issue--warn {\n  /* stylelint-disable-next-line no-descending-specificity */\n}\n.tox .tox-dialog__body-content .accessibility-issue--warn .accessibility-issue__description {\n  background-color: rgba(255, 165, 0, 0.5);\n  color: #fff;\n}\n.tox .tox-dialog__body-content .accessibility-issue--warn .tox-form__group h2 {\n  color: #fff;\n}\n.tox .tox-dialog__body-content .accessibility-issue--warn .tox-icon svg {\n  fill: #fff;\n}\n.tox .tox-dialog__body-content .accessibility-issue--warn a.tox-button--naked.tox-button--icon {\n  background-color: #FFE89D;\n  color: #2A3746;\n}\n.tox .tox-dialog__body-content .accessibility-issue--warn a.tox-button--naked.tox-button--icon:hover,\n.tox .tox-dialog__body-content .accessibility-issue--warn a.tox-button--naked.tox-button--icon:focus {\n  background-color: #F2D574;\n  color: #2A3746;\n}\n.tox .tox-dialog__body-content .accessibility-issue--warn a.tox-button--naked.tox-button--icon:active {\n  background-color: #E8C657;\n  color: #2A3746;\n}\n.tox .tox-dialog__body-content .accessibility-issue--error {\n  /* stylelint-disable-next-line no-descending-specificity */\n}\n.tox .tox-dialog__body-content .accessibility-issue--error .accessibility-issue__description {\n  background-color: rgba(204, 0, 0, 0.5);\n  color: #fff;\n}\n.tox .tox-dialog__body-content .accessibility-issue--error .tox-form__group h2 {\n  color: #fff;\n}\n.tox .tox-dialog__body-content .accessibility-issue--error .tox-icon svg {\n  fill: #fff;\n}\n.tox .tox-dialog__body-content .accessibility-issue--error a.tox-button--naked.tox-button--icon {\n  background-color: #F2BFBF;\n  color: #2A3746;\n}\n.tox .tox-dialog__body-content .accessibility-issue--error a.tox-button--naked.tox-button--icon:hover,\n.tox .tox-dialog__body-content .accessibility-issue--error a.tox-button--naked.tox-button--icon:focus {\n  background-color: #E9A4A4;\n  color: #2A3746;\n}\n.tox .tox-dialog__body-content .accessibility-issue--error a.tox-button--naked.tox-button--icon:active {\n  background-color: #EE9494;\n  color: #2A3746;\n}\n.tox .tox-dialog__body-content .accessibility-issue--success .accessibility-issue__description {\n  background-color: rgba(120, 171, 70, 0.5);\n  color: #fff;\n}\n.tox .tox-dialog__body-content .accessibility-issue--success .accessibility-issue__description > *:last-child {\n  display: none;\n}\n.tox .tox-dialog__body-content .accessibility-issue--success .tox-form__group h2 {\n  color: #fff;\n}\n.tox .tox-dialog__body-content .accessibility-issue--success .tox-icon svg {\n  fill: #fff;\n}\n.tox .tox-dialog__body-content .accessibility-issue__header .tox-form__group h1,\n.tox .tox-dialog__body-content .tox-form__group .accessibility-issue__description h2 {\n  font-size: 14px;\n  margin-top: 0;\n}\n.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__header .tox-button {\n  margin-left: 4px;\n}\n.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__header > *:nth-last-child(2) {\n  margin-left: auto;\n}\n.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__description {\n  padding: 4px 4px 4px 8px;\n}\n.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__header .tox-button {\n  margin-right: 4px;\n}\n.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__header > *:nth-last-child(2) {\n  margin-right: auto;\n}\n.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__description {\n  padding: 4px 8px 4px 4px;\n}\n.tox .tox-advtemplate .tox-form__grid {\n  flex: 1;\n}\n.tox .tox-advtemplate .tox-form__grid > div:first-child {\n  display: flex;\n  flex-direction: column;\n  width: 30%;\n}\n.tox .tox-advtemplate .tox-form__grid > div:first-child > div:nth-child(2) {\n  flex-basis: 0;\n  flex-grow: 1;\n  overflow: auto;\n}\n@media only screen and (max-width: 767px ) {\n  body:not(.tox-force-desktop) .tox .tox-advtemplate .tox-form__grid > div:first-child {\n    width: 100%;\n  }\n}\n.tox .tox-advtemplate iframe {\n  border-color: #000000;\n  border-radius: 0;\n  border-style: solid;\n  border-width: 1px;\n  margin: 0 10px;\n}\n.tox .tox-anchorbar {\n  display: flex;\n  flex: 0 0 auto;\n}\n.tox .tox-bottom-anchorbar {\n  display: flex;\n  flex: 0 0 auto;\n}\n.tox .tox-bar {\n  display: flex;\n  flex: 0 0 auto;\n}\n.tox .tox-button {\n  background-color: #207ab7;\n  background-image: none;\n  background-position: 0 0;\n  background-repeat: repeat;\n  border-color: #207ab7;\n  border-radius: 3px;\n  border-style: solid;\n  border-width: 1px;\n  box-shadow: none;\n  box-sizing: border-box;\n  color: #fff;\n  cursor: pointer;\n  display: inline-block;\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: bold;\n  letter-spacing: normal;\n  line-height: 24px;\n  margin: 0;\n  outline: none;\n  padding: 4px 16px;\n  position: relative;\n  text-align: center;\n  text-decoration: none;\n  text-transform: none;\n  white-space: nowrap;\n}\n.tox .tox-button::before {\n  border-radius: 3px;\n  bottom: -1px;\n  box-shadow: inset 0 0 0 2px #fff, 0 0 0 1px #207ab7, 0 0 0 3px rgba(32, 122, 183, 0.25);\n  content: '';\n  left: -1px;\n  opacity: 0;\n  pointer-events: none;\n  position: absolute;\n  right: -1px;\n  top: -1px;\n}\n.tox .tox-button[disabled] {\n  background-color: #207ab7;\n  background-image: none;\n  border-color: #207ab7;\n  box-shadow: none;\n  color: rgba(255, 255, 255, 0.5);\n  cursor: not-allowed;\n}\n.tox .tox-button:focus:not(:disabled) {\n  background-color: #1c6ca1;\n  background-image: none;\n  border-color: #1c6ca1;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button:focus-visible:not(:disabled)::before {\n  opacity: 1;\n}\n.tox .tox-button:hover:not(:disabled) {\n  background-color: #1c6ca1;\n  background-image: none;\n  border-color: #1c6ca1;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button:active:not(:disabled) {\n  background-color: #185d8c;\n  background-image: none;\n  border-color: #185d8c;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button.tox-button--enabled {\n  background-color: #185d8c;\n  background-image: none;\n  border-color: #185d8c;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button.tox-button--enabled[disabled] {\n  background-color: #185d8c;\n  background-image: none;\n  border-color: #185d8c;\n  box-shadow: none;\n  color: rgba(255, 255, 255, 0.5);\n  cursor: not-allowed;\n}\n.tox .tox-button.tox-button--enabled:focus:not(:disabled) {\n  background-color: #154f76;\n  background-image: none;\n  border-color: #154f76;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button.tox-button--enabled:hover:not(:disabled) {\n  background-color: #154f76;\n  background-image: none;\n  border-color: #154f76;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button.tox-button--enabled:active:not(:disabled) {\n  background-color: #114060;\n  background-image: none;\n  border-color: #114060;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button--icon-and-text,\n.tox .tox-button.tox-button--icon-and-text,\n.tox .tox-button.tox-button--secondary.tox-button--icon-and-text {\n  display: flex;\n  padding: 5px 4px;\n}\n.tox .tox-button--icon-and-text .tox-icon svg,\n.tox .tox-button.tox-button--icon-and-text .tox-icon svg,\n.tox .tox-button.tox-button--secondary.tox-button--icon-and-text .tox-icon svg {\n  display: block;\n  fill: currentColor;\n}\n.tox .tox-button--secondary {\n  background-color: #3d546f;\n  background-image: none;\n  background-position: 0 0;\n  background-repeat: repeat;\n  border-color: #3d546f;\n  border-radius: 3px;\n  border-style: solid;\n  border-width: 1px;\n  box-shadow: none;\n  color: #fff;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: bold;\n  letter-spacing: normal;\n  outline: none;\n  padding: 4px 16px;\n  text-decoration: none;\n  text-transform: none;\n}\n.tox .tox-button--secondary[disabled] {\n  background-color: #3d546f;\n  background-image: none;\n  border-color: #3d546f;\n  box-shadow: none;\n  color: rgba(255, 255, 255, 0.5);\n}\n.tox .tox-button--secondary:focus:not(:disabled) {\n  background-color: #34485f;\n  background-image: none;\n  border-color: #34485f;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button--secondary:hover:not(:disabled) {\n  background-color: #34485f;\n  background-image: none;\n  border-color: #34485f;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button--secondary:active:not(:disabled) {\n  background-color: #2b3b4e;\n  background-image: none;\n  border-color: #2b3b4e;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button--secondary.tox-button--enabled {\n  background-color: #346085;\n  background-image: none;\n  border-color: #346085;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button--secondary.tox-button--enabled[disabled] {\n  background-color: #346085;\n  background-image: none;\n  border-color: #346085;\n  box-shadow: none;\n  color: rgba(255, 255, 255, 0.5);\n}\n.tox .tox-button--secondary.tox-button--enabled:focus:not(:disabled) {\n  background-color: #2d5373;\n  background-image: none;\n  border-color: #2d5373;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button--secondary.tox-button--enabled:hover:not(:disabled) {\n  background-color: #2d5373;\n  background-image: none;\n  border-color: #2d5373;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button--secondary.tox-button--enabled:active:not(:disabled) {\n  background-color: #264560;\n  background-image: none;\n  border-color: #264560;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button--icon,\n.tox .tox-button.tox-button--icon,\n.tox .tox-button.tox-button--secondary.tox-button--icon {\n  padding: 4px;\n}\n.tox .tox-button--icon .tox-icon svg,\n.tox .tox-button.tox-button--icon .tox-icon svg,\n.tox .tox-button.tox-button--secondary.tox-button--icon .tox-icon svg {\n  display: block;\n  fill: currentColor;\n}\n.tox .tox-button-link {\n  background: 0;\n  border: none;\n  box-sizing: border-box;\n  cursor: pointer;\n  display: inline-block;\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n  font-size: 16px;\n  font-weight: normal;\n  line-height: 1.3;\n  margin: 0;\n  padding: 0;\n  white-space: nowrap;\n}\n.tox .tox-button-link--sm {\n  font-size: 14px;\n}\n.tox .tox-button--naked {\n  background-color: transparent;\n  border-color: transparent;\n  box-shadow: unset;\n  color: #fff;\n}\n.tox .tox-button--naked[disabled] {\n  background-color: #3d546f;\n  border-color: #3d546f;\n  box-shadow: none;\n  color: rgba(255, 255, 255, 0.5);\n}\n.tox .tox-button--naked:hover:not(:disabled) {\n  background-color: #34485f;\n  border-color: #34485f;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button--naked:focus:not(:disabled) {\n  background-color: #34485f;\n  border-color: #34485f;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button--naked:active:not(:disabled) {\n  background-color: #2b3b4e;\n  border-color: #2b3b4e;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-button--naked .tox-icon svg {\n  fill: currentColor;\n}\n.tox .tox-button--naked.tox-button--icon:hover:not(:disabled) {\n  color: #fff;\n}\n.tox .tox-checkbox {\n  align-items: center;\n  border-radius: 3px;\n  cursor: pointer;\n  display: flex;\n  height: 36px;\n  min-width: 36px;\n}\n.tox .tox-checkbox__input {\n  /* Hide from view but visible to screen readers */\n  height: 1px;\n  overflow: hidden;\n  position: absolute;\n  top: auto;\n  width: 1px;\n}\n.tox .tox-checkbox__icons {\n  align-items: center;\n  border-radius: 3px;\n  box-shadow: 0 0 0 2px transparent;\n  box-sizing: content-box;\n  display: flex;\n  height: 24px;\n  justify-content: center;\n  padding: calc(4px - 1px);\n  width: 24px;\n}\n.tox .tox-checkbox__icons .tox-checkbox-icon__unchecked svg {\n  display: block;\n  fill: rgba(255, 255, 255, 0.2);\n}\n.tox .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg {\n  display: none;\n  fill: #207ab7;\n}\n.tox .tox-checkbox__icons .tox-checkbox-icon__checked svg {\n  display: none;\n  fill: #207ab7;\n}\n.tox .tox-checkbox--disabled {\n  color: rgba(255, 255, 255, 0.5);\n  cursor: not-allowed;\n}\n.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__checked svg {\n  fill: rgba(255, 255, 255, 0.5);\n}\n.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__unchecked svg {\n  fill: rgba(255, 255, 255, 0.5);\n}\n.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg {\n  fill: rgba(255, 255, 255, 0.5);\n}\n.tox input.tox-checkbox__input:checked + .tox-checkbox__icons .tox-checkbox-icon__unchecked svg {\n  display: none;\n}\n.tox input.tox-checkbox__input:checked + .tox-checkbox__icons .tox-checkbox-icon__checked svg {\n  display: block;\n}\n.tox input.tox-checkbox__input:indeterminate + .tox-checkbox__icons .tox-checkbox-icon__unchecked svg {\n  display: none;\n}\n.tox input.tox-checkbox__input:indeterminate + .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg {\n  display: block;\n}\n.tox input.tox-checkbox__input:focus + .tox-checkbox__icons {\n  border-radius: 3px;\n  box-shadow: inset 0 0 0 1px #207ab7;\n  padding: calc(4px - 1px);\n}\n.tox:not([dir=rtl]) .tox-checkbox__label {\n  margin-left: 4px;\n}\n.tox:not([dir=rtl]) .tox-checkbox__input {\n  left: -10000px;\n}\n.tox:not([dir=rtl]) .tox-bar .tox-checkbox {\n  margin-left: 4px;\n}\n.tox[dir=rtl] .tox-checkbox__label {\n  margin-right: 4px;\n}\n.tox[dir=rtl] .tox-checkbox__input {\n  right: -10000px;\n}\n.tox[dir=rtl] .tox-bar .tox-checkbox {\n  margin-right: 4px;\n}\n.tox {\n  /* stylelint-disable-next-line no-descending-specificity */\n}\n.tox .tox-collection--toolbar .tox-collection__group {\n  display: flex;\n  padding: 0;\n}\n.tox .tox-collection--grid .tox-collection__group {\n  display: flex;\n  flex-wrap: wrap;\n  max-height: 208px;\n  overflow-x: hidden;\n  overflow-y: auto;\n  padding: 0;\n}\n.tox .tox-collection--list .tox-collection__group {\n  border-bottom-width: 0;\n  border-color: #1a1a1a;\n  border-left-width: 0;\n  border-right-width: 0;\n  border-style: solid;\n  border-top-width: 1px;\n  padding: 4px 0;\n}\n.tox .tox-collection--list .tox-collection__group:first-child {\n  border-top-width: 0;\n}\n.tox .tox-collection__group-heading {\n  background-color: #333333;\n  color: #fff;\n  cursor: default;\n  font-size: 12px;\n  font-style: normal;\n  font-weight: normal;\n  margin-bottom: 4px;\n  margin-top: -4px;\n  padding: 4px 8px;\n  text-transform: none;\n  -webkit-touch-callout: none;\n  -webkit-user-select: none;\n     -moz-user-select: none;\n          user-select: none;\n}\n.tox .tox-collection__item {\n  align-items: center;\n  border-radius: 3px;\n  color: #fff;\n  display: flex;\n  -webkit-touch-callout: none;\n  -webkit-user-select: none;\n     -moz-user-select: none;\n          user-select: none;\n}\n.tox .tox-collection--list .tox-collection__item {\n  padding: 4px 8px;\n}\n.tox .tox-collection--toolbar .tox-collection__item {\n  border-radius: 3px;\n  padding: 4px;\n}\n.tox .tox-collection--grid .tox-collection__item {\n  border-radius: 3px;\n  padding: 4px;\n}\n.tox .tox-collection--list .tox-collection__item--enabled {\n  background-color: #2b3b4e;\n  color: #fff;\n}\n.tox .tox-collection--list .tox-collection__item--active {\n  background-color: #4a5562;\n}\n.tox .tox-collection--toolbar .tox-collection__item--enabled {\n  background-color: #757d87;\n  color: #fff;\n}\n.tox .tox-collection--toolbar .tox-collection__item--active {\n  background-color: #4a5562;\n}\n.tox .tox-collection--grid .tox-collection__item--enabled {\n  background-color: #757d87;\n  color: #fff;\n}\n.tox .tox-collection--grid .tox-collection__item--active:not(.tox-collection__item--state-disabled) {\n  background-color: #4a5562;\n  color: #fff;\n}\n.tox .tox-collection--list .tox-collection__item--active:not(.tox-collection__item--state-disabled) {\n  color: #fff;\n}\n.tox .tox-collection--toolbar .tox-collection__item--active:not(.tox-collection__item--state-disabled) {\n  color: #fff;\n}\n.tox .tox-collection__item-icon,\n.tox .tox-collection__item-checkmark {\n  align-items: center;\n  display: flex;\n  height: 24px;\n  justify-content: center;\n  width: 24px;\n}\n.tox .tox-collection__item-icon svg,\n.tox .tox-collection__item-checkmark svg {\n  fill: currentColor;\n}\n.tox .tox-collection--toolbar-lg .tox-collection__item-icon {\n  height: 48px;\n  width: 48px;\n}\n.tox .tox-collection__item-label {\n  color: currentColor;\n  display: inline-block;\n  flex: 1;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: normal;\n  line-height: 24px;\n  text-transform: none;\n  word-break: break-all;\n}\n.tox .tox-collection__item-accessory {\n  color: rgba(255, 255, 255, 0.5);\n  display: inline-block;\n  font-size: 14px;\n  height: 24px;\n  line-height: 24px;\n  text-transform: none;\n}\n.tox .tox-collection__item-caret {\n  align-items: center;\n  display: flex;\n  min-height: 24px;\n}\n.tox .tox-collection__item-caret::after {\n  content: '';\n  font-size: 0;\n  min-height: inherit;\n}\n.tox .tox-collection__item-caret svg {\n  fill: #fff;\n}\n.tox .tox-collection__item--state-disabled {\n  background-color: transparent;\n  color: rgba(255, 255, 255, 0.5);\n  cursor: not-allowed;\n}\n.tox .tox-collection__item--state-disabled .tox-collection__item-caret svg {\n  fill: rgba(255, 255, 255, 0.5);\n}\n.tox .tox-collection--list .tox-collection__item:not(.tox-collection__item--enabled) .tox-collection__item-checkmark svg {\n  display: none;\n}\n.tox .tox-collection--list .tox-collection__item:not(.tox-collection__item--enabled) .tox-collection__item-accessory + .tox-collection__item-checkmark {\n  display: none;\n}\n.tox .tox-collection--horizontal {\n  background-color: #2b3b4e;\n  border: 1px solid #1a1a1a;\n  border-radius: 3px;\n  box-shadow: 0 0 2px 0 rgba(42, 55, 70, 0.2), 0 4px 8px 0 rgba(42, 55, 70, 0.15);\n  display: flex;\n  flex: 0 0 auto;\n  flex-shrink: 0;\n  flex-wrap: nowrap;\n  margin-bottom: 0;\n  overflow-x: auto;\n  padding: 0;\n}\n.tox .tox-collection--horizontal .tox-collection__group {\n  align-items: center;\n  display: flex;\n  flex-wrap: nowrap;\n  margin: 0;\n  padding: 0 4px;\n}\n.tox .tox-collection--horizontal .tox-collection__item {\n  height: 34px;\n  margin: 3px 0 2px 0;\n  padding: 0 4px;\n}\n.tox .tox-collection--horizontal .tox-collection__item-label {\n  white-space: nowrap;\n}\n.tox .tox-collection--horizontal .tox-collection__item-caret {\n  margin-left: 4px;\n}\n.tox .tox-collection__item-container {\n  display: flex;\n}\n.tox .tox-collection__item-container--row {\n  align-items: center;\n  flex: 1 1 auto;\n  flex-direction: row;\n}\n.tox .tox-collection__item-container--row.tox-collection__item-container--align-left {\n  margin-right: auto;\n}\n.tox .tox-collection__item-container--row.tox-collection__item-container--align-right {\n  justify-content: flex-end;\n  margin-left: auto;\n}\n.tox .tox-collection__item-container--row.tox-collection__item-container--valign-top {\n  align-items: flex-start;\n  margin-bottom: auto;\n}\n.tox .tox-collection__item-container--row.tox-collection__item-container--valign-middle {\n  align-items: center;\n}\n.tox .tox-collection__item-container--row.tox-collection__item-container--valign-bottom {\n  align-items: flex-end;\n  margin-top: auto;\n}\n.tox .tox-collection__item-container--column {\n  align-self: center;\n  flex: 1 1 auto;\n  flex-direction: column;\n}\n.tox .tox-collection__item-container--column.tox-collection__item-container--align-left {\n  align-items: flex-start;\n}\n.tox .tox-collection__item-container--column.tox-collection__item-container--align-right {\n  align-items: flex-end;\n}\n.tox .tox-collection__item-container--column.tox-collection__item-container--valign-top {\n  align-self: flex-start;\n}\n.tox .tox-collection__item-container--column.tox-collection__item-container--valign-middle {\n  align-self: center;\n}\n.tox .tox-collection__item-container--column.tox-collection__item-container--valign-bottom {\n  align-self: flex-end;\n}\n.tox:not([dir=rtl]) .tox-collection--horizontal .tox-collection__group:not(:last-of-type) {\n  border-right: 1px solid #000000;\n}\n.tox:not([dir=rtl]) .tox-collection--list .tox-collection__item > *:not(:first-child) {\n  margin-left: 8px;\n}\n.tox:not([dir=rtl]) .tox-collection--list .tox-collection__item > .tox-collection__item-label:first-child {\n  margin-left: 4px;\n}\n.tox:not([dir=rtl]) .tox-collection__item-accessory {\n  margin-left: 16px;\n  text-align: right;\n}\n.tox:not([dir=rtl]) .tox-collection .tox-collection__item-caret {\n  margin-left: 16px;\n}\n.tox[dir=rtl] .tox-collection--horizontal .tox-collection__group:not(:last-of-type) {\n  border-left: 1px solid #000000;\n}\n.tox[dir=rtl] .tox-collection--list .tox-collection__item > *:not(:first-child) {\n  margin-right: 8px;\n}\n.tox[dir=rtl] .tox-collection--list .tox-collection__item > .tox-collection__item-label:first-child {\n  margin-right: 4px;\n}\n.tox[dir=rtl] .tox-collection__item-accessory {\n  margin-right: 16px;\n  text-align: left;\n}\n.tox[dir=rtl] .tox-collection .tox-collection__item-caret {\n  margin-right: 16px;\n  transform: rotateY(180deg);\n}\n.tox[dir=rtl] .tox-collection--horizontal .tox-collection__item-caret {\n  margin-right: 4px;\n}\n.tox .tox-color-picker-container {\n  display: flex;\n  flex-direction: row;\n  height: 225px;\n  margin: 0;\n}\n.tox .tox-sv-palette {\n  box-sizing: border-box;\n  display: flex;\n  height: 100%;\n}\n.tox .tox-sv-palette-spectrum {\n  height: 100%;\n}\n.tox .tox-sv-palette,\n.tox .tox-sv-palette-spectrum {\n  width: 225px;\n}\n.tox .tox-sv-palette-thumb {\n  background: none;\n  border: 1px solid black;\n  border-radius: 50%;\n  box-sizing: content-box;\n  height: 12px;\n  position: absolute;\n  width: 12px;\n}\n.tox .tox-sv-palette-inner-thumb {\n  border: 1px solid white;\n  border-radius: 50%;\n  height: 10px;\n  position: absolute;\n  width: 10px;\n}\n.tox .tox-hue-slider {\n  box-sizing: border-box;\n  height: 100%;\n  width: 25px;\n}\n.tox .tox-hue-slider-spectrum {\n  background: linear-gradient(to bottom, #f00, #ff0080, #f0f, #8000ff, #00f, #0080ff, #0ff, #00ff80, #0f0, #80ff00, #ff0, #ff8000, #f00);\n  height: 100%;\n  width: 100%;\n}\n.tox .tox-hue-slider,\n.tox .tox-hue-slider-spectrum {\n  width: 20px;\n}\n.tox .tox-hue-slider-thumb {\n  background: white;\n  border: 1px solid black;\n  box-sizing: content-box;\n  height: 4px;\n  width: 100%;\n}\n.tox .tox-rgb-form {\n  display: flex;\n  flex-direction: column;\n  justify-content: space-between;\n}\n.tox .tox-rgb-form div {\n  align-items: center;\n  display: flex;\n  justify-content: space-between;\n  margin-bottom: 5px;\n  width: inherit;\n}\n.tox .tox-rgb-form input {\n  width: 6em;\n}\n.tox .tox-rgb-form input.tox-invalid {\n  /* Need !important to override Chrome's focus styling unfortunately */\n  border: 1px solid red !important;\n}\n.tox .tox-rgb-form .tox-rgba-preview {\n  border: 1px solid black;\n  flex-grow: 2;\n  margin-bottom: 0;\n}\n.tox:not([dir=rtl]) .tox-sv-palette {\n  margin-right: 15px;\n}\n.tox:not([dir=rtl]) .tox-hue-slider {\n  margin-right: 15px;\n}\n.tox:not([dir=rtl]) .tox-hue-slider-thumb {\n  margin-left: -1px;\n}\n.tox:not([dir=rtl]) .tox-rgb-form label {\n  margin-right: 0.5em;\n}\n.tox[dir=rtl] .tox-sv-palette {\n  margin-left: 15px;\n}\n.tox[dir=rtl] .tox-hue-slider {\n  margin-left: 15px;\n}\n.tox[dir=rtl] .tox-hue-slider-thumb {\n  margin-right: -1px;\n}\n.tox[dir=rtl] .tox-rgb-form label {\n  margin-left: 0.5em;\n}\n.tox .tox-toolbar .tox-swatches,\n.tox .tox-toolbar__primary .tox-swatches,\n.tox .tox-toolbar__overflow .tox-swatches {\n  margin: 2px 0 3px 4px;\n}\n.tox .tox-collection--list .tox-collection__group .tox-swatches-menu {\n  border: 0;\n  margin: -4px 0;\n}\n.tox .tox-swatches__row {\n  display: flex;\n}\n.tox .tox-swatch {\n  height: 30px;\n  transition: transform 0.15s, box-shadow 0.15s;\n  width: 30px;\n}\n.tox .tox-swatch:hover,\n.tox .tox-swatch:focus {\n  box-shadow: 0 0 0 1px rgba(127, 127, 127, 0.3) inset;\n  transform: scale(0.8);\n}\n.tox .tox-swatch--remove {\n  align-items: center;\n  display: flex;\n  justify-content: center;\n}\n.tox .tox-swatch--remove svg path {\n  stroke: #e74c3c;\n}\n.tox .tox-swatches__picker-btn {\n  align-items: center;\n  background-color: transparent;\n  border: 0;\n  cursor: pointer;\n  display: flex;\n  height: 30px;\n  justify-content: center;\n  outline: none;\n  padding: 0;\n  width: 30px;\n}\n.tox .tox-swatches__picker-btn svg {\n  fill: #fff;\n  height: 24px;\n  width: 24px;\n}\n.tox .tox-swatches__picker-btn:hover {\n  background: #4a5562;\n}\n.tox div.tox-swatch:not(.tox-swatch--remove) svg {\n  display: none;\n  fill: #fff;\n  height: 24px;\n  margin: calc((30px - 24px) / 2) calc((30px - 24px) / 2);\n  width: 24px;\n}\n.tox div.tox-swatch:not(.tox-swatch--remove) svg path {\n  fill: #fff;\n  paint-order: stroke;\n  stroke: #222f3e;\n  stroke-width: 2px;\n}\n.tox div.tox-swatch:not(.tox-swatch--remove).tox-collection__item--enabled svg {\n  display: block;\n}\n.tox:not([dir=rtl]) .tox-swatches__picker-btn {\n  margin-left: auto;\n}\n.tox[dir=rtl] .tox-swatches__picker-btn {\n  margin-right: auto;\n}\n.tox .tox-comment-thread {\n  background: #2b3b4e;\n  position: relative;\n}\n.tox .tox-comment-thread > *:not(:first-child) {\n  margin-top: 8px;\n}\n.tox .tox-comment {\n  background: #2b3b4e;\n  border: 1px solid #000000;\n  border-radius: 3px;\n  box-shadow: 0 4px 8px 0 rgba(42, 55, 70, 0.1);\n  padding: 8px 8px 16px 8px;\n  position: relative;\n}\n.tox .tox-comment__header {\n  align-items: center;\n  color: #fff;\n  display: flex;\n  justify-content: space-between;\n}\n.tox .tox-comment__date {\n  color: #fff;\n  font-size: 12px;\n  line-height: 18px;\n}\n.tox .tox-comment__body {\n  color: #fff;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: normal;\n  line-height: 1.3;\n  margin-top: 8px;\n  position: relative;\n  text-transform: initial;\n}\n.tox .tox-comment__body textarea {\n  resize: none;\n  white-space: normal;\n  width: 100%;\n}\n.tox .tox-comment__expander {\n  padding-top: 8px;\n}\n.tox .tox-comment__expander p {\n  color: rgba(255, 255, 255, 0.5);\n  font-size: 14px;\n  font-style: normal;\n}\n.tox .tox-comment__body p {\n  margin: 0;\n}\n.tox .tox-comment__buttonspacing {\n  padding-top: 16px;\n  text-align: center;\n}\n.tox .tox-comment-thread__overlay::after {\n  background: #2b3b4e;\n  bottom: 0;\n  content: \"\";\n  display: flex;\n  left: 0;\n  opacity: 0.9;\n  position: absolute;\n  right: 0;\n  top: 0;\n  z-index: 5;\n}\n.tox .tox-comment__reply {\n  display: flex;\n  flex-shrink: 0;\n  flex-wrap: wrap;\n  justify-content: flex-end;\n  margin-top: 8px;\n}\n.tox .tox-comment__reply > *:first-child {\n  margin-bottom: 8px;\n  width: 100%;\n}\n.tox .tox-comment__edit {\n  display: flex;\n  flex-wrap: wrap;\n  justify-content: flex-end;\n  margin-top: 16px;\n}\n.tox .tox-comment__gradient::after {\n  background: linear-gradient(rgba(43, 59, 78, 0), #2b3b4e);\n  bottom: 0;\n  content: \"\";\n  display: block;\n  height: 5em;\n  margin-top: -40px;\n  position: absolute;\n  width: 100%;\n}\n.tox .tox-comment__overlay {\n  background: #2b3b4e;\n  bottom: 0;\n  display: flex;\n  flex-direction: column;\n  flex-grow: 1;\n  left: 0;\n  opacity: 0.9;\n  position: absolute;\n  right: 0;\n  text-align: center;\n  top: 0;\n  z-index: 5;\n}\n.tox .tox-comment__loading-text {\n  align-items: center;\n  color: #fff;\n  display: flex;\n  flex-direction: column;\n  position: relative;\n}\n.tox .tox-comment__loading-text > div {\n  padding-bottom: 16px;\n}\n.tox .tox-comment__overlaytext {\n  bottom: 0;\n  flex-direction: column;\n  font-size: 14px;\n  left: 0;\n  padding: 1em;\n  position: absolute;\n  right: 0;\n  top: 0;\n  z-index: 10;\n}\n.tox .tox-comment__overlaytext p {\n  background-color: #2b3b4e;\n  box-shadow: 0 0 8px 8px #2b3b4e;\n  color: #fff;\n  text-align: center;\n}\n.tox .tox-comment__overlaytext div:nth-of-type(2) {\n  font-size: 0.8em;\n}\n.tox .tox-comment__busy-spinner {\n  align-items: center;\n  background-color: #2b3b4e;\n  bottom: 0;\n  display: flex;\n  justify-content: center;\n  left: 0;\n  position: absolute;\n  right: 0;\n  top: 0;\n  z-index: 20;\n}\n.tox .tox-comment__scroll {\n  display: flex;\n  flex-direction: column;\n  flex-shrink: 1;\n  overflow: auto;\n}\n.tox .tox-conversations {\n  margin: 8px;\n}\n.tox:not([dir=rtl]) .tox-comment__edit {\n  margin-left: 8px;\n}\n.tox:not([dir=rtl]) .tox-comment__buttonspacing > *:last-child,\n.tox:not([dir=rtl]) .tox-comment__edit > *:last-child,\n.tox:not([dir=rtl]) .tox-comment__reply > *:last-child {\n  margin-left: 8px;\n}\n.tox[dir=rtl] .tox-comment__edit {\n  margin-right: 8px;\n}\n.tox[dir=rtl] .tox-comment__buttonspacing > *:last-child,\n.tox[dir=rtl] .tox-comment__edit > *:last-child,\n.tox[dir=rtl] .tox-comment__reply > *:last-child {\n  margin-right: 8px;\n}\n.tox .tox-user {\n  align-items: center;\n  display: flex;\n}\n.tox .tox-user__avatar svg {\n  fill: rgba(255, 255, 255, 0.5);\n}\n.tox .tox-user__avatar img {\n  border-radius: 50%;\n  height: 36px;\n  object-fit: cover;\n  vertical-align: middle;\n  width: 36px;\n}\n.tox .tox-user__name {\n  color: #fff;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: bold;\n  line-height: 18px;\n  text-transform: none;\n}\n.tox:not([dir=rtl]) .tox-user__avatar svg,\n.tox:not([dir=rtl]) .tox-user__avatar img {\n  margin-right: 8px;\n}\n.tox:not([dir=rtl]) .tox-user__avatar + .tox-user__name {\n  margin-left: 8px;\n}\n.tox[dir=rtl] .tox-user__avatar svg,\n.tox[dir=rtl] .tox-user__avatar img {\n  margin-left: 8px;\n}\n.tox[dir=rtl] .tox-user__avatar + .tox-user__name {\n  margin-right: 8px;\n}\n.tox .tox-dialog-wrap {\n  align-items: center;\n  bottom: 0;\n  display: flex;\n  justify-content: center;\n  left: 0;\n  position: fixed;\n  right: 0;\n  top: 0;\n  z-index: 1100;\n}\n.tox .tox-dialog-wrap__backdrop {\n  background-color: rgba(34, 47, 62, 0.75);\n  bottom: 0;\n  left: 0;\n  position: absolute;\n  right: 0;\n  top: 0;\n  z-index: 1;\n}\n.tox .tox-dialog-wrap__backdrop--opaque {\n  background-color: #222f3e;\n}\n.tox .tox-dialog {\n  background-color: #2b3b4e;\n  border-color: #000000;\n  border-radius: 3px;\n  border-style: solid;\n  border-width: 1px;\n  box-shadow: 0 16px 16px -10px rgba(42, 55, 70, 0.15), 0 0 40px 1px rgba(42, 55, 70, 0.15);\n  display: flex;\n  flex-direction: column;\n  max-height: 100%;\n  max-width: 480px;\n  overflow: hidden;\n  position: relative;\n  width: 95vw;\n  z-index: 2;\n}\n@media only screen and (max-width: 767px ) {\n  body:not(.tox-force-desktop) .tox .tox-dialog {\n    align-self: flex-start;\n    margin: 8px auto;\n    max-height: calc(100vh - 8px * 2);\n    width: calc(100vw - 16px);\n  }\n}\n.tox .tox-dialog-inline {\n  z-index: 1100;\n}\n.tox .tox-dialog__header {\n  align-items: center;\n  background-color: #2b3b4e;\n  border-bottom: none;\n  color: #fff;\n  display: flex;\n  font-size: 16px;\n  justify-content: space-between;\n  padding: 8px 16px 0 16px;\n  position: relative;\n}\n.tox .tox-dialog__header .tox-button {\n  z-index: 1;\n}\n.tox .tox-dialog__draghandle {\n  cursor: grab;\n  height: 100%;\n  left: 0;\n  position: absolute;\n  top: 0;\n  width: 100%;\n}\n.tox .tox-dialog__draghandle:active {\n  cursor: grabbing;\n}\n.tox .tox-dialog__dismiss {\n  margin-left: auto;\n}\n.tox .tox-dialog__title {\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n  font-size: 20px;\n  font-style: normal;\n  font-weight: normal;\n  line-height: 1.3;\n  margin: 0;\n  text-transform: none;\n}\n.tox .tox-dialog__body {\n  color: #fff;\n  display: flex;\n  flex: 1;\n  font-size: 16px;\n  font-style: normal;\n  font-weight: normal;\n  line-height: 1.3;\n  min-width: 0;\n  text-align: left;\n  text-transform: none;\n}\n@media only screen and (max-width: 767px ) {\n  body:not(.tox-force-desktop) .tox .tox-dialog__body {\n    flex-direction: column;\n  }\n}\n.tox .tox-dialog__body-nav {\n  align-items: flex-start;\n  display: flex;\n  flex-direction: column;\n  flex-shrink: 0;\n  padding: 16px 16px;\n}\n@media only screen and (min-width: 768px ) {\n  .tox .tox-dialog__body-nav {\n    max-width: 11em;\n  }\n}\n@media only screen and (max-width: 767px ) {\n  body:not(.tox-force-desktop) .tox .tox-dialog__body-nav {\n    flex-direction: row;\n    -webkit-overflow-scrolling: touch;\n    overflow-x: auto;\n    padding-bottom: 0;\n  }\n}\n.tox .tox-dialog__body-nav-item {\n  border-bottom: 2px solid transparent;\n  color: rgba(255, 255, 255, 0.5);\n  display: inline-block;\n  flex-shrink: 0;\n  font-size: 14px;\n  line-height: 1.3;\n  margin-bottom: 8px;\n  max-width: 13em;\n  text-decoration: none;\n}\n.tox .tox-dialog__body-nav-item:focus {\n  background-color: rgba(32, 122, 183, 0.1);\n}\n.tox .tox-dialog__body-nav-item--active {\n  border-bottom: 2px solid #207ab7;\n  color: #207ab7;\n}\n.tox .tox-dialog__body-content {\n  box-sizing: border-box;\n  display: flex;\n  flex: 1;\n  flex-direction: column;\n  max-height: min(650px, calc(100vh - 110px));\n  overflow: auto;\n  -webkit-overflow-scrolling: touch;\n  padding: 16px 16px;\n}\n.tox .tox-dialog__body-content > * {\n  margin-bottom: 0;\n  margin-top: 16px;\n}\n.tox .tox-dialog__body-content > *:first-child {\n  margin-top: 0;\n}\n.tox .tox-dialog__body-content > *:last-child {\n  margin-bottom: 0;\n}\n.tox .tox-dialog__body-content > *:only-child {\n  margin-bottom: 0;\n  margin-top: 0;\n}\n.tox .tox-dialog__body-content a {\n  color: #207ab7;\n  cursor: pointer;\n  text-decoration: none;\n}\n.tox .tox-dialog__body-content a:hover,\n.tox .tox-dialog__body-content a:focus {\n  color: #185d8c;\n  text-decoration: none;\n}\n.tox .tox-dialog__body-content a:active {\n  color: #185d8c;\n  text-decoration: none;\n}\n.tox .tox-dialog__body-content svg {\n  fill: #fff;\n}\n.tox .tox-dialog__body-content strong {\n  font-weight: bold;\n}\n.tox .tox-dialog__body-content ul {\n  list-style-type: disc;\n}\n.tox .tox-dialog__body-content ul,\n.tox .tox-dialog__body-content ol,\n.tox .tox-dialog__body-content dd {\n  padding-inline-start: 2.5rem;\n}\n.tox .tox-dialog__body-content ul,\n.tox .tox-dialog__body-content ol,\n.tox .tox-dialog__body-content dl {\n  margin-bottom: 16px;\n}\n.tox .tox-dialog__body-content ul,\n.tox .tox-dialog__body-content ol,\n.tox .tox-dialog__body-content dl,\n.tox .tox-dialog__body-content dd,\n.tox .tox-dialog__body-content dt {\n  display: block;\n  margin-inline-end: 0;\n  margin-inline-start: 0;\n}\n.tox .tox-dialog__body-content .tox-form__group h1 {\n  color: #fff;\n  font-size: 20px;\n  font-style: normal;\n  font-weight: bold;\n  letter-spacing: normal;\n  margin-bottom: 16px;\n  margin-top: 2rem;\n  text-transform: none;\n}\n.tox .tox-dialog__body-content .tox-form__group h2 {\n  color: #fff;\n  font-size: 16px;\n  font-style: normal;\n  font-weight: bold;\n  letter-spacing: normal;\n  margin-bottom: 16px;\n  margin-top: 2rem;\n  text-transform: none;\n}\n.tox .tox-dialog__body-content .tox-form__group p {\n  margin-bottom: 16px;\n}\n.tox .tox-dialog__body-content .tox-form__group h1:first-child,\n.tox .tox-dialog__body-content .tox-form__group h2:first-child,\n.tox .tox-dialog__body-content .tox-form__group p:first-child {\n  margin-top: 0;\n}\n.tox .tox-dialog__body-content .tox-form__group h1:last-child,\n.tox .tox-dialog__body-content .tox-form__group h2:last-child,\n.tox .tox-dialog__body-content .tox-form__group p:last-child {\n  margin-bottom: 0;\n}\n.tox .tox-dialog__body-content .tox-form__group h1:only-child,\n.tox .tox-dialog__body-content .tox-form__group h2:only-child,\n.tox .tox-dialog__body-content .tox-form__group p:only-child {\n  margin-bottom: 0;\n  margin-top: 0;\n}\n.tox .tox-dialog__body-content .tox-form__group .tox-label.tox-label--center {\n  text-align: center;\n}\n.tox .tox-dialog__body-content .tox-form__group .tox-label.tox-label--end {\n  text-align: end;\n}\n.tox .tox-dialog--width-lg {\n  height: 650px;\n  max-width: 1200px;\n}\n.tox .tox-dialog--fullscreen {\n  height: 100%;\n  max-width: 100%;\n}\n.tox .tox-dialog--fullscreen .tox-dialog__body-content {\n  max-height: 100%;\n}\n.tox .tox-dialog--width-md {\n  max-width: 800px;\n}\n.tox .tox-dialog--width-md .tox-dialog__body-content {\n  overflow: auto;\n}\n.tox .tox-dialog__body-content--centered {\n  text-align: center;\n}\n.tox .tox-dialog__footer {\n  align-items: center;\n  background-color: #2b3b4e;\n  border-top: 1px solid #000000;\n  display: flex;\n  justify-content: space-between;\n  padding: 8px 16px;\n}\n.tox .tox-dialog__footer-start,\n.tox .tox-dialog__footer-end {\n  display: flex;\n}\n.tox .tox-dialog__busy-spinner {\n  align-items: center;\n  background-color: rgba(34, 47, 62, 0.75);\n  bottom: 0;\n  display: flex;\n  justify-content: center;\n  left: 0;\n  position: absolute;\n  right: 0;\n  top: 0;\n  z-index: 3;\n}\n.tox .tox-dialog__table {\n  border-collapse: collapse;\n  width: 100%;\n}\n.tox .tox-dialog__table thead th {\n  font-weight: bold;\n  padding-bottom: 8px;\n}\n.tox .tox-dialog__table thead th:first-child {\n  padding-right: 8px;\n}\n.tox .tox-dialog__table tbody tr {\n  border-bottom: 1px solid #000000;\n}\n.tox .tox-dialog__table tbody tr:last-child {\n  border-bottom: none;\n}\n.tox .tox-dialog__table td {\n  padding-bottom: 8px;\n  padding-top: 8px;\n}\n.tox .tox-dialog__table td:first-child {\n  padding-right: 8px;\n}\n.tox .tox-dialog__iframe {\n  min-height: 200px;\n}\n.tox .tox-dialog__iframe.tox-dialog__iframe--opaque {\n  background: #fff;\n}\n.tox .tox-dialog__iframe.tox-dialog__iframe--bordered {\n  border: 1px solid #000000;\n  border-radius: 3px;\n}\n.tox .tox-dialog__popups {\n  position: absolute;\n  width: 100%;\n  z-index: 1100;\n}\n.tox .tox-dialog__body-iframe {\n  display: flex;\n  flex: 1;\n  flex-direction: column;\n}\n.tox .tox-dialog__body-iframe .tox-navobj {\n  display: flex;\n  flex: 1;\n}\n.tox .tox-dialog__body-iframe .tox-navobj :nth-child(2) {\n  flex: 1;\n  height: 100%;\n}\n.tox .tox-dialog-dock-fadeout {\n  opacity: 0;\n  visibility: hidden;\n}\n.tox .tox-dialog-dock-fadein {\n  opacity: 1;\n  visibility: visible;\n}\n.tox .tox-dialog-dock-transition {\n  transition: visibility 0s linear 0.3s, opacity 0.3s ease;\n}\n.tox .tox-dialog-dock-transition.tox-dialog-dock-fadein {\n  transition-delay: 0s;\n}\n@media only screen and (max-width: 767px ) {\n  body:not(.tox-force-desktop) .tox:not([dir=rtl]) .tox-dialog__body-nav {\n    margin-right: 0;\n  }\n}\n@media only screen and (max-width: 767px ) {\n  body:not(.tox-force-desktop) .tox:not([dir=rtl]) .tox-dialog__body-nav-item:not(:first-child) {\n    margin-left: 8px;\n  }\n}\n.tox:not([dir=rtl]) .tox-dialog__footer .tox-dialog__footer-start > *,\n.tox:not([dir=rtl]) .tox-dialog__footer .tox-dialog__footer-end > * {\n  margin-left: 8px;\n}\n.tox[dir=rtl] .tox-dialog__body {\n  text-align: right;\n}\n@media only screen and (max-width: 767px ) {\n  body:not(.tox-force-desktop) .tox[dir=rtl] .tox-dialog__body-nav {\n    margin-left: 0;\n  }\n}\n@media only screen and (max-width: 767px ) {\n  body:not(.tox-force-desktop) .tox[dir=rtl] .tox-dialog__body-nav-item:not(:first-child) {\n    margin-right: 8px;\n  }\n}\n.tox[dir=rtl] .tox-dialog__footer .tox-dialog__footer-start > *,\n.tox[dir=rtl] .tox-dialog__footer .tox-dialog__footer-end > * {\n  margin-right: 8px;\n}\nbody.tox-dialog__disable-scroll {\n  overflow: hidden;\n}\n.tox .tox-dropzone-container {\n  display: flex;\n  flex: 1;\n}\n.tox .tox-dropzone {\n  align-items: center;\n  background: #fff;\n  border: 2px dashed #000000;\n  box-sizing: border-box;\n  display: flex;\n  flex-direction: column;\n  flex-grow: 1;\n  justify-content: center;\n  min-height: 100px;\n  padding: 10px;\n}\n.tox .tox-dropzone p {\n  color: rgba(255, 255, 255, 0.5);\n  margin: 0 0 16px 0;\n}\n.tox .tox-edit-area {\n  display: flex;\n  flex: 1;\n  overflow: hidden;\n  position: relative;\n}\n.tox .tox-edit-area::before {\n  border: 2px solid #2D6ADF;\n  border-radius: 4px;\n  content: '';\n  inset: 0;\n  opacity: 0;\n  pointer-events: none;\n  position: absolute;\n  transition: opacity 0.15s;\n  z-index: 1;\n}\n.tox .tox-edit-area__iframe {\n  background-color: #fff;\n  border: 0;\n  box-sizing: border-box;\n  flex: 1;\n  height: 100%;\n  position: absolute;\n  width: 100%;\n}\n.tox.tox-edit-focus .tox-edit-area::before {\n  opacity: 1;\n}\n.tox.tox-inline-edit-area {\n  border: 1px dotted #000000;\n}\n.tox .tox-editor-container {\n  display: flex;\n  flex: 1 1 auto;\n  flex-direction: column;\n  overflow: hidden;\n}\n.tox .tox-editor-header {\n  display: grid;\n  grid-template-columns: 1fr min-content;\n  z-index: 2;\n}\n.tox:not(.tox-tinymce-inline) .tox-editor-header {\n  background-color: #222f3e;\n  border-bottom: none;\n  box-shadow: none;\n  padding: 4px 0;\n}\n.tox:not(.tox-tinymce-inline) .tox-editor-header:not(.tox-editor-dock-transition) {\n  transition: box-shadow 0.5s;\n}\n.tox:not(.tox-tinymce-inline).tox-tinymce--toolbar-bottom .tox-editor-header {\n  border-top: 1px solid #000000;\n  box-shadow: none;\n}\n.tox:not(.tox-tinymce-inline).tox-tinymce--toolbar-sticky-on .tox-editor-header {\n  background-color: #222f3e;\n  box-shadow: 0 4px 4px -3px rgba(0, 0, 0, 0.25);\n  padding: 4px 0;\n}\n.tox:not(.tox-tinymce-inline).tox-tinymce--toolbar-sticky-on.tox-tinymce--toolbar-bottom .tox-editor-header {\n  box-shadow: 0 4px 4px -3px rgba(0, 0, 0, 0.25);\n}\n.tox.tox:not(.tox-tinymce-inline) .tox-editor-header.tox-editor-header--empty {\n  background: none;\n  border: none;\n  box-shadow: none;\n  padding: 0;\n}\n.tox-editor-dock-fadeout {\n  opacity: 0;\n  visibility: hidden;\n}\n.tox-editor-dock-fadein {\n  opacity: 1;\n  visibility: visible;\n}\n.tox-editor-dock-transition {\n  transition: visibility 0s linear 0.25s, opacity 0.25s ease;\n}\n.tox-editor-dock-transition.tox-editor-dock-fadein {\n  transition-delay: 0s;\n}\n.tox .tox-control-wrap {\n  flex: 1;\n  position: relative;\n}\n.tox .tox-control-wrap:not(.tox-control-wrap--status-invalid) .tox-control-wrap__status-icon-invalid,\n.tox .tox-control-wrap:not(.tox-control-wrap--status-unknown) .tox-control-wrap__status-icon-unknown,\n.tox .tox-control-wrap:not(.tox-control-wrap--status-valid) .tox-control-wrap__status-icon-valid {\n  display: none;\n}\n.tox .tox-control-wrap svg {\n  display: block;\n}\n.tox .tox-control-wrap__status-icon-wrap {\n  position: absolute;\n  top: 50%;\n  transform: translateY(-50%);\n}\n.tox .tox-control-wrap__status-icon-invalid svg {\n  fill: #c00;\n}\n.tox .tox-control-wrap__status-icon-unknown svg {\n  fill: orange;\n}\n.tox .tox-control-wrap__status-icon-valid svg {\n  fill: green;\n}\n.tox:not([dir=rtl]) .tox-control-wrap--status-invalid .tox-textfield,\n.tox:not([dir=rtl]) .tox-control-wrap--status-unknown .tox-textfield,\n.tox:not([dir=rtl]) .tox-control-wrap--status-valid .tox-textfield {\n  padding-right: 32px;\n}\n.tox:not([dir=rtl]) .tox-control-wrap__status-icon-wrap {\n  right: 4px;\n}\n.tox[dir=rtl] .tox-control-wrap--status-invalid .tox-textfield,\n.tox[dir=rtl] .tox-control-wrap--status-unknown .tox-textfield,\n.tox[dir=rtl] .tox-control-wrap--status-valid .tox-textfield {\n  padding-left: 32px;\n}\n.tox[dir=rtl] .tox-control-wrap__status-icon-wrap {\n  left: 4px;\n}\n.tox .tox-autocompleter {\n  max-width: 25em;\n}\n.tox .tox-autocompleter .tox-menu {\n  box-sizing: border-box;\n  max-width: 25em;\n}\n.tox .tox-autocompleter .tox-autocompleter-highlight {\n  font-weight: bold;\n}\n.tox .tox-color-input {\n  display: flex;\n  position: relative;\n  z-index: 1;\n}\n.tox .tox-color-input .tox-textfield {\n  z-index: -1;\n}\n.tox .tox-color-input span {\n  border-color: rgba(42, 55, 70, 0.2);\n  border-radius: 3px;\n  border-style: solid;\n  border-width: 1px;\n  box-shadow: none;\n  box-sizing: border-box;\n  height: 24px;\n  position: absolute;\n  top: 6px;\n  width: 24px;\n}\n.tox .tox-color-input span:hover:not([aria-disabled=true]),\n.tox .tox-color-input span:focus:not([aria-disabled=true]) {\n  border-color: #207ab7;\n  cursor: pointer;\n}\n.tox .tox-color-input span::before {\n  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.25) 25%, transparent 25%), linear-gradient(-45deg, rgba(255, 255, 255, 0.25) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, rgba(255, 255, 255, 0.25) 75%), linear-gradient(-45deg, transparent 75%, rgba(255, 255, 255, 0.25) 75%);\n  background-position: 0 0, 0 6px, 6px -6px, -6px 0;\n  background-size: 12px 12px;\n  border: 1px solid #2b3b4e;\n  border-radius: 3px;\n  box-sizing: border-box;\n  content: '';\n  height: 24px;\n  left: -1px;\n  position: absolute;\n  top: -1px;\n  width: 24px;\n  z-index: -1;\n}\n.tox .tox-color-input span[aria-disabled=true] {\n  cursor: not-allowed;\n}\n.tox:not([dir=rtl]) .tox-color-input {\n  /* stylelint-disable-next-line no-descending-specificity */\n}\n.tox:not([dir=rtl]) .tox-color-input .tox-textfield {\n  padding-left: 36px;\n}\n.tox:not([dir=rtl]) .tox-color-input span {\n  left: 6px;\n}\n.tox[dir=\"rtl\"] .tox-color-input {\n  /* stylelint-disable-next-line no-descending-specificity */\n}\n.tox[dir=\"rtl\"] .tox-color-input .tox-textfield {\n  padding-right: 36px;\n}\n.tox[dir=\"rtl\"] .tox-color-input span {\n  right: 6px;\n}\n.tox .tox-label,\n.tox .tox-toolbar-label {\n  color: rgba(255, 255, 255, 0.5);\n  display: block;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: normal;\n  line-height: 1.3;\n  padding: 0 8px 0 0;\n  text-transform: none;\n  white-space: nowrap;\n}\n.tox .tox-toolbar-label {\n  padding: 0 8px;\n}\n.tox[dir=rtl] .tox-label {\n  padding: 0 0 0 8px;\n}\n.tox .tox-form {\n  display: flex;\n  flex: 1;\n  flex-direction: column;\n}\n.tox .tox-form__group {\n  box-sizing: border-box;\n  margin-bottom: 4px;\n}\n.tox .tox-form-group--maximize {\n  flex: 1;\n}\n.tox .tox-form__group--error {\n  color: #c00;\n}\n.tox .tox-form__group--collection {\n  display: flex;\n}\n.tox .tox-form__grid {\n  display: flex;\n  flex-direction: row;\n  flex-wrap: wrap;\n  justify-content: space-between;\n}\n.tox .tox-form__grid--2col > .tox-form__group {\n  width: calc(50% - (8px / 2));\n}\n.tox .tox-form__grid--3col > .tox-form__group {\n  width: calc(100% / 3 - (8px / 2));\n}\n.tox .tox-form__grid--4col > .tox-form__group {\n  width: calc(25% - (8px / 2));\n}\n.tox .tox-form__controls-h-stack {\n  align-items: center;\n  display: flex;\n}\n.tox .tox-form__group--inline {\n  align-items: center;\n  display: flex;\n}\n.tox .tox-form__group--stretched {\n  display: flex;\n  flex: 1;\n  flex-direction: column;\n}\n.tox .tox-form__group--stretched .tox-textarea {\n  flex: 1;\n}\n.tox .tox-form__group--stretched .tox-navobj {\n  display: flex;\n  flex: 1;\n}\n.tox .tox-form__group--stretched .tox-navobj :nth-child(2) {\n  flex: 1;\n  height: 100%;\n}\n.tox:not([dir=rtl]) .tox-form__controls-h-stack > *:not(:first-child) {\n  margin-left: 4px;\n}\n.tox[dir=rtl] .tox-form__controls-h-stack > *:not(:first-child) {\n  margin-right: 4px;\n}\n.tox .tox-lock.tox-locked .tox-lock-icon__unlock,\n.tox .tox-lock:not(.tox-locked) .tox-lock-icon__lock {\n  display: none;\n}\n.tox .tox-textfield,\n.tox .tox-toolbar-textfield,\n.tox .tox-listboxfield .tox-listbox--select,\n.tox .tox-textarea,\n.tox .tox-textarea-wrap .tox-textarea:focus {\n  -webkit-appearance: none;\n     -moz-appearance: none;\n          appearance: none;\n  background-color: #2b3b4e;\n  border-color: #000000;\n  border-radius: 3px;\n  border-style: solid;\n  border-width: 1px;\n  box-shadow: none;\n  box-sizing: border-box;\n  color: #fff;\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n  font-size: 16px;\n  line-height: 24px;\n  margin: 0;\n  min-height: 34px;\n  outline: none;\n  padding: 5px 4.75px;\n  resize: none;\n  width: 100%;\n}\n.tox .tox-textfield[disabled],\n.tox .tox-textarea[disabled] {\n  background-color: #222f3e;\n  color: rgba(255, 255, 255, 0.85);\n  cursor: not-allowed;\n}\n.tox .tox-textfield:focus,\n.tox .tox-dialog__iframe.tox-dialog__iframe--bordered:focus-within,\n.tox .tox-listboxfield .tox-listbox--select:focus,\n.tox .tox-textarea-wrap:focus-within,\n.tox .tox-textarea:focus,\n.tox .tox-custom-editor:focus-within {\n  background-color: #2b3b4e;\n  border-color: #207ab7;\n  box-shadow: none;\n  outline: 2px solid rgba(32, 122, 183, 0.25);\n}\n.tox .tox-toolbar-textfield {\n  border-width: 0;\n  margin-bottom: 3px;\n  margin-top: 2px;\n  max-width: 250px;\n}\n.tox .tox-naked-btn {\n  background-color: transparent;\n  border: 0;\n  border-color: transparent;\n  box-shadow: unset;\n  color: #207ab7;\n  cursor: pointer;\n  display: block;\n  margin: 0;\n  padding: 0;\n}\n.tox .tox-naked-btn svg {\n  display: block;\n  fill: #fff;\n}\n.tox:not([dir=rtl]) .tox-toolbar-textfield + * {\n  margin-left: 4px;\n}\n.tox[dir=rtl] .tox-toolbar-textfield + * {\n  margin-right: 4px;\n}\n.tox .tox-listboxfield {\n  cursor: pointer;\n  position: relative;\n}\n.tox .tox-listboxfield .tox-listbox--select[disabled] {\n  background-color: #19232e;\n  color: rgba(255, 255, 255, 0.85);\n  cursor: not-allowed;\n}\n.tox .tox-listbox__select-label {\n  cursor: default;\n  flex: 1;\n  margin: 0 4px;\n}\n.tox .tox-listbox__select-chevron {\n  align-items: center;\n  display: flex;\n  justify-content: center;\n  width: 16px;\n}\n.tox .tox-listbox__select-chevron svg {\n  fill: #fff;\n}\n.tox .tox-listboxfield .tox-listbox--select {\n  align-items: center;\n  display: flex;\n}\n.tox:not([dir=rtl]) .tox-listboxfield svg {\n  right: 8px;\n}\n.tox[dir=rtl] .tox-listboxfield svg {\n  left: 8px;\n}\n.tox .tox-selectfield {\n  cursor: pointer;\n  position: relative;\n}\n.tox .tox-selectfield select {\n  -webkit-appearance: none;\n     -moz-appearance: none;\n          appearance: none;\n  background-color: #2b3b4e;\n  border-color: #000000;\n  border-radius: 3px;\n  border-style: solid;\n  border-width: 1px;\n  box-shadow: none;\n  box-sizing: border-box;\n  color: #fff;\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n  font-size: 16px;\n  line-height: 24px;\n  margin: 0;\n  min-height: 34px;\n  outline: none;\n  padding: 5px 4.75px;\n  resize: none;\n  width: 100%;\n}\n.tox .tox-selectfield select[disabled] {\n  background-color: #19232e;\n  color: rgba(255, 255, 255, 0.85);\n  cursor: not-allowed;\n}\n.tox .tox-selectfield select::-ms-expand {\n  display: none;\n}\n.tox .tox-selectfield select:focus {\n  background-color: #2b3b4e;\n  border-color: #207ab7;\n  box-shadow: none;\n  outline: 2px solid rgba(32, 122, 183, 0.25);\n}\n.tox .tox-selectfield svg {\n  pointer-events: none;\n  position: absolute;\n  top: 50%;\n  transform: translateY(-50%);\n}\n.tox:not([dir=rtl]) .tox-selectfield select[size=\"0\"],\n.tox:not([dir=rtl]) .tox-selectfield select[size=\"1\"] {\n  padding-right: 24px;\n}\n.tox:not([dir=rtl]) .tox-selectfield svg {\n  right: 8px;\n}\n.tox[dir=rtl] .tox-selectfield select[size=\"0\"],\n.tox[dir=rtl] .tox-selectfield select[size=\"1\"] {\n  padding-left: 24px;\n}\n.tox[dir=rtl] .tox-selectfield svg {\n  left: 8px;\n}\n.tox .tox-textarea-wrap {\n  border-color: #000000;\n  border-radius: 3px;\n  border-style: solid;\n  border-width: 1px;\n  display: flex;\n  flex: 1;\n  overflow: hidden;\n}\n.tox .tox-textarea {\n  -webkit-appearance: textarea;\n     -moz-appearance: textarea;\n          appearance: textarea;\n  white-space: pre-wrap;\n}\n.tox .tox-textarea-wrap .tox-textarea {\n  border: none;\n}\n.tox .tox-textarea-wrap .tox-textarea:focus {\n  border: none;\n}\n.tox-fullscreen {\n  border: 0;\n  height: 100%;\n  margin: 0;\n  overflow: hidden;\n  overscroll-behavior: none;\n  padding: 0;\n  touch-action: pinch-zoom;\n  width: 100%;\n}\n.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle {\n  display: none;\n}\n.tox.tox-tinymce.tox-fullscreen,\n.tox-shadowhost.tox-fullscreen {\n  left: 0;\n  position: fixed;\n  top: 0;\n  z-index: 1200;\n}\n.tox.tox-tinymce.tox-fullscreen {\n  background-color: transparent;\n}\n.tox-fullscreen .tox.tox-tinymce-aux,\n.tox-fullscreen ~ .tox.tox-tinymce-aux {\n  z-index: 1201;\n}\n.tox .tox-help__more-link {\n  list-style: none;\n  margin-top: 1em;\n}\n.tox .tox-imagepreview {\n  background-color: #666;\n  height: 380px;\n  overflow: hidden;\n  position: relative;\n  width: 100%;\n}\n.tox .tox-imagepreview.tox-imagepreview__loaded {\n  overflow: auto;\n}\n.tox .tox-imagepreview__container {\n  display: flex;\n  left: 100vw;\n  position: absolute;\n  top: 100vw;\n}\n.tox .tox-imagepreview__image {\n  background: url(data:image/gif;base64,R0lGODdhDAAMAIABAMzMzP///ywAAAAADAAMAAACFoQfqYeabNyDMkBQb81Uat85nxguUAEAOw==);\n}\n.tox .tox-image-tools .tox-spacer {\n  flex: 1;\n}\n.tox .tox-image-tools .tox-bar {\n  align-items: center;\n  display: flex;\n  height: 60px;\n  justify-content: center;\n}\n.tox .tox-image-tools .tox-imagepreview,\n.tox .tox-image-tools .tox-imagepreview + .tox-bar {\n  margin-top: 8px;\n}\n.tox .tox-image-tools .tox-croprect-block {\n  background: black;\n  filter: alpha(opacity=50);\n  opacity: 0.5;\n  position: absolute;\n  zoom: 1;\n}\n.tox .tox-image-tools .tox-croprect-handle {\n  border: 2px solid white;\n  height: 20px;\n  left: 0;\n  position: absolute;\n  top: 0;\n  width: 20px;\n}\n.tox .tox-image-tools .tox-croprect-handle-move {\n  border: 0;\n  cursor: move;\n  position: absolute;\n}\n.tox .tox-image-tools .tox-croprect-handle-nw {\n  border-width: 2px 0 0 2px;\n  cursor: nw-resize;\n  left: 100px;\n  margin: -2px 0 0 -2px;\n  top: 100px;\n}\n.tox .tox-image-tools .tox-croprect-handle-ne {\n  border-width: 2px 2px 0 0;\n  cursor: ne-resize;\n  left: 200px;\n  margin: -2px 0 0 -20px;\n  top: 100px;\n}\n.tox .tox-image-tools .tox-croprect-handle-sw {\n  border-width: 0 0 2px 2px;\n  cursor: sw-resize;\n  left: 100px;\n  margin: -20px 2px 0 -2px;\n  top: 200px;\n}\n.tox .tox-image-tools .tox-croprect-handle-se {\n  border-width: 0 2px 2px 0;\n  cursor: se-resize;\n  left: 200px;\n  margin: -20px 0 0 -20px;\n  top: 200px;\n}\n.tox .tox-insert-table-picker {\n  display: flex;\n  flex-wrap: wrap;\n  width: 170px;\n}\n.tox .tox-insert-table-picker > div {\n  border-color: #000000;\n  border-style: solid;\n  border-width: 0 1px 1px 0;\n  box-sizing: border-box;\n  height: 17px;\n  width: 17px;\n}\n.tox .tox-collection--list .tox-collection__group .tox-insert-table-picker {\n  margin: 0 -4px;\n}\n.tox .tox-insert-table-picker .tox-insert-table-picker__selected {\n  background-color: rgba(32, 122, 183, 0.5);\n  border-color: rgba(32, 122, 183, 0.5);\n}\n.tox .tox-insert-table-picker__label {\n  color: #fff;\n  display: block;\n  font-size: 14px;\n  padding: 4px;\n  text-align: center;\n  width: 100%;\n}\n.tox:not([dir=rtl]) {\n  /* stylelint-disable-next-line no-descending-specificity */\n}\n.tox:not([dir=rtl]) .tox-insert-table-picker > div:nth-child(10n) {\n  border-right: 0;\n}\n.tox[dir=rtl] {\n  /* stylelint-disable-next-line no-descending-specificity */\n}\n.tox[dir=rtl] .tox-insert-table-picker > div:nth-child(10n+1) {\n  border-right: 0;\n}\n.tox {\n  /* stylelint-disable */\n  /* stylelint-enable */\n}\n.tox .tox-menu {\n  background-color: #2b3b4e;\n  border: 1px solid #000000;\n  border-radius: 3px;\n  box-shadow: 0 4px 8px 0 rgba(42, 55, 70, 0.1);\n  display: inline-block;\n  overflow: hidden;\n  vertical-align: top;\n  z-index: 1150;\n}\n.tox .tox-menu.tox-collection.tox-collection--list {\n  padding: 0 0;\n}\n.tox .tox-menu.tox-collection.tox-collection--toolbar {\n  padding: 4px;\n}\n.tox .tox-menu.tox-collection.tox-collection--grid {\n  padding: 4px;\n}\n@media only screen and (min-width: 768px ) {\n  .tox .tox-menu .tox-collection__item-label {\n    overflow-wrap: break-word;\n    word-break: normal;\n  }\n}\n.tox .tox-menu__label h1,\n.tox .tox-menu__label h2,\n.tox .tox-menu__label h3,\n.tox .tox-menu__label h4,\n.tox .tox-menu__label h5,\n.tox .tox-menu__label h6,\n.tox .tox-menu__label p,\n.tox .tox-menu__label blockquote,\n.tox .tox-menu__label code {\n  margin: 0;\n}\n.tox .tox-menubar {\n  background: url(\"data:image/svg+xml;charset=utf8,%3Csvg height='39px' viewBox='0 0 40 39px' width='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='38px' width='100' height='1' fill='%23000000'/%3E%3C/svg%3E\") left 0 top 0 #222f3e;\n  background-color: #222f3e;\n  display: flex;\n  flex: 0 0 auto;\n  flex-shrink: 0;\n  flex-wrap: wrap;\n  grid-column: 1 / -1;\n  grid-row: 1;\n  padding: 0 4px 0 4px;\n}\n.tox .tox-promotion + .tox-menubar {\n  grid-column: 1;\n}\n.tox .tox-promotion {\n  background: url(\"data:image/svg+xml;charset=utf8,%3Csvg height='39px' viewBox='0 0 40 39px' width='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='38px' width='100' height='1' fill='%23000000'/%3E%3C/svg%3E\") left 0 top 0 #222f3e;\n  background-color: #222f3e;\n  grid-column: 2;\n  grid-row: 1;\n  padding-inline-end: 8px;\n  padding-inline-start: 4px;\n  padding-top: 5px;\n}\n.tox .tox-promotion-link {\n  align-items: unsafe center;\n  background-color: #E8F1F8;\n  border-radius: 5px;\n  color: #086BE6;\n  cursor: pointer;\n  display: flex;\n  font-size: 14px;\n  height: 26.6px;\n  padding: 4px 8px;\n  white-space: nowrap;\n}\n.tox .tox-promotion-link:hover {\n  background-color: #B4D7FF;\n}\n.tox .tox-promotion-link:focus {\n  background-color: #D9EDF7;\n}\n/* Deprecated. Remove in next major release */\n.tox .tox-mbtn {\n  align-items: center;\n  background: transparent;\n  border: 0;\n  border-radius: 3px;\n  box-shadow: none;\n  color: #fff;\n  display: flex;\n  flex: 0 0 auto;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: normal;\n  height: 34px;\n  justify-content: center;\n  margin: 2px 0 3px 0;\n  outline: none;\n  overflow: hidden;\n  padding: 0 4px;\n  text-transform: none;\n  width: auto;\n}\n.tox .tox-mbtn[disabled] {\n  background-color: transparent;\n  border: 0;\n  box-shadow: none;\n  color: rgba(255, 255, 255, 0.5);\n  cursor: not-allowed;\n}\n.tox .tox-mbtn:focus:not(:disabled) {\n  background: #4a5562;\n  border: 0;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-mbtn--active {\n  background: #757d87;\n  border: 0;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-mbtn:hover:not(:disabled):not(.tox-mbtn--active) {\n  background: #4a5562;\n  border: 0;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-mbtn__select-label {\n  cursor: default;\n  font-weight: normal;\n  margin: 0 4px;\n}\n.tox .tox-mbtn[disabled] .tox-mbtn__select-label {\n  cursor: not-allowed;\n}\n.tox .tox-mbtn__select-chevron {\n  align-items: center;\n  display: flex;\n  justify-content: center;\n  width: 16px;\n  display: none;\n}\n.tox .tox-notification {\n  border-radius: 3px;\n  border-style: solid;\n  border-width: 1px;\n  box-shadow: none;\n  box-sizing: border-box;\n  display: grid;\n  font-size: 14px;\n  font-weight: normal;\n  grid-template-columns: minmax(40px, 1fr) auto minmax(40px, 1fr);\n  margin-top: 4px;\n  opacity: 0;\n  padding: 4px;\n  transition: transform 100ms ease-in, opacity 150ms ease-in;\n}\n.tox .tox-notification p {\n  font-size: 14px;\n  font-weight: normal;\n}\n.tox .tox-notification a {\n  cursor: pointer;\n  text-decoration: underline;\n}\n.tox .tox-notification--in {\n  opacity: 1;\n}\n.tox .tox-notification--success {\n  background-color: #334840;\n  border-color: #3c5440;\n  color: #fff;\n}\n.tox .tox-notification--success p {\n  color: #fff;\n}\n.tox .tox-notification--success a {\n  color: #b5d199;\n}\n.tox .tox-notification--success svg {\n  fill: #fff;\n}\n.tox .tox-notification--error {\n  background-color: #442632;\n  border-color: #55212b;\n  color: #fff;\n}\n.tox .tox-notification--error p {\n  color: #fff;\n}\n.tox .tox-notification--error a {\n  color: #e68080;\n}\n.tox .tox-notification--error svg {\n  fill: #fff;\n}\n.tox .tox-notification--warn,\n.tox .tox-notification--warning {\n  background-color: #222f3e;\n  border-color: #000000;\n  color: #fff0b3;\n}\n.tox .tox-notification--warn p,\n.tox .tox-notification--warning p {\n  color: #fff0b3;\n}\n.tox .tox-notification--warn a,\n.tox .tox-notification--warning a {\n  color: #ffcc00;\n}\n.tox .tox-notification--warn svg,\n.tox .tox-notification--warning svg {\n  fill: #fff0b3;\n}\n.tox .tox-notification--info {\n  background-color: #254161;\n  border-color: #264972;\n  color: #fff;\n}\n.tox .tox-notification--info p {\n  color: #fff;\n}\n.tox .tox-notification--info a {\n  color: #83b7f3;\n}\n.tox .tox-notification--info svg {\n  fill: #fff;\n}\n.tox .tox-notification__body {\n  align-self: center;\n  color: #fff;\n  font-size: 14px;\n  grid-column-end: 3;\n  grid-column-start: 2;\n  grid-row-end: 2;\n  grid-row-start: 1;\n  text-align: center;\n  white-space: normal;\n  word-break: break-all;\n  word-break: break-word;\n}\n.tox .tox-notification__body > * {\n  margin: 0;\n}\n.tox .tox-notification__body > * + * {\n  margin-top: 1rem;\n}\n.tox .tox-notification__icon {\n  align-self: center;\n  grid-column-end: 2;\n  grid-column-start: 1;\n  grid-row-end: 2;\n  grid-row-start: 1;\n  justify-self: end;\n}\n.tox .tox-notification__icon svg {\n  display: block;\n}\n.tox .tox-notification__dismiss {\n  align-self: start;\n  grid-column-end: 4;\n  grid-column-start: 3;\n  grid-row-end: 2;\n  grid-row-start: 1;\n  justify-self: end;\n}\n.tox .tox-notification .tox-progress-bar {\n  grid-column-end: 4;\n  grid-column-start: 1;\n  grid-row-end: 3;\n  grid-row-start: 2;\n  justify-self: center;\n}\n.tox .tox-pop {\n  display: inline-block;\n  position: relative;\n}\n.tox .tox-pop--resizing {\n  transition: width 0.1s ease;\n}\n.tox .tox-pop--resizing .tox-toolbar,\n.tox .tox-pop--resizing .tox-toolbar__group {\n  flex-wrap: nowrap;\n}\n.tox .tox-pop--transition {\n  transition: 0.15s ease;\n  transition-property: left, right, top, bottom;\n}\n.tox .tox-pop--transition::before,\n.tox .tox-pop--transition::after {\n  transition: all 0.15s, visibility 0s, opacity 0.075s ease 0.075s;\n}\n.tox .tox-pop__dialog {\n  background-color: #222f3e;\n  border: 1px solid #000000;\n  border-radius: 3px;\n  box-shadow: 0 0 2px 0 rgba(42, 55, 70, 0.2), 0 4px 8px 0 rgba(42, 55, 70, 0.15);\n  min-width: 0;\n  overflow: hidden;\n}\n.tox .tox-pop__dialog > *:not(.tox-toolbar) {\n  margin: 4px 4px 4px 8px;\n}\n.tox .tox-pop__dialog .tox-toolbar {\n  background-color: transparent;\n  margin-bottom: -1px;\n}\n.tox .tox-pop::before,\n.tox .tox-pop::after {\n  border-style: solid;\n  content: '';\n  display: block;\n  height: 0;\n  opacity: 1;\n  position: absolute;\n  width: 0;\n}\n.tox .tox-pop.tox-pop--inset::before,\n.tox .tox-pop.tox-pop--inset::after {\n  opacity: 0;\n  transition: all 0s 0.15s, visibility 0s, opacity 0.075s ease;\n}\n.tox .tox-pop.tox-pop--bottom::before,\n.tox .tox-pop.tox-pop--bottom::after {\n  left: 50%;\n  top: 100%;\n}\n.tox .tox-pop.tox-pop--bottom::after {\n  border-color: #222f3e transparent transparent transparent;\n  border-width: 8px;\n  margin-left: -8px;\n  margin-top: -1px;\n}\n.tox .tox-pop.tox-pop--bottom::before {\n  border-color: #000000 transparent transparent transparent;\n  border-width: 9px;\n  margin-left: -9px;\n}\n.tox .tox-pop.tox-pop--top::before,\n.tox .tox-pop.tox-pop--top::after {\n  left: 50%;\n  top: 0;\n  transform: translateY(-100%);\n}\n.tox .tox-pop.tox-pop--top::after {\n  border-color: transparent transparent #222f3e transparent;\n  border-width: 8px;\n  margin-left: -8px;\n  margin-top: 1px;\n}\n.tox .tox-pop.tox-pop--top::before {\n  border-color: transparent transparent #000000 transparent;\n  border-width: 9px;\n  margin-left: -9px;\n}\n.tox .tox-pop.tox-pop--left::before,\n.tox .tox-pop.tox-pop--left::after {\n  left: 0;\n  top: calc(50% - 1px);\n  transform: translateY(-50%);\n}\n.tox .tox-pop.tox-pop--left::after {\n  border-color: transparent #222f3e transparent transparent;\n  border-width: 8px;\n  margin-left: -15px;\n}\n.tox .tox-pop.tox-pop--left::before {\n  border-color: transparent #000000 transparent transparent;\n  border-width: 10px;\n  margin-left: -19px;\n}\n.tox .tox-pop.tox-pop--right::before,\n.tox .tox-pop.tox-pop--right::after {\n  left: 100%;\n  top: calc(50% + 1px);\n  transform: translateY(-50%);\n}\n.tox .tox-pop.tox-pop--right::after {\n  border-color: transparent transparent transparent #222f3e;\n  border-width: 8px;\n  margin-left: -1px;\n}\n.tox .tox-pop.tox-pop--right::before {\n  border-color: transparent transparent transparent #000000;\n  border-width: 10px;\n  margin-left: -1px;\n}\n.tox .tox-pop.tox-pop--align-left::before,\n.tox .tox-pop.tox-pop--align-left::after {\n  left: 20px;\n}\n.tox .tox-pop.tox-pop--align-right::before,\n.tox .tox-pop.tox-pop--align-right::after {\n  left: calc(100% - 20px);\n}\n.tox .tox-sidebar-wrap {\n  display: flex;\n  flex-direction: row;\n  flex-grow: 1;\n  min-height: 0;\n}\n.tox .tox-sidebar {\n  background-color: #222f3e;\n  display: flex;\n  flex-direction: row;\n  justify-content: flex-end;\n}\n.tox .tox-sidebar__slider {\n  display: flex;\n  overflow: hidden;\n}\n.tox .tox-sidebar__pane-container {\n  display: flex;\n}\n.tox .tox-sidebar__pane {\n  display: flex;\n}\n.tox .tox-sidebar--sliding-closed {\n  opacity: 0;\n}\n.tox .tox-sidebar--sliding-open {\n  opacity: 1;\n}\n.tox .tox-sidebar--sliding-growing,\n.tox .tox-sidebar--sliding-shrinking {\n  transition: width 0.5s ease, opacity 0.5s ease;\n}\n.tox .tox-selector {\n  background-color: #4099ff;\n  border-color: #4099ff;\n  border-style: solid;\n  border-width: 1px;\n  box-sizing: border-box;\n  display: inline-block;\n  height: 10px;\n  position: absolute;\n  width: 10px;\n}\n.tox.tox-platform-touch .tox-selector {\n  height: 12px;\n  width: 12px;\n}\n.tox .tox-slider {\n  align-items: center;\n  display: flex;\n  flex: 1;\n  height: 24px;\n  justify-content: center;\n  position: relative;\n}\n.tox .tox-slider__rail {\n  background-color: transparent;\n  border: 1px solid #000000;\n  border-radius: 3px;\n  height: 10px;\n  min-width: 120px;\n  width: 100%;\n}\n.tox .tox-slider__handle {\n  background-color: #207ab7;\n  border: 2px solid #185d8c;\n  border-radius: 3px;\n  box-shadow: none;\n  height: 24px;\n  left: 50%;\n  position: absolute;\n  top: 50%;\n  transform: translateX(-50%) translateY(-50%);\n  width: 14px;\n}\n.tox .tox-form__controls-h-stack > .tox-slider:not(:first-of-type) {\n  margin-inline-start: 8px;\n}\n.tox .tox-form__controls-h-stack > .tox-form__group + .tox-slider {\n  margin-inline-start: 32px;\n}\n.tox .tox-form__controls-h-stack > .tox-slider + .tox-form__group {\n  margin-inline-start: 32px;\n}\n.tox .tox-source-code {\n  overflow: auto;\n}\n.tox .tox-spinner {\n  display: flex;\n}\n.tox .tox-spinner > div {\n  animation: tam-bouncing-dots 1.5s ease-in-out 0s infinite both;\n  background-color: rgba(255, 255, 255, 0.5);\n  border-radius: 100%;\n  height: 8px;\n  width: 8px;\n}\n.tox .tox-spinner > div:nth-child(1) {\n  animation-delay: -0.32s;\n}\n.tox .tox-spinner > div:nth-child(2) {\n  animation-delay: -0.16s;\n}\n@keyframes tam-bouncing-dots {\n  0%,\n  80%,\n  100% {\n    transform: scale(0);\n  }\n  40% {\n    transform: scale(1);\n  }\n}\n.tox:not([dir=rtl]) .tox-spinner > div:not(:first-child) {\n  margin-left: 4px;\n}\n.tox[dir=rtl] .tox-spinner > div:not(:first-child) {\n  margin-right: 4px;\n}\n.tox .tox-statusbar {\n  align-items: center;\n  background-color: #222f3e;\n  border-top: 1px solid #000000;\n  color: #fff;\n  display: flex;\n  flex: 0 0 auto;\n  font-size: 12px;\n  font-weight: normal;\n  height: 18px;\n  overflow: hidden;\n  padding: 0 8px;\n  position: relative;\n  text-transform: uppercase;\n}\n.tox .tox-statusbar__text-container {\n  display: flex;\n  flex: 1 1 auto;\n  justify-content: flex-end;\n  overflow: hidden;\n}\n.tox .tox-statusbar__path {\n  display: flex;\n  flex: 1 1 auto;\n  margin-right: auto;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n.tox .tox-statusbar__path > * {\n  display: inline;\n  white-space: nowrap;\n}\n.tox .tox-statusbar__wordcount {\n  flex: 0 0 auto;\n  margin-left: 1ch;\n}\n.tox .tox-statusbar a,\n.tox .tox-statusbar__path-item,\n.tox .tox-statusbar__wordcount {\n  color: #fff;\n  text-decoration: none;\n}\n.tox .tox-statusbar a:hover:not(:disabled):not([aria-disabled=true]),\n.tox .tox-statusbar__path-item:hover:not(:disabled):not([aria-disabled=true]),\n.tox .tox-statusbar__wordcount:hover:not(:disabled):not([aria-disabled=true]),\n.tox .tox-statusbar a:focus:not(:disabled):not([aria-disabled=true]),\n.tox .tox-statusbar__path-item:focus:not(:disabled):not([aria-disabled=true]),\n.tox .tox-statusbar__wordcount:focus:not(:disabled):not([aria-disabled=true]) {\n  color: #fff;\n  cursor: pointer;\n}\n.tox .tox-statusbar__branding svg {\n  fill: rgba(255, 255, 255, 0.8);\n  height: 1.14em;\n  vertical-align: -0.28em;\n  width: 3.6em;\n}\n.tox .tox-statusbar__branding a:hover:not(:disabled):not([aria-disabled=true]) svg,\n.tox .tox-statusbar__branding a:focus:not(:disabled):not([aria-disabled=true]) svg {\n  fill: #fff;\n}\n.tox .tox-statusbar__resize-handle {\n  align-items: flex-end;\n  align-self: stretch;\n  cursor: nwse-resize;\n  display: flex;\n  flex: 0 0 auto;\n  justify-content: flex-end;\n  margin-left: auto;\n  margin-right: -8px;\n  padding-bottom: 3px;\n  padding-left: 1ch;\n  padding-right: 3px;\n}\n.tox .tox-statusbar__resize-handle svg {\n  display: block;\n  fill: rgba(255, 255, 255, 0.5);\n}\n.tox .tox-statusbar__resize-handle:focus svg {\n  background-color: #4a5562;\n  border-radius: 1px 1px -4px 1px;\n  box-shadow: 0 0 0 2px #4a5562;\n}\n.tox:not([dir=rtl]) .tox-statusbar__path > * {\n  margin-right: 4px;\n}\n.tox:not([dir=rtl]) .tox-statusbar__branding {\n  margin-left: 2ch;\n}\n.tox[dir=rtl] .tox-statusbar {\n  flex-direction: row-reverse;\n}\n.tox[dir=rtl] .tox-statusbar__path > * {\n  margin-left: 4px;\n}\n.tox .tox-throbber {\n  z-index: 1299;\n}\n.tox .tox-throbber__busy-spinner {\n  align-items: center;\n  background-color: rgba(34, 47, 62, 0.6);\n  bottom: 0;\n  display: flex;\n  justify-content: center;\n  left: 0;\n  position: absolute;\n  right: 0;\n  top: 0;\n}\n.tox .tox-tbtn {\n  align-items: center;\n  background: transparent;\n  border: 0;\n  border-radius: 3px;\n  box-shadow: none;\n  color: #fff;\n  display: flex;\n  flex: 0 0 auto;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: normal;\n  height: 34px;\n  justify-content: center;\n  margin: 3px 0 2px 0;\n  outline: none;\n  overflow: hidden;\n  padding: 0;\n  text-transform: none;\n  width: 34px;\n}\n.tox .tox-tbtn svg {\n  display: block;\n  fill: #fff;\n}\n.tox .tox-tbtn.tox-tbtn-more {\n  padding-left: 5px;\n  padding-right: 5px;\n  width: inherit;\n}\n.tox .tox-tbtn:focus {\n  background: #4a5562;\n  border: 0;\n  box-shadow: none;\n}\n.tox .tox-tbtn:hover {\n  background: #4a5562;\n  border: 0;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-tbtn:hover svg {\n  fill: #fff;\n}\n.tox .tox-tbtn:active {\n  background: #757d87;\n  border: 0;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-tbtn:active svg {\n  fill: #fff;\n}\n.tox .tox-tbtn--disabled .tox-tbtn--enabled svg {\n  fill: rgba(255, 255, 255, 0.5);\n}\n.tox .tox-tbtn--disabled,\n.tox .tox-tbtn--disabled:hover,\n.tox .tox-tbtn:disabled,\n.tox .tox-tbtn:disabled:hover {\n  background: transparent;\n  border: 0;\n  box-shadow: none;\n  color: rgba(255, 255, 255, 0.5);\n  cursor: not-allowed;\n}\n.tox .tox-tbtn--disabled svg,\n.tox .tox-tbtn--disabled:hover svg,\n.tox .tox-tbtn:disabled svg,\n.tox .tox-tbtn:disabled:hover svg {\n  /* stylelint-disable-line no-descending-specificity */\n  fill: rgba(255, 255, 255, 0.5);\n}\n.tox .tox-tbtn--enabled,\n.tox .tox-tbtn--enabled:hover {\n  background: #757d87;\n  border: 0;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-tbtn--enabled > *,\n.tox .tox-tbtn--enabled:hover > * {\n  transform: none;\n}\n.tox .tox-tbtn--enabled svg,\n.tox .tox-tbtn--enabled:hover svg {\n  /* stylelint-disable-line no-descending-specificity */\n  fill: #fff;\n}\n.tox .tox-tbtn--enabled.tox-tbtn--disabled svg,\n.tox .tox-tbtn--enabled:hover.tox-tbtn--disabled svg {\n  fill: rgba(255, 255, 255, 0.5);\n}\n.tox .tox-tbtn:focus:not(.tox-tbtn--disabled) {\n  color: #fff;\n}\n.tox .tox-tbtn:focus:not(.tox-tbtn--disabled) svg {\n  fill: #fff;\n}\n.tox .tox-tbtn:active > * {\n  transform: none;\n}\n.tox .tox-tbtn--md {\n  height: 51px;\n  width: 51px;\n}\n.tox .tox-tbtn--lg {\n  flex-direction: column;\n  height: 68px;\n  width: 68px;\n}\n.tox .tox-tbtn--return {\n  align-self: stretch;\n  height: unset;\n  width: 16px;\n}\n.tox .tox-tbtn--labeled {\n  padding: 0 4px;\n  width: unset;\n}\n.tox .tox-tbtn__vlabel {\n  display: block;\n  font-size: 10px;\n  font-weight: normal;\n  letter-spacing: -0.025em;\n  margin-bottom: 4px;\n  white-space: nowrap;\n}\n.tox .tox-number-input {\n  border-radius: 3px;\n  display: flex;\n  margin: 3px 0 2px 0;\n  padding: 0 4px;\n  width: auto;\n}\n.tox .tox-number-input .tox-input-wrapper {\n  background: transparent;\n  display: flex;\n  pointer-events: none;\n  text-align: center;\n}\n.tox .tox-number-input .tox-input-wrapper:focus {\n  background: #4a5562;\n}\n.tox .tox-number-input input {\n  border-radius: 3px;\n  color: #fff;\n  font-size: 14px;\n  margin: 2px 0;\n  pointer-events: all;\n  width: 60px;\n}\n.tox .tox-number-input input:hover {\n  background: #4a5562;\n  color: #fff;\n}\n.tox .tox-number-input input:focus {\n  background: #fff;\n  color: #2A3746;\n}\n.tox .tox-number-input input:disabled {\n  background: transparent;\n  border: 0;\n  box-shadow: none;\n  color: rgba(255, 255, 255, 0.5);\n  cursor: not-allowed;\n}\n.tox .tox-number-input button {\n  background: transparent;\n  color: #fff;\n  height: 34px;\n  text-align: center;\n  width: 24px;\n}\n.tox .tox-number-input button svg {\n  display: block;\n  fill: #fff;\n  margin: 0 auto;\n  transform: scale(0.67);\n}\n.tox .tox-number-input button:focus {\n  background: #4a5562;\n}\n.tox .tox-number-input button:hover {\n  background: #4a5562;\n  border: 0;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-number-input button:hover svg {\n  fill: #fff;\n}\n.tox .tox-number-input button:active {\n  background: #757d87;\n  border: 0;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-number-input button:active svg {\n  fill: #fff;\n}\n.tox .tox-number-input button:disabled {\n  background: transparent;\n  border: 0;\n  box-shadow: none;\n  color: rgba(255, 255, 255, 0.5);\n  cursor: not-allowed;\n}\n.tox .tox-number-input button:disabled svg {\n  fill: rgba(255, 255, 255, 0.5);\n}\n.tox .tox-number-input button.minus {\n  border-radius: 3px 0 0 3px;\n}\n.tox .tox-number-input button.plus {\n  border-radius: 0 3px 3px 0;\n}\n.tox .tox-number-input:focus:not(:active) > button,\n.tox .tox-number-input:focus:not(:active) > .tox-input-wrapper {\n  background: #4a5562;\n}\n.tox .tox-tbtn--select {\n  margin: 3px 0 2px 0;\n  padding: 0 4px;\n  width: auto;\n}\n.tox .tox-tbtn__select-label {\n  cursor: default;\n  font-weight: normal;\n  height: initial;\n  margin: 0 4px;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n.tox .tox-tbtn__select-chevron {\n  align-items: center;\n  display: flex;\n  justify-content: center;\n  width: 16px;\n}\n.tox .tox-tbtn__select-chevron svg {\n  fill: rgba(255, 255, 255, 0.5);\n}\n.tox .tox-tbtn--bespoke {\n  background: transparent;\n}\n.tox .tox-tbtn--bespoke + .tox-tbtn--bespoke {\n  margin-inline-start: 0;\n}\n.tox .tox-tbtn--bespoke .tox-tbtn__select-label {\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  width: 7em;\n}\n.tox .tox-tbtn--disabled .tox-tbtn__select-label,\n.tox .tox-tbtn--select:disabled .tox-tbtn__select-label {\n  cursor: not-allowed;\n}\n.tox .tox-split-button {\n  border: 0;\n  border-radius: 3px;\n  box-sizing: border-box;\n  display: flex;\n  margin: 3px 0 2px 0;\n  overflow: hidden;\n}\n.tox .tox-split-button:hover {\n  box-shadow: 0 0 0 1px #4a5562 inset;\n}\n.tox .tox-split-button:focus {\n  background: #4a5562;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-split-button > * {\n  border-radius: 0;\n}\n.tox .tox-split-button__chevron {\n  width: 16px;\n}\n.tox .tox-split-button__chevron svg {\n  fill: rgba(255, 255, 255, 0.5);\n}\n.tox .tox-split-button .tox-tbtn {\n  margin: 0;\n}\n.tox .tox-split-button.tox-tbtn--disabled:hover,\n.tox .tox-split-button.tox-tbtn--disabled:focus,\n.tox .tox-split-button.tox-tbtn--disabled .tox-tbtn:hover,\n.tox .tox-split-button.tox-tbtn--disabled .tox-tbtn:focus {\n  background: transparent;\n  box-shadow: none;\n  color: rgba(255, 255, 255, 0.5);\n}\n.tox.tox-platform-touch .tox-split-button .tox-tbtn--select {\n  padding: 0 0px;\n}\n.tox.tox-platform-touch .tox-split-button .tox-tbtn:not(.tox-tbtn--select):first-child {\n  width: 30px;\n}\n.tox.tox-platform-touch .tox-split-button__chevron {\n  width: 20px;\n}\n.tox .tox-split-button.tox-tbtn--disabled svg #tox-icon-text-color__color,\n.tox .tox-split-button.tox-tbtn--disabled svg #tox-icon-highlight-bg-color__color {\n  opacity: 0.6;\n}\n.tox .tox-toolbar-overlord {\n  background-color: #222f3e;\n}\n.tox .tox-toolbar,\n.tox .tox-toolbar__primary,\n.tox .tox-toolbar__overflow {\n  background-attachment: local;\n  background-color: #222f3e;\n  background-image: repeating-linear-gradient(#000000 0px 1px, transparent 1px 39px);\n  background-position: center top 39px;\n  background-repeat: no-repeat;\n  background-size: calc(100% - 4px * 2) calc(100% - 39px);\n  display: flex;\n  flex: 0 0 auto;\n  flex-shrink: 0;\n  flex-wrap: wrap;\n  padding: 0 0px;\n  transform: perspective(1px);\n}\n.tox .tox-toolbar-overlord > .tox-toolbar,\n.tox .tox-toolbar-overlord > .tox-toolbar__primary,\n.tox .tox-toolbar-overlord > .tox-toolbar__overflow {\n  background-position: center top 0px;\n  background-size: calc(100% - 4px * 2) calc(100% - 0px);\n}\n.tox .tox-toolbar__overflow.tox-toolbar__overflow--closed {\n  height: 0;\n  opacity: 0;\n  padding-bottom: 0;\n  padding-top: 0;\n  visibility: hidden;\n}\n.tox .tox-toolbar__overflow--growing {\n  transition: height 0.3s ease, opacity 0.2s linear 0.1s;\n}\n.tox .tox-toolbar__overflow--shrinking {\n  transition: opacity 0.3s ease, height 0.2s linear 0.1s, visibility 0s linear 0.3s;\n}\n.tox .tox-toolbar-overlord,\n.tox .tox-anchorbar {\n  grid-column: 1 / -1;\n}\n.tox .tox-menubar + .tox-toolbar,\n.tox .tox-menubar + .tox-toolbar-overlord {\n  border-top: 1px solid #000000;\n  margin-top: -1px;\n  padding-bottom: 0px;\n  padding-top: 0px;\n}\n.tox .tox-toolbar--scrolling {\n  flex-wrap: nowrap;\n  overflow-x: auto;\n}\n.tox .tox-pop .tox-toolbar {\n  border-width: 0;\n}\n.tox .tox-toolbar--no-divider {\n  background-image: none;\n}\n.tox .tox-toolbar-overlord .tox-toolbar:not(.tox-toolbar--scrolling):first-child,\n.tox .tox-toolbar-overlord .tox-toolbar__primary {\n  background-position: center top 39px;\n}\n.tox .tox-editor-header > .tox-toolbar--scrolling,\n.tox .tox-toolbar-overlord .tox-toolbar--scrolling:first-child {\n  background-image: none;\n}\n.tox.tox-tinymce-aux .tox-toolbar__overflow {\n  background-color: #222f3e;\n  background-position: center top 43px;\n  background-size: calc(100% - 8px * 2) calc(100% - 51px);\n  border: none;\n  border-radius: 3px;\n  box-shadow: 0 0 2px 0 rgba(42, 55, 70, 0.2), 0 4px 8px 0 rgba(42, 55, 70, 0.15);\n  overscroll-behavior: none;\n  padding: 4px 0;\n}\n.tox-pop .tox-pop__dialog {\n  /* stylelint-disable-next-line no-descending-specificity */\n}\n.tox-pop .tox-pop__dialog .tox-toolbar {\n  background-position: center top 43px;\n  background-size: calc(100% - 4px * 2) calc(100% - 51px);\n  padding: 4px 0;\n}\n.tox .tox-toolbar__group {\n  align-items: center;\n  display: flex;\n  flex-wrap: wrap;\n  margin: 0 0;\n  padding: 0 4px 0 4px;\n}\n.tox .tox-toolbar__group--pull-right {\n  margin-left: auto;\n}\n.tox .tox-toolbar--scrolling .tox-toolbar__group {\n  flex-shrink: 0;\n  flex-wrap: nowrap;\n}\n.tox:not([dir=rtl]) .tox-toolbar__group:not(:last-of-type) {\n  border-right: 1px solid #000000;\n}\n.tox[dir=rtl] .tox-toolbar__group:not(:last-of-type) {\n  border-left: 1px solid #000000;\n}\n.tox .tox-tooltip {\n  display: inline-block;\n  padding: 8px;\n  position: relative;\n}\n.tox .tox-tooltip__body {\n  background-color: #3d546f;\n  border-radius: 3px;\n  box-shadow: 0 2px 4px rgba(42, 55, 70, 0.3);\n  color: rgba(255, 255, 255, 0.75);\n  font-size: 14px;\n  font-style: normal;\n  font-weight: normal;\n  padding: 4px 8px;\n  text-transform: none;\n}\n.tox .tox-tooltip__arrow {\n  position: absolute;\n}\n.tox .tox-tooltip--down .tox-tooltip__arrow {\n  border-left: 8px solid transparent;\n  border-right: 8px solid transparent;\n  border-top: 8px solid #3d546f;\n  bottom: 0;\n  left: 50%;\n  position: absolute;\n  transform: translateX(-50%);\n}\n.tox .tox-tooltip--up .tox-tooltip__arrow {\n  border-bottom: 8px solid #3d546f;\n  border-left: 8px solid transparent;\n  border-right: 8px solid transparent;\n  left: 50%;\n  position: absolute;\n  top: 0;\n  transform: translateX(-50%);\n}\n.tox .tox-tooltip--right .tox-tooltip__arrow {\n  border-bottom: 8px solid transparent;\n  border-left: 8px solid #3d546f;\n  border-top: 8px solid transparent;\n  position: absolute;\n  right: 0;\n  top: 50%;\n  transform: translateY(-50%);\n}\n.tox .tox-tooltip--left .tox-tooltip__arrow {\n  border-bottom: 8px solid transparent;\n  border-right: 8px solid #3d546f;\n  border-top: 8px solid transparent;\n  left: 0;\n  position: absolute;\n  top: 50%;\n  transform: translateY(-50%);\n}\n.tox .tox-tree {\n  display: flex;\n  flex-direction: column;\n}\n.tox .tox-tree .tox-trbtn {\n  align-items: center;\n  background: transparent;\n  border: 0;\n  border-radius: 4px;\n  box-shadow: none;\n  color: #fff;\n  display: flex;\n  flex: 0 0 auto;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: normal;\n  height: 28px;\n  margin-bottom: 4px;\n  margin-top: 4px;\n  outline: none;\n  overflow: hidden;\n  padding: 0;\n  padding-left: 8px;\n  text-transform: none;\n}\n.tox .tox-tree .tox-trbtn .tox-tree__label {\n  cursor: default;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n.tox .tox-tree .tox-trbtn svg {\n  display: block;\n  fill: #fff;\n}\n.tox .tox-tree .tox-trbtn:focus {\n  background: #4a5562;\n  border: 0;\n  box-shadow: none;\n}\n.tox .tox-tree .tox-trbtn:hover {\n  background: #4a5562;\n  border: 0;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-tree .tox-trbtn:hover svg {\n  fill: #fff;\n}\n.tox .tox-tree .tox-trbtn:active {\n  background: #6ea9d0;\n  border: 0;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-tree .tox-trbtn:active svg {\n  fill: #fff;\n}\n.tox .tox-tree .tox-trbtn--disabled,\n.tox .tox-tree .tox-trbtn--disabled:hover,\n.tox .tox-tree .tox-trbtn:disabled,\n.tox .tox-tree .tox-trbtn:disabled:hover {\n  background: transparent;\n  border: 0;\n  box-shadow: none;\n  color: rgba(255, 255, 255, 0.5);\n  cursor: not-allowed;\n}\n.tox .tox-tree .tox-trbtn--disabled svg,\n.tox .tox-tree .tox-trbtn--disabled:hover svg,\n.tox .tox-tree .tox-trbtn:disabled svg,\n.tox .tox-tree .tox-trbtn:disabled:hover svg {\n  /* stylelint-disable-line no-descending-specificity */\n  fill: rgba(255, 255, 255, 0.5);\n}\n.tox .tox-tree .tox-trbtn--enabled,\n.tox .tox-tree .tox-trbtn--enabled:hover {\n  background: #6ea9d0;\n  border: 0;\n  box-shadow: none;\n  color: #fff;\n}\n.tox .tox-tree .tox-trbtn--enabled > *,\n.tox .tox-tree .tox-trbtn--enabled:hover > * {\n  transform: none;\n}\n.tox .tox-tree .tox-trbtn--enabled svg,\n.tox .tox-tree .tox-trbtn--enabled:hover svg {\n  /* stylelint-disable-line no-descending-specificity */\n  fill: #fff;\n}\n.tox .tox-tree .tox-trbtn:focus:not(.tox-trbtn--disabled) {\n  color: #fff;\n}\n.tox .tox-tree .tox-trbtn:focus:not(.tox-trbtn--disabled) svg {\n  fill: #fff;\n}\n.tox .tox-tree .tox-trbtn:active > * {\n  transform: none;\n}\n.tox .tox-tree .tox-trbtn--return {\n  align-self: stretch;\n  height: unset;\n  width: 16px;\n}\n.tox .tox-tree .tox-trbtn--labeled {\n  padding: 0 4px;\n  width: unset;\n}\n.tox .tox-tree .tox-trbtn__vlabel {\n  display: block;\n  font-size: 10px;\n  font-weight: normal;\n  letter-spacing: -0.025em;\n  margin-bottom: 4px;\n  white-space: nowrap;\n}\n.tox .tox-tree .tox-tree--directory {\n  display: flex;\n  flex-direction: column;\n  /* stylelint-disable no-descending-specificity */\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label {\n  font-weight: bold;\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-mbtn {\n  margin-left: auto;\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-mbtn svg {\n  fill: transparent;\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-mbtn.tox-mbtn--active svg,\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-mbtn:focus svg {\n  fill: #fff;\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:hover .tox-mbtn svg,\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:focus .tox-mbtn svg {\n  fill: #fff;\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:hover:has(.tox-mbtn:hover) {\n  background-color: transparent;\n  color: #fff;\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:hover:has(.tox-mbtn:hover) .tox-chevron svg {\n  fill: #fff;\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-chevron {\n  margin-right: 6px;\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:has(+ .tox-tree--directory__children--growing) .tox-chevron,\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:has(+ .tox-tree--directory__children--shrinking) .tox-chevron {\n  transition: transform 0.5s ease-in-out;\n}\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:has(+ .tox-tree--directory__children--growing) .tox-chevron,\n.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:has(+ .tox-tree--directory__children--open) .tox-chevron {\n  transform: rotate(90deg);\n}\n.tox .tox-tree .tox-tree--leaf__label {\n  font-weight: normal;\n}\n.tox .tox-tree .tox-tree--leaf__label .tox-mbtn {\n  margin-left: auto;\n}\n.tox .tox-tree .tox-tree--leaf__label .tox-mbtn svg {\n  fill: transparent;\n}\n.tox .tox-tree .tox-tree--leaf__label .tox-mbtn.tox-mbtn--active svg,\n.tox .tox-tree .tox-tree--leaf__label .tox-mbtn:focus svg {\n  fill: #fff;\n}\n.tox .tox-tree .tox-tree--leaf__label:hover .tox-mbtn svg {\n  fill: #fff;\n}\n.tox .tox-tree .tox-tree--leaf__label:hover:has(.tox-mbtn:hover) {\n  background-color: transparent;\n  color: #fff;\n}\n.tox .tox-tree .tox-tree--leaf__label:hover:has(.tox-mbtn:hover) .tox-chevron svg {\n  fill: #fff;\n}\n.tox .tox-tree .tox-tree--directory__children {\n  overflow: hidden;\n  padding-left: 16px;\n}\n.tox .tox-tree .tox-tree--directory__children.tox-tree--directory__children--growing,\n.tox .tox-tree .tox-tree--directory__children.tox-tree--directory__children--shrinking {\n  transition: height 0.5s ease-in-out;\n}\n.tox .tox-tree .tox-trbtn.tox-tree--leaf__label {\n  display: flex;\n  justify-content: space-between;\n}\n.tox .tox-view-wrap,\n.tox .tox-view-wrap__slot-container {\n  background-color: #222f3e;\n  display: flex;\n  flex: 1;\n  flex-direction: column;\n}\n.tox .tox-view {\n  display: flex;\n  flex: 1 1 auto;\n  flex-direction: column;\n  overflow: hidden;\n}\n.tox .tox-view__header {\n  align-items: center;\n  display: flex;\n  font-size: 16px;\n  justify-content: space-between;\n  padding: 8px 8px 0 8px;\n  position: relative;\n}\n.tox .tox-view--mobile.tox-view__header,\n.tox .tox-view--mobile.tox-view__toolbar {\n  padding: 8px;\n}\n.tox .tox-view--scrolling {\n  flex-wrap: nowrap;\n  overflow-x: auto;\n}\n.tox .tox-view__toolbar {\n  display: flex;\n  flex-direction: row;\n  gap: 8px;\n  justify-content: space-between;\n  padding: 8px 8px 0 8px;\n}\n.tox .tox-view__toolbar__group {\n  display: flex;\n  flex-direction: row;\n  gap: 12px;\n}\n.tox .tox-view__header-start,\n.tox .tox-view__header-end {\n  display: flex;\n}\n.tox .tox-view__pane {\n  height: 100%;\n  padding: 8px;\n  width: 100%;\n}\n.tox .tox-view__pane_panel {\n  border: 1px solid #000000;\n  border-radius: 3px;\n}\n.tox:not([dir=rtl]) .tox-view__header .tox-view__header-start > *,\n.tox:not([dir=rtl]) .tox-view__header .tox-view__header-end > * {\n  margin-left: 8px;\n}\n.tox[dir=rtl] .tox-view__header .tox-view__header-start > *,\n.tox[dir=rtl] .tox-view__header .tox-view__header-end > * {\n  margin-right: 8px;\n}\n.tox .tox-well {\n  border: 1px solid #000000;\n  border-radius: 3px;\n  padding: 8px;\n  width: 100%;\n}\n.tox .tox-well > *:first-child {\n  margin-top: 0;\n}\n.tox .tox-well > *:last-child {\n  margin-bottom: 0;\n}\n.tox .tox-well > *:only-child {\n  margin: 0;\n}\n.tox .tox-custom-editor {\n  border: 1px solid #000000;\n  border-radius: 3px;\n  display: flex;\n  flex: 1;\n  overflow: hidden;\n  position: relative;\n}\n/* stylelint-disable */\n.tox {\n  /* stylelint-enable */\n}\n.tox .tox-dialog-loading::before {\n  background-color: rgba(0, 0, 0, 0.5);\n  content: \"\";\n  height: 100%;\n  position: absolute;\n  width: 100%;\n  z-index: 1000;\n}\n.tox .tox-tab {\n  cursor: pointer;\n}\n.tox .tox-dialog__content-js {\n  display: flex;\n  flex: 1;\n}\n.tox .tox-dialog__body-content .tox-collection {\n  display: flex;\n  flex: 1;\n}\n.tox:not(.tox-tinymce-inline) .tox-editor-header {\n  background-color: none;\n  padding: 0;\n}\n.tox.tox-tinymce--toolbar-bottom .tox-editor-header,\n.tox.tox-tinymce-inline .tox-editor-header {\n  margin-bottom: -1px;\n}\n.tox.tox-tinymce-inline .tox-editor-container {\n  overflow: hidden;\n}\n.tox:not(.tox-tinymce-inline).tox-tinymce--toolbar-bottom .tox-editor-header {\n  border-top: none;\n  box-shadow: none;\n}\n.tox.tox.tox-tinymce--toolbar-sticky-on .tox-editor-header {\n  background-color: transparent;\n  box-shadow: 0 4px 4px -3px rgba(0, 0, 0, 0.25);\n  padding: 0;\n}\n.tox.tox.tox-tinymce--toolbar-sticky-on.tox-tinymce--toolbar-bottom .tox-editor-header {\n  box-shadow: 0 4px 4px -3px rgba(0, 0, 0, 0.25);\n}\n.tox .tox-collection--list .tox-collection__group .tox-insert-table-picker {\n  margin: -4px 0;\n}\n.tox .tox-menu.tox-collection.tox-collection--list {\n  padding: 0;\n}\n.tox .tox-pop {\n  box-shadow: none;\n}\n.tox .tox-tbtn,\n.tox .tox-number-input,\n.tox .tox-tbtn--select,\n.tox .tox-split-button {\n  margin: 2px 0 3px 0;\n}\n.tox .tox-toolbar,\n.tox .tox-toolbar__primary,\n.tox .tox-toolbar__overflow {\n  background: url(\"data:image/svg+xml;charset=utf8,%3Csvg height='39px' viewBox='0 0 40 39px' width='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='38px' width='100' height='1' fill='%23000000'/%3E%3C/svg%3E\") left 0 top 0px #222f3e !important;\n}\n.tox .tox-menubar + .tox-toolbar-overlord {\n  border-top: none;\n}\n.tox .tox-menubar + .tox-toolbar,\n.tox .tox-menubar + .tox-toolbar-overlord .tox-toolbar__primary {\n  border-top: 1px solid #000000;\n  margin-top: -1px;\n}\n.tox.tox-tinymce-aux .tox-toolbar__overflow {\n  border: 1px solid #000000;\n  padding: 0;\n}\n.tox .tox-pop .tox-pop__dialog .tox-toolbar {\n  padding: 0;\n}\n.tox:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-menubar {\n  border-top: 1px solid #000000;\n}\n.tox:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-toolbar-overlord:first-child .tox-toolbar__primary,\n.tox:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-toolbar:first-child {\n  border-top: 1px solid #000000;\n}\n.tox .tox-toolbar__group {\n  padding: 0 4px 0 4px;\n}\n.tox .tox-collection__item {\n  border-radius: 0;\n  cursor: pointer;\n}\n.tox .tox-statusbar a:hover:not(:disabled):not([aria-disabled=true]),\n.tox .tox-statusbar__path-item:hover:not(:disabled):not([aria-disabled=true]),\n.tox .tox-statusbar__wordcount:hover:not(:disabled):not([aria-disabled=true]),\n.tox .tox-statusbar a:focus:not(:disabled):not([aria-disabled=true]),\n.tox .tox-statusbar__path-item:focus:not(:disabled):not([aria-disabled=true]),\n.tox .tox-statusbar__wordcount:focus:not(:disabled):not([aria-disabled=true]) {\n  color: #fff;\n  text-decoration: underline;\n}\n.tox .tox-statusbar__branding svg {\n  vertical-align: -0.25em;\n}\n.tox:not([dir=rtl]) .tox-statusbar__branding {\n  margin-left: 1ch;\n}\n.tox .tox-statusbar__resize-handle {\n  padding-bottom: 0;\n  padding-right: 0;\n}\n.tox .tox-button::before {\n  display: none;\n}\n"
  },
  {
    "path": "minimalist-vue3/public/tinymce/skins/ui/tinymce-5-dark/skin.shadowdom.css",
    "content": "body.tox-dialog__disable-scroll {\n  overflow: hidden;\n}\n.tox-fullscreen {\n  border: 0;\n  height: 100%;\n  margin: 0;\n  overflow: hidden;\n  overscroll-behavior: none;\n  padding: 0;\n  touch-action: pinch-zoom;\n  width: 100%;\n}\n.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle {\n  display: none;\n}\n.tox.tox-tinymce.tox-fullscreen,\n.tox-shadowhost.tox-fullscreen {\n  left: 0;\n  position: fixed;\n  top: 0;\n  z-index: 1200;\n}\n.tox.tox-tinymce.tox-fullscreen {\n  background-color: transparent;\n}\n.tox-fullscreen .tox.tox-tinymce-aux,\n.tox-fullscreen ~ .tox.tox-tinymce-aux {\n  z-index: 1201;\n}\n"
  },
  {
    "path": "minimalist-vue3/src/App.vue",
    "content": "<template>\n    <router-view v-if=\"isRouterAlive\"></router-view>\n</template>\n<script setup>\nimport {nextTick, provide, ref} from \"vue\";\nimport {useSysStore} from \"~/store/module/sys-store.js\";\n//缓存\nconst sysStore = useSysStore()\n//router-view是否展示\nconst isRouterAlive = ref(true)\n//重新加载\nconst reload = () => {\n    isRouterAlive.value = false\n    nextTick(() => {\n        isRouterAlive.value = true\n        //保留首页缓存\n        sysStore.includePage = ['index']\n    })\n}\n//暴露 reload 给子组件使用\nprovide('reload', reload)\n</script>\n<style scoped>\n\n</style>\n"
  },
  {
    "path": "minimalist-vue3/src/api/config.js",
    "content": "import axios from '~/axios'\n\n//添加参数\nexport function addConfigApi(data) {\n    return axios.post('/basic/config/addConfig', data)\n}\n\n//删除参数\nexport function deleteConfigByConfigIdApi(configId) {\n    return axios({\n        method: 'DELETE',\n        url: '/basic/config/deleteConfigByConfigId',\n        params: {\n            configId: configId\n        }\n    })\n}\n\n//修改参数\nexport function updateConfigByConfigIdApi(data) {\n    return axios.put('/basic/config/updateConfigByConfigId', data)\n}\n\n//获取参数列表 - 分页\nexport function getPageConfigListApi(params) {\n    return axios({\n        method: 'GET',\n        url: '/basic/config/getPageConfigList',\n        params: params\n    })\n}\n\n//获取参数详细信息\nexport function getConfigByConfigIdApi(configId) {\n    return axios.get(`/basic/config/getConfigByConfigId/${configId}`)\n}\n\n"
  },
  {
    "path": "minimalist-vue3/src/api/dept.js",
    "content": "import axios from '~/axios'\n\n//添加部门\nexport function addDeptApi(data) {\n    return axios.post('/basic/dept/addDept', data)\n}\n\n//删除部门\nexport function deleteDeptByDeptIdApi(deptId) {\n    return axios({\n        method: 'DELETE',\n        url: '/basic/dept/deleteDeptByDeptId',\n        params: {\n            deptId: deptId\n        }\n    })\n}\n\n//修改部门\nexport function updateDeptByDeptIdApi(data) {\n    return axios.put('/basic/dept/updateDeptByDeptId', data)\n}\n\n//获取部门树 -> 部门管理中使用\nexport function getDeptListApi(params) {\n    return axios({\n        method: 'GET',\n        url: '/basic/dept/getDeptList',\n        params: params\n    })\n}\n\n//获取部门树(只查询状态为正常的部门)\nexport function getEnableDeptListApi() {\n    return axios.get(\"/basic/dept/getEnableDeptList\")\n}\n\n//获取部门详细信息\nexport function getDeptByDeptIdApi(deptId) {\n    return axios.get(`/basic/dept/getDeptByDeptId/${deptId}`)\n}"
  },
  {
    "path": "minimalist-vue3/src/api/dict.js",
    "content": "import axios from '~/axios'\n\n//获取字典列表 - 分页\nexport function getPageDictListApi(params) {\n    return axios({\n        method: 'GET',\n        url: '/basic/dict/getPageDictList',\n        params: params\n    })\n}\n\n//添加字典\nexport function addDictApi(data) {\n    return axios.post('/basic/dict/addDict', data)\n}\n\n//根据字典类型查询字典 - 单个类型\nexport function getDictByDictTypeApi(dictType) {\n    return axios.get(`/basic/dict/getDictByDictType/${dictType}`)\n}\n\n//根据字典类型查询字典 - 多个类型\nexport function getDictByDictTypeListApi(dictTypes) {\n    return axios.get(`/basic/dict/getDictList/${dictTypes}`)\n}\n\n//删除字典 -> 根据字典类型删除\nexport function deleteDictByDictTypeApi(dictType) {\n    return axios({\n        method: 'DELETE',\n        url: '/basic/dict/deleteDictByDictType',\n        params: {\n            dictType: dictType\n        }\n    })\n}\n\n//删除字典 -> 根据字典ID删除\nexport function deleteDictByDictIdApi(dictId) {\n    return axios({\n        method: 'DELETE',\n        url: '/basic/dict/deleteDictByDictId',\n        params: {\n            dictId: dictId\n        }\n    })\n}\n\n//修改字典\nexport function updateDictApi(data) {\n    return axios.put('/basic/dict/updateDictByDictId', data)\n}\n"
  },
  {
    "path": "minimalist-vue3/src/api/file.js",
    "content": "import axios from '~/axios'\n\n//上传文件\nexport function uploadFileApi(data, onUploadProgress) {\n    return axios.post('/basic/file/uploadFile', data, {\n        onUploadProgress:(e)=>{\n            //获取上传进度\n            if (onUploadProgress) {\n                onUploadProgress(e)\n            }\n        }\n    })\n}\n\n//删除文件 -> 根据文件URL删除\nexport function deleteFileApi(fileId) {\n    return axios({\n        method: 'DELETE',\n        url: '/basic/file/deleteFile',\n        params: {\n            fileId: fileId\n        }\n    })\n}\n\n//获取文件列表 - 分页\nexport function getPageFileListApi(params) {\n    return axios({\n        method: 'GET',\n        url: '/basic/file/getPageFileList',\n        params: params\n    })\n}"
  },
  {
    "path": "minimalist-vue3/src/api/notice.js",
    "content": "import axios from '~/axios'\n\n//添加公告\nexport function addNoticeApi(data) {\n    return axios.post('/basic/notice/addNotice', data)\n}\n\n//删除公告\nexport function deleteNoticeByNoticeIdApi(noticeId) {\n    return axios({\n        method: 'DELETE',\n        url: '/basic/notice/deleteNoticeByNoticeId',\n        params: {\n            noticeId: noticeId\n        }\n    })\n}\n\n//修改公告\nexport function updateNoticeByNoticeIdApi(data) {\n    return axios.put('/basic/notice/updateNoticeByNoticeId', data)\n}\n\n//获取公告列表 - 分页 -> 公告管理使用\nexport function getPageNoticeListApi(params) {\n    return axios({\n        method: 'GET',\n        url: '/basic/notice/getPageNoticeList',\n        params: params\n    })\n}\n\n//获取公告列表 - 分页 -> 首页使用\nexport function getPageHomeNoticeListApi(params) {\n    return axios({\n        method: 'GET',\n        url: '/basic/notice/getPageHomeNoticeList',\n        params: params\n    })\n}\n\n//获取公告详细信息\nexport function getNoticeByNoticeIdApi(noticeId) {\n    return axios.get(`/basic/notice/getNoticeByNoticeId/${noticeId}`)\n}"
  },
  {
    "path": "minimalist-vue3/src/api/perm.js",
    "content": "import axios from '~/axios'\n\n//权限类型\nexport const permType = {\n    M: {key: 'M', value: '菜单'},\n    B: {key: 'B', value: '按钮'}\n}\n\n//添加权限\nexport function addPermApi(data) {\n    return axios.post('/basic/permission/addPerm', data)\n}\n\n//删除权限\nexport function deletePermByPermIdApi(permId) {\n    return axios({\n        method: 'DELETE',\n        url: '/basic/permission/deletePermByPermId',\n        params: {\n            permId: permId\n        }\n    })\n}\n\n//修改权限\nexport function updatePermByPermIdApi(data) {\n    return axios.put('/basic/permission/updatePermByPermId', data)\n}\n\n//获取权限树\nexport function getPermListApi(params) {\n    return axios({\n        method: 'GET',\n        url: '/basic/permission/getPermList',\n        params: params\n    })\n}\n\n\n//获取系统租户权限树(只查询启用的权限)\nexport function getEnablePermListApi() {\n    return axios.get(\"/basic/permission/getEnablePermList\")\n}\n\n//获取租户权限树(只查询启用的权限)\nexport function getTenantEnablePermListApi() {\n    return axios.get(\"/basic/permission/getTenantEnablePermList\")\n}\n\n//获取权限详细信息\nexport function getPermByPermIdApi(permId) {\n    return axios.get(`/basic/permission/getPermByPermId/${permId}`)\n}\n"
  },
  {
    "path": "minimalist-vue3/src/api/post.js",
    "content": "import axios from '~/axios'\n\n//添加岗位\nexport function addPostApi(data) {\n    return axios.post('/basic/post/addPost', data)\n}\n\n//删除岗位\nexport function deletePostByPostIdApi(postId) {\n    return axios({\n        method: 'DELETE',\n        url: '/basic/post/deletePostByPostId',\n        params: {\n            postId: postId\n        }\n    })\n}\n\n//修改岗位\nexport function updatePostByPostIdApi(data) {\n    return axios.put('/basic/post/updatePostByPostId', data)\n}\n\n//获取岗位列表 - 分页\nexport function getPagePostListApi(params) {\n    return axios({\n        method: 'GET',\n        url: '/basic/post/getPagePostList',\n        params: params\n    })\n}\n\n//获取岗位详细信息\nexport function getPostByPostIdApi(postId) {\n    return axios.get(`/basic/post/getPostByPostId/${postId}`)\n}"
  },
  {
    "path": "minimalist-vue3/src/api/role.js",
    "content": "import axios from '~/axios'\n\n//添加角色\nexport function addRoleApi(data) {\n    return axios.post('/basic/role/addRole', data)\n}\n\n//删除角色\nexport function deleteRoleByRoleIdApi(roleId) {\n    return axios({\n        method: 'DELETE',\n        url: '/basic/role/deleteRoleByRoleId',\n        params: {\n            roleId: roleId\n        }\n    })\n}\n\n//修改角色\nexport function updateRoleByRoleIdApi(data) {\n    return axios.put('/basic/role/updateRoleByRoleId', data)\n}\n\n//获取角色列表 - 分页\nexport function getPageRoleListApi(params) {\n    return axios({\n        method: 'GET',\n        url: '/basic/role/getPageRoleList',\n        params: params\n    })\n}\n\n//获取角色详细信息\nexport function getRoleByRoleIdApi(roleId) {\n    return axios.get(`/basic/role/getRoleByRoleId/${roleId}`)\n}"
  },
  {
    "path": "minimalist-vue3/src/api/storage.js",
    "content": "import axios from '~/axios'\n\n//添加存储\nexport function addStorageApi(data) {\n    return axios.post('/basic/storage/addStorage', data)\n}\n\n//删除存储\nexport function deleteStorageByStorageIdApi(storageId) {\n    return axios({\n        method: 'DELETE',\n        url: '/basic/storage/deleteStorageByStorageId',\n        params: {\n            storageId: storageId\n        }\n    })\n}\n\n//修改存储\nexport function updateStorageByStorageIdApi(data) {\n    return axios.put('/basic/storage/updateStorageByStorageId', data)\n}\n\n//获取存储列表 - 分页\nexport function getPageStorageListApi(params) {\n    return axios({\n        method: 'GET',\n        url: '/basic/storage/getPageStorageList',\n        params: params\n    })\n}\n\n//获取存储详细信息\nexport function getStorageByStorageIdApi(storageId) {\n    return axios.get(`/basic/storage/getStorageByStorageId/${storageId}`)\n}\n"
  },
  {
    "path": "minimalist-vue3/src/api/tenant.js",
    "content": "import axios from '~/axios'\n\n//添加租户\nexport function addTenantApi(data) {\n    return axios.post('/basic/tenant/addTenant', data)\n}\n//删除租户\nexport function deleteTenantByTenantIdApi(tenantId) {\n    return axios({\n        method: 'DELETE',\n        url: '/basic/tenant/deleteTenantByTenantId',\n        params: {\n            tenantId: tenantId\n        }\n    })\n}\n//修改租户\nexport function updateTenantByTenantIdApi(data) {\n    return axios.put('/basic/tenant/updateTenantByTenantId', data)\n}\n//获取租户列表 - 分页\nexport function getPageTenantListApi(params) {\n    return axios({\n        method: 'GET',\n        url: '/basic/tenant/getPageTenantList',\n        params: params\n    })\n}\n//获取租户详细信息\nexport function getTenantByTenantIdApi(tenantId) {\n    return axios.get(`/basic/tenant/getTenantByTenantId/${tenantId}`)\n}"
  },
  {
    "path": "minimalist-vue3/src/api/tenantPackage.js",
    "content": "import axios from '~/axios'\n\n//添加套餐\nexport function addTenantPackageApi(data) {\n    return axios.post('/basic/tenantPackage/addTenantPackage', data)\n}\n//删除租户套餐\nexport function deleteTenantPackageByTenantPackageIdApi(tenantPackageId) {\n    return axios({\n        method: 'DELETE',\n        url: '/basic/tenantPackage/deleteTenantPackageByTenantPackageId',\n        params: {\n            tenantPackageId: tenantPackageId\n        }\n    })\n}\n//修改套餐\nexport function updateTenantPackageByTenantPackageIdApi(data) {\n    return axios.put('/basic/tenantPackage/updateTenantPackageByTenantPackageId', data)\n}\n//获取租户套餐列表 - 分页\nexport function getPageTenantPackageListApi(params) {\n    return axios({\n        method: 'GET',\n        url: '/basic/tenantPackage/getPageTenantPackageList',\n        params: params\n    })\n}\n//获取租户套餐详细信息\nexport function getTenantPackageByTenantPackageIdApi(tenantPackageId) {\n    return axios.get(`/basic/tenantPackage/getTenantPackageByTenantPackageId/${tenantPackageId}`)\n}\n\n"
  },
  {
    "path": "minimalist-vue3/src/api/user.js",
    "content": "import axios from '~/axios'\n\n//添加用户\nexport function addUserApi(data) {\n    return axios.post('/basic/user/addUser', data)\n}\n\n//删除用户\nexport function deleteUserByUserIdApi(userId) {\n    return axios({\n        method: 'DELETE',\n        url: '/basic/user/deleteUserByUserId',\n        params: {\n            userId: userId\n        }\n    })\n}\n\n//修改用户\nexport function updateUserByUserIdApi(data) {\n    return axios.put('/basic/user/updateUserByUserId', data)\n}\n\n//获取用户列表 - 分页\nexport function getPageUserListApi(params) {\n    return axios({\n        method: 'GET',\n        url: '/basic/user/getPageUserList',\n        params: params\n    })\n}\n\n//获取用户详细信息\nexport function getUserByUserIdApi(userId) {\n    return axios.get(`/basic/user/getUserByUserId/${userId}`)\n}\n\n//获取用户信息\nexport function getUserInfoApi() {\n    return axios.get('/basic/user/getUserInfo')\n}\n\n//获取图形验证码\nexport function getImageCaptchaApi() {\n    return axios.get('/basic/user/getImageCaptcha')\n}\n\n//登录\nexport function loginApi(loginForm) {\n    return axios.post('/basic/user/login', {\n        username: loginForm.username,\n        password: loginForm.password,\n        captcha: loginForm.captcha,\n        captchaId: loginForm.captchaId\n    })\n}\n\n//修改密码\nexport function resetPasswordApi(rePasswordForm) {\n    return axios.post('/basic/user/resetPassword', {\n        oldPassword: rePasswordForm.oldPassword,\n        newPassword: rePasswordForm.newPassword\n    })\n}\n\n//修改用户头像\nexport function updateUserAvatarApi(userAvatar) {\n    let params = new FormData();\n    params.append('userAvatar', userAvatar);\n    return axios.post('/basic/user/updateUserAvatar', params)\n}\n\n//用户设置 -> 修改用户信息\nexport function updateUserInfoApi(form) {\n    return axios.post('/basic/user/updateUserInfo', form)\n}\n\n//退出\nexport function logoutApi() {\n    return axios.post('/basic/user/logout')\n}"
  },
  {
    "path": "minimalist-vue3/src/assets/globalStyle.css",
    "content": "/* 全局样式 */\nbody {\n    margin: 0;\n    padding: 0;\n    width: 100%;\n    height: 100%;\n}\n/* 过渡动画 -> 渐显/渐隐 */\n/* 进入之前 */\n.fade-enter-from {\n    @apply opacity-0;\n}\n/* 进入之后 */\n.fade-enter-to {\n    @apply opacity-100;\n}\n/* 离开之前 */\n.fade-leave-from {\n    @apply opacity-100;\n}\n/* 离开之后 */\n.fade-leave-to {\n    @apply opacity-0;\n}\n.fade-enter-active,.fade-leave-active {\n    @apply transition-all;\n}\n.fade-enter-active {\n    transition-delay: 0.4s;\n}\n\n/* 配合文件选择组件 - 图片 */\n.image-grid {\n    display: grid;\n    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));\n    gap: 8px; /* 图片间距 */\n    padding: 0;\n    margin: 0;\n    width: 100%;\n}\n.image-item,\n.upload-item {\n    aspect-ratio: 1; /* 保持1:1比例 */\n    position: relative;\n}\n.image-content {\n    width: 100%;\n    height: 100%;\n    object-fit: cover;\n    border-radius: 4px;\n}\n.action-buttons {\n    position: absolute;\n    top: 0;\n    right: 0;\n    left: 0;\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    height: 100%;\n    opacity: 0;\n    background: rgba(0, 0, 0, .5);\n    transition: opacity .1s cubic-bezier(0,0,1,1);\n}\n.action-buttons:hover {\n    opacity: 1;\n}\n.icon-btn {\n    border: none;\n    padding: 3px 6px;\n    border-radius: 4px;\n    cursor: pointer;\n    display: flex;\n    color: #ffffff;\n    font-size: 1.5em;\n}\n.upload-btn {\n    width: 100%;\n    height: 100%;\n    border: 2px dashed var(--color-border-3);\n    background: var(--color-border-2);\n    border-radius: 4px;\n    cursor: pointer;\n    transition: border-color 0.3s;\n}\n.upload-btn:hover {\n    border-color: rgb(var(--arcoblue-6));\n}\n.upload-content {\n    height: 100%;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    justify-content: center;\n    font-size: 1.2em;\n    color: var(--color-text-3);\n}\n.upload-icon {\n    font-size: 1.5em;\n    margin-bottom: 4px;\n}"
  },
  {
    "path": "minimalist-vue3/src/axios.js",
    "content": "import axios from \"axios\";\nimport Msg from '~/utils/msg';\n\nconst service = axios.create({\n    //基础URL\n    baseURL: import.meta.env.VITE_API_BASE_PREFIX,\n    //请求超时30秒\n    timeout: 30000\n})\n//请求拦截器\nservice.interceptors.request.use(function (config) {\n    return config;\n}, function (err) {\n    //请求错误\n    return Promise.reject(err);\n});\n\n\n//响应拦截器\nservice.interceptors.response.use(function (response) {\n    //对响应数据做点什么\n    return response.data\n}, function (res) {\n    //响应错误\n    Msg.error(res.response.data || '请求失败')\n    return Promise.reject(res);\n});\n\nexport default service"
  },
  {
    "path": "minimalist-vue3/src/components/MHeader.vue",
    "content": "<template>\n    <div class=\"w-[100%] flex justify-between items-center\">\n        <div class=\"flex items-center ml-4 cursor-pointer\" @click=\"logoClick()\">\n            <img class=\"w-[35px] h-[35px]\" src=\"../assets/logo.png\" />\n            <span class=\"logo-text\">极简多租户管理系统</span>\n        </div>\n        <div class=\"flex items-center\">\n            <a-space>\n                <!-- 租户切换 -->\n                <template v-if=\"getCookie(CHANGE_TENANT_ALLOW)\">\n                    <a-select v-model=\"tenantId\" placeholder=\"租户切换\" class=\"min-w-[18em]\" allow-clear @change=\"tenantChange\">\n                        <template v-for=\"(d, index) in dicts[proxy.DICT.tenantList]\" :key=\"index\">\n                            <!-- 将系统租户隐藏，因为默认就是系统租户 -->\n                            <a-option  :value=\"d.dictKey\" :label=\"d.dictValue\" v-if=\"d.dictKey !== 0\" />\n                        </template>\n                    </a-select>\n                </template>\n                <!-- 全屏 -->\n                <a-button shape=\"circle\" size=\"small\" @click=\"toggle\">\n                    <template #icon>\n                        <icon-fullscreen-exit v-if=\"isFullscreen\" />\n                        <icon-fullscreen v-else />\n                    </template>\n                </a-button>\n                <!-- 切换主题 -->\n                <a-button shape=\"circle\" size=\"small\" @click=\"themeChange\">\n                    <template #icon>\n                        <icon-sun-fill v-if=\"theme\" />\n                        <icon-moon-fill v-else />\n                    </template>\n                </a-button>\n                <!-- gitee -->\n                <a-button shape=\"circle\" size=\"small\" @click=\"skipLink('https://gitee.com/marlife/minimalist-saas')\">\n                    <template #icon>\n                        <svg t=\"1690378638998\" class=\"icon\" viewBox=\"0 0 1024 1024\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" p-id=\"1580\" width=\"16\" height=\"16\"><path d=\"M512 1024C229.222 1024 0 794.778 0 512S229.222 0 512 0s512 229.222 512 512-229.222 512-512 512z m259.149-568.883h-290.74a25.293 25.293 0 0 0-25.292 25.293l-0.026 63.206c0 13.952 11.315 25.293 25.267 25.293h177.024c13.978 0 25.293 11.315 25.293 25.267v12.646a75.853 75.853 0 0 1-75.853 75.853h-240.23a25.293 25.293 0 0 1-25.267-25.293V417.203a75.853 75.853 0 0 1 75.827-75.853h353.946a25.293 25.293 0 0 0 25.267-25.292l0.077-63.207a25.293 25.293 0 0 0-25.268-25.293H417.152a189.62 189.62 0 0 0-189.62 189.645V771.15c0 13.977 11.316 25.293 25.294 25.293h372.94a170.65 170.65 0 0 0 170.65-170.65V480.384a25.293 25.293 0 0 0-25.293-25.267z\" fill=\"#C71D23\" p-id=\"1581\"></path></svg>                    </template>\n                </a-button>\n                <!-- github -->\n                <a-button shape=\"circle\" size=\"small\" @click=\"skipLink('https://github.com/lmq2582609/minimalist-saas')\">\n                    <template #icon>\n                        <icon-github />\n                    </template>\n                </a-button>\n                <!-- 用户头像 下拉菜单 -->\n                <div class=\"mr-3 cursor-pointer\">\n                    <a-dropdown @select=\"dropdownSelect\">\n                        <a-avatar :size=\"40\" class=\"bg-blue-300\">\n                            <template v-if=\"sysStore.user?.userAvatar\">\n                                <img :src=\"sysStore.user.userAvatar\" alt=\"头像\" />\n                            </template>\n                            <template v-else>\n                                <img src=\"../assets/default-avatar.jpg\" alt=\"头像\" />\n                            </template>\n                        </a-avatar>\n                        <template #content>\n                            <a-doption value=\"userSetting\">\n                                <template #icon>\n                                    <icon-settings />\n                                </template>\n                                <template #default>用户设置</template>\n                            </a-doption>\n                            <a-divider class=\"mt-0 mb-0\" />\n                            <a-doption value=\"logout\">\n                                <template #icon>\n                                    <icon-poweroff />\n                                </template>\n                                <template #default>退出系统</template>\n                            </a-doption>\n                        </template>\n                    </a-dropdown>\n                </div>\n            </a-space>\n        </div>\n    </div>\n</template>\n<script setup>\nimport {ref, getCurrentInstance, onMounted} from 'vue'\nimport { useRouter } from 'vue-router'\nimport { useFullscreen } from '@vueuse/core'\nimport { logoutApi } from \"~/api/user.js\";\nimport { useSysStore } from '~/store/module/sys-store.js'\nimport { useCookies } from '@vueuse/integrations/useCookies'\nimport {CHANGE_TENANT_ALLOW, CHANGE_TENANT_ID, CHANGE_TENANT_ID_BASE64, getCookie} from \"~/utils/cookie.js\";\nimport {Modal} from \"@arco-design/web-vue\";\n//cookie\nconst cookie = useCookies()\n//路由\nconst router = useRouter()\n//缓存\nconst sysStore = useSysStore()\n//全局实例\nconst {proxy} = getCurrentInstance()\n//加载字典\nconst dicts = proxy.LoadDicts([proxy.DICT.tenantList])\n\n//主题2种模式，true白天，false黑夜\nconst theme = ref(true)\n//切换主题\nconst themeChange = () => {\n    theme.value = !theme.value\n    if (theme.value) {\n        // 恢复亮色主题\n        document.body.removeAttribute('arco-theme');\n    } else {\n        // 设置为暗黑主题\n        document.body.setAttribute('arco-theme', 'dark')\n    }\n}\n//vue use isFullscreen: 是否全屏，toggle: 切换全屏\nconst { isFullscreen, toggle } = useFullscreen()\n//退出系统loading\nconst logoutLoading = ref(false)\n//下拉菜单\nconst dropdownSelect = (val) => {\n    //用户设置\n    if (val === 'userSetting') {\n        //跳转到用户设置页\n        router.push('/user/setting')\n    }\n    //退出系统\n    if (val === 'logout') {\n        Modal.confirm({\n            title: '提示',\n            content: '是否退出系统？',\n            okLoading: logoutLoading.value,\n            onBeforeOk: async () => {\n                await logoutApi()\n                //退出登录后续处理，清除登录信息\n                sysStore.userLogoutHandler()\n                //跳转到登录页\n                await router.push('/login')\n                return true\n            }\n        });\n    }\n}\n//点击logo -> 跳转到首页\nconst logoClick = () => {\n    router.push('/')\n}\n//超链接跳转\nconst skipLink = (url) => {\n    window.open(url, '_blank')\n}\n//租户切换\nconst tenantId = ref()\n//租户切换父组件函数\nconst emits = defineEmits(['tenantChange'])\nconst tenantChange = () => {\n    if (!tenantId.value) {\n        //清除cookie\n        cookie.remove(CHANGE_TENANT_ID)\n        cookie.remove(CHANGE_TENANT_ID_BASE64)\n    } else {\n        //设置cookie\n        cookie.set(CHANGE_TENANT_ID, tenantId.value)\n        //多存储一个base64数据，是因为Long类型cookie.get后会丢失精度，所以get时获取base64的数据后再解码拿到tenantId\n        cookie.set(CHANGE_TENANT_ID_BASE64, btoa(tenantId.value))\n    }\n    //调用父组件租户切换处理\n    emits('tenantChange')\n}\nonMounted(() => {\n    //初始化时，如果cookie中有tenantId，则回显\n    let tid = cookie.get(CHANGE_TENANT_ID_BASE64)\n    if (tid) {\n        tenantId.value = atob(tid)\n    }\n})\n</script>\n<style scoped>\n.logo-text {\n    font-size: 1.2rem;\n    color: var(--color-text-1);\n    margin-left: 0.5rem;\n}\n</style>\n"
  },
  {
    "path": "minimalist-vue3/src/components/MSider.vue",
    "content": "<template>\n    <a-menu accordion\n        :collapsed=\"siderCollapsed\"\n        @collapse=\"onCollapse\"\n        class=\"w-[100%] h-[100%]\"\n        :selected-keys=\"[$route.path]\" auto-open-selected\n        show-collapse-button :defaultOpenKeys=\"['0']\" :defaultSelectedKeys=\"['/']\" @menu-item-click=\"menuClick\">\n        <!-- 菜单处理 - 从store中获取菜单 -->\n        <template v-if=\"user && user.menus\">\n            <!-- visible=true 菜单可见 -->\n            <a-menu-item key=\"/\">\n                <template #icon>\n                    <icon-bytedance-color/>\n                </template>\n                控制台\n            </a-menu-item>\n            <menu-tree :menuTreeData=\"user.menus\" />\n        </template>\n    </a-menu>\n</template>\n<script setup>\nimport { useRouter } from 'vue-router'\nimport { storeToRefs } from 'pinia'\nimport { useSysStore } from '~/store/module/sys-store.js'\nimport MenuTree from \"~/components/menuTree/index.vue\";\n\n//路由\nconst router = useRouter()\n//菜单点击事件\nconst menuClick = (url) => {\n    if (url.includes('http')) {\n        //外部链接\n        window.open(url);\n    } else {\n        //跳转页面\n        router.push(url)\n    }\n}\n\n//缓存\nconst sysStore = useSysStore()\n//响应式数据：siderCollapsed: sider是否展开，siderWidth: sider宽度\nconst { siderCollapsed, siderWidth, user } = storeToRefs(sysStore)\n//左侧slider 展开/缩起\nconst onCollapse = (val, type) => {\n    siderCollapsed.value = !siderCollapsed.value\n    siderWidth.value = siderCollapsed.value ? sysStore.siderMinWidth : sysStore.siderMaxWidth\n}\n\n</script>\n<style scoped>\n</style>\n"
  },
  {
    "path": "minimalist-vue3/src/components/PageTabList.vue",
    "content": "<template>\n    <div class=\"fixed top-[50px] right-0 h-[45px] flex items-center px-3 shadow\"\n         :style=\"{left: sysStore.siderWidth + 'px'}\" style=\"background-color: var(--color-bg-1);z-index: 100\">\n        <a-tabs type=\"card-gutter\" v-model:active-key=\"activeTab\" :editable=\"true\" @delete=\"deleteTab\" auto-switch>\n            <a-tab-pane v-for=\"(item, index) of tabList\" :key=\"item.path\" :title=\"item.title\" :closable=\"item.path !== '/'\"></a-tab-pane>\n        </a-tabs>\n\n        <span class=\"rounded ml-auto flex items-center justify-center\">\n            <a-dropdown @select=\"tabDropdownSelect\" position=\"br\">\n                <a-button type=\"primary\">\n                    <template #icon>\n                        <icon-down/>\n                    </template>\n                </a-button>\n                <template #content>\n                    <a-doption value=\"clearOther\">关闭其他</a-doption>\n                    <a-doption value=\"clearAll\">关闭全部</a-doption>\n                </template>\n            </a-dropdown>\n        </span>\n    </div>\n\n    <!-- 高度占位 -->\n    <div class=\"h-[47px]\"></div>\n</template>\n<script setup>\nimport {onMounted, ref, watch} from \"vue\";\nimport {useSysStore} from \"~/store/module/sys-store.js\";\nimport {useRoute, useRouter, onBeforeRouteUpdate} from \"vue-router\";\nimport { useCookies } from '@vueuse/integrations/useCookies'\nimport {PAGE_TAB_LIST} from \"~/utils/cookie.js\";\n//cookie\nconst cookie = useCookies()\n//缓存\nconst sysStore = useSysStore()\n//路由\nconst route = useRoute()\nconst router = useRouter()\n//根据路径获取组件名称\nconst getCompName = (path) => {\n    const currentRoute = router.getRoutes().find(r => r.path === path)\n    //如果指定了名称，直接返回\n    if (currentRoute && currentRoute.name) {\n        return currentRoute.name\n    }\n    //未指定，返回文件名\n    return currentRoute.meta.name\n}\n\n\n//当前选中的tab\nconst activeTab = ref(route.fullPath)\n//首页控制台tab\nconst indexTab = {title: '控制台', path: '/'}\n//全部tab\nconst tabList = ref([ indexTab ]);\n//添加tab页\nconst addTab = (tab) => {\n    let index = tabList.value.findIndex(t => t.path === tab.path)\n    //如果tabList中没有这个tab，添加\n    if (index === -1) {\n        tabList.value.push(tab)\n        //同时添加到cookie，保证页面刷新后tab还存在\n        cookie.set(PAGE_TAB_LIST, tabList.value)\n\n        //获取组件名称并缓存该页面\n        let compName = getCompName(tab.path)\n        if (compName) {\n            sysStore.includePage.push(compName)\n        }\n    }\n    //激活这个tab页\n    activeTab.value = tab.path\n}\n//删除tab页\nconst deleteTab = (path) => {\n    let tabs = tabList.value\n    let active = activeTab.value\n    //如果关闭的是当前tab\n    if (active === path) {\n        tabs.forEach((tab, index) => {\n            //找到当前tab的下一个tab，如果没有下一个tab，就取上一个tab\n            if (tab.path === path) {\n                const nextTab = tabs[index+1] || tabs[index-1]\n                if (nextTab) {\n                    active = nextTab.path\n                }\n            }\n        })\n    }\n    //修改当前tab\n    activeTab.value = active\n    //在tabList中删除关闭的tab\n    tabList.value = tabList.value.filter(tab => tab.path !== path)\n    //同时添加到cookie，保证页面刷新后tab还存在\n    cookie.set(PAGE_TAB_LIST, tabList.value)\n\n    //获取组件名称并在缓存中删除该页面\n    let compName = getCompName(path)\n    if (compName) {\n        sysStore.includePage = sysStore.includePage.filter(item => item !== compName)\n    }\n}\n//to   -> 跳转到哪个页面去(路径)\n//from -> 从哪个页面跳转过来的(路径)\nonBeforeRouteUpdate((to, from) => {\n    //跳转页面，添加到tabList\n    addTab({\n        title: to.meta.title,\n        path: to.path\n    })\n})\n//初始化标签页\nconst initTabList = () => {\n    let tabs = cookie.get(PAGE_TAB_LIST)\n    if (tabs && tabs.length >= 0) {\n        tabList.value = tabs\n    }\n}\n//初始化\nonMounted(() => {\n    //初始化标签页\n    initTabList()\n})\n\n//关闭tab下拉菜单钮\nconst tabDropdownSelect = (key) => {\n    //关闭其他\n    if (key === 'clearOther') {\n        //除首页 和 当前tab，其他全部关闭\n        tabList.value = tabList.value.filter(tab => tab.path === '/' || tab.path === activeTab.value)\n        //移除其他页面缓存，只保留当前页和首页\n        let compNames = []\n        for (const tab of tabList.value) {\n            compNames.push(getCompName(tab.path))\n        }\n        sysStore.includePage = compNames\n    }\n    //关闭全部\n    if (key === 'clearAll') {\n        //回首页\n        activeTab.value = '/'\n        tabList.value = [ indexTab ]\n        //只保留首页缓存\n        sysStore.includePage = ['index']\n    }\n    //同时添加到cookie，保证页面刷新后tab还存在\n    cookie.set(PAGE_TAB_LIST, tabList.value)\n}\n//暴露子组件\ndefineExpose({tabDropdownSelect})\n\n//监听参数变化\nwatch(() =>  activeTab.value, (newVal, oldVal) => {\n    //tab变更，跳转至这个页面\n    router.push( activeTab.value)\n}, { deep: true, immediate: true })\n</script>\n<style scoped>\n:deep(.arco-tabs-content) {\n    display: none;\n    border: 0;\n}\n:deep(.arco-tabs-tab-active) {\n    border-bottom-color: transparent;\n}\n:deep(.arco-tabs-tab-active:hover) {\n    border-bottom-color: transparent;\n}\n</style>\n"
  },
  {
    "path": "minimalist-vue3/src/components/dictValue/index.vue",
    "content": "<template>\n    <template v-if=\"dict.dictClass && dict.dictClass !== 'default'\">\n        <a-tag :color=\"dict.dictClass\" bordered>{{dict.dictValue}}</a-tag>\n    </template>\n    <template v-else-if=\"dict.dictClass && dict.dictClass === 'default'\">\n        <a-tag bordered>{{dict.dictValue}}</a-tag>\n    </template>\n    <template v-else>\n        {{dict.dictValue}}\n    </template>\n</template>\n<script setup>\nimport { reactive, watch } from 'vue'\n\n//接收父组件参数\nconst props = defineProps({\n    //字典数据，通过父组件传入\n    dictData: {\n        type: Array,\n        default: null\n    },\n    //字典key\n    dictKey: {\n        type: [Number, String, Boolean],\n        default: null\n    }\n})\n\n//展示的数据\nconst dict = reactive({})\n//查找字典数据\nconst findDictByDictKey = () => {\n    let dictList = props.dictData\n    if (dictList && dictList.length > 0) {\n        for (let i = 0;i < dictList.length;i++) {\n            if (dictList[i].dictKey == props.dictKey) {\n                return dictList[i]\n            }\n        }\n    }\n    return ''\n}\n//监听参数变化\nwatch(() => [props.dictKey, props.dictData], (newVal, oldVal) => {\n    if (newVal) {\n        Object.assign(dict, findDictByDictKey())\n    }\n}, { deep: true, immediate: true })\n</script>"
  },
  {
    "path": "minimalist-vue3/src/components/iconSelect/FunctionalIcons.vue",
    "content": "<script>\n  import { h, resolveDynamicComponent } from \"vue\"\n\n  function Icon(props) {\n    const Component = resolveDynamicComponent(props.icon)\n    return h(Component)\n  }\n  export default Icon\n</script>"
  },
  {
    "path": "minimalist-vue3/src/components/iconSelect/index.vue",
    "content": "<template>\n    <a-select :modelValue=\"modelValue\" @change=\"selectChangeHandler\" placeholder=\"图标\" allow-search allow-clear>\n        <template #prefix v-if=\"modelValue\">\n            <functional-icons :icon=\"modelValue\" size=\"30\"></functional-icons>\n        </template>\n        <a-option v-for=\"item in icons\" :key=\"item\" :label=\"item\" :value=\"item\">\n            <div class=\"flex items-center justify-between\">\n                <functional-icons :icon=\"item\" size=\"30\"></functional-icons>\n                <span class=\"text-gray-500 ml-3\">{{ item }}</span>\n            </div>\n        </a-option>\n    </a-select>\n</template>\n<script setup>\nimport { ref } from 'vue'\nimport * as iconList from '@arco-design/web-vue/es/icon';\nimport FunctionalIcons from \"~/components/iconSelect/FunctionalIcons.vue\";\n\n//接收父组件参数\ndefineProps({\n    //v-model\n    modelValue: String\n})\n//更新选中的值\nconst emit = defineEmits(['update:modelValue'])\n//选择事件\nconst selectChangeHandler = (val) => {\n    emit('update:modelValue', val);\n}\n//所有图标\nconst icons = ref([])\n//去除图标数组中的default\nconst iconHanlder = () => {\n    let allIconName = Object.keys(iconList)\n    for (let i = 0;i < allIconName.length;i++) {\n        if (allIconName[i] === 'default') {\n            allIconName.splice(i, 1)\n        }\n    }\n    icons.value = allIconName\n}\n\niconHanlder()\n</script>"
  },
  {
    "path": "minimalist-vue3/src/components/menuTree/index.vue",
    "content": "<!-- 指定组件名称，递归使用 -->\n<script>export default { name: \"menuTree\"}</script>\n<template>\n    <div>\n        <template v-for=\"(item, index) in menuTreeData\" :key=\"index\">\n            <!-- 如果有子菜单，则是折叠的sub-menu -->\n            <a-sub-menu :key=\"item.permId\" v-if=\"item.children && item.children.length > 0\">\n                <!-- 图标 -->\n                <template #icon v-if=\"item.permIcon\">\n                    <functional-icons :icon=\"item.permIcon\" size=\"30\"></functional-icons>\n                </template>\n                <!-- 折叠菜单名称 -->\n                <template #title>\n                    {{item.permName}}\n                </template>\n                <!-- 组件递归 -->\n                <menu-tree :menuTreeData=\"item.children\"></menu-tree>\n            </a-sub-menu>\n            <!-- 菜单 :key 给定路由，需要跳转 -->\n            <template v-else>\n                <!-- visible=true 菜单可见 -->\n                <a-menu-item :key=\"item.permPath\" v-if=\"item.visible\">\n                    <!-- 图标 -->\n                    <template #icon v-if=\"item.permIcon\">\n                        <functional-icons :icon=\"item.permIcon\" size=\"30\"></functional-icons>\n                    </template>\n                    {{ item.permName }}\n                </a-menu-item>\n            </template>\n        </template>\n    </div>\n</template>\n\n<script setup>\nimport FunctionalIcons from \"~/components/iconSelect/FunctionalIcons.vue\";\n\ndefineProps({\n    menuTreeData: Array\n})\n</script>"
  },
  {
    "path": "minimalist-vue3/src/components/pagination/index.vue",
    "content": "<template>\n    <a-pagination show-total show-jumper show-page-size\n            :total=\"total\" :size=\"size\"\n            :page-size-options=\"pageSizeOptions\"\n            v-model:current=\"current\"\n            v-model:page-size=\"limit\"\n            @change=\"handleCurrentChange\"\n            @page-size-change=\"handleSizeChange\" />\n</template>\n\n<script setup>\nimport { computed } from 'vue'\n\n//接收父组件参数\nconst props = defineProps({\n    //总条数\n    total: {\n        type: Number,\n        default: 0\n    },\n    //当前页\n    pageNum: {\n        type: Number,\n        default: 1\n    },\n    //每页条数\n    pageSize: {\n        type: Number,\n        default: 10\n    },\n    //数据条数选择器的选项列表\n    pageSizeOptions: {\n        type: Array,\n        default: () => [10, 20, 30, 40, 50]\n    },\n    //分页选择器的大小\n    size: {\n        type: String,\n        default: 'medium'\n    }\n})\nconst emits = defineEmits(['update:pageNum', 'update:pageSize', 'pagination'])\n//计算属性 -> 当前页\nconst current = computed({\n    get() {\n        return props.pageNum;\n    },\n    set(val) {\n        emits(\"update:pageNum\", val);\n    },\n});\n//计算属性 -> 每页条数\nconst limit = computed({\n    get() {\n        return props.pageSize;\n    },\n    set(val) {\n        emits(\"update:pageSize\", val);\n    },\n});\n\n//数据条数改变时触发\nfunction handleSizeChange(val) {\n    //强制跳转到第 1 页\n    if (current.value * val > props.total) {\n        current.value = 1\n    }\n    //重新加载数据\n    emits('pagination', { pageNum: current.value, pageSize: val })\n}\n\n//页码改变时触发\nfunction handleCurrentChange(val) {\n    //重新加载数据\n    emits('pagination', { pageNum: val, pageSize: limit.value })\n}\n\n</script>\n\n<style scoped>\n\n</style>\n"
  },
  {
    "path": "minimalist-vue3/src/components/tinymceEditor/index.vue",
    "content": "<template>\n    <div class=\"w-[100%]\">\n        <editor id=\"tinymce\" v-model=\"editorHtml\" :init=\"tinymceInit\"></editor>\n\n        <!-- 文件选择组件演示 -->\n        <a-modal v-model:visible=\"modal.visible\" width=\"60%\" :esc-to-close=\"false\" :mask-closable=\"false\" draggable :footer=\"false\" style=\"z-index: 99999\">\n            <template #title>{{ modal.title }}</template>\n            <file-select :file-type=\"modal.fileType\" :limit=\"modal.limit\" @ok=\"onOk\" @cancel=\"onCancel\" v-if=\"modal.visible\" />\n        </a-modal>\n    </div>\n</template>\n\n<script setup>\nimport {reactive, ref} from 'vue'\nimport { uploadFileApi } from \"~/api/file.js\";\nimport tinymce from \"tinymce/tinymce\";\nimport \"tinymce/models/dom\";\nimport \"tinymce/themes/silver/theme\";\nimport Editor from \"@tinymce/tinymce-vue\";\n//tinymce插件 -> 更多插件参考：https://www.tiny.cloud/docs/plugins/\nimport 'tinymce/plugins/image' // 插入上传图片插件\nimport \"tinymce/plugins/importcss\"; //图片工具\nimport 'tinymce/plugins/media' // 插入视频插件\nimport 'tinymce/plugins/table' // 插入表格插件\nimport 'tinymce/plugins/lists' // 列表插件\nimport \"tinymce/plugins/charmap\"; // 特殊字符\nimport 'tinymce/plugins/wordcount' // 字数统计插件\nimport \"tinymce/plugins/codesample\"; // 插入代码\nimport \"tinymce/plugins/code\"; // 查看源码\nimport \"tinymce/plugins/fullscreen\"; //全屏\nimport 'tinymce/plugins/link' //超链接\nimport 'tinymce/plugins/preview' // 预览\nimport \"tinymce/plugins/searchreplace\"; //查询替换\n//字体图标\nimport 'tinymce/icons/default/icons.js'\nimport FileSelect from \"~/pages/basic/file/FileSelect.vue\";\nimport {fileType} from \"~/utils/sys.js\";\n\n\n/********************** 文件选择组件开始 **********************/\n//接收父组件参数\nconst props = defineProps({\n    //编辑器高度\n    editorHeight: {\n        type: Number,\n        default: 450\n    },\n    //文件来源 -> 若上传文件，需传入此参数，用于标识文件的作用\n    fileSource: {\n        type: Number,\n        default: 0\n    },\n    //存储信息ID -> 标识用哪个存储，不穿则使用默认的存储\n    storageId: {\n        type: String,\n        default: null\n    }\n})\n//文件选择组件\nconst modal = reactive({\n    //是否显示\n    visible: false,\n    //模态框标题\n    title: '文件选择',\n    //文件类型\n    fileType: '',\n    //可以选择几个文件\n    limit: 1,\n    //将文件放置到富文本编辑器中的回调\n    callback: null,\n});\n//模态框 -> 确认\nconst onOk = (selectedFiles) => {\n    let file = null\n    if (selectedFiles && selectedFiles.length > 0) {\n        file = selectedFiles[0]\n    } else {\n        //未选择文件，关闭\n        onCancel()\n        return\n    }\n    if (modal.fileType === fileType.image.key) {\n        //图片\n        modal.callback(file.fileUrl, {alt: file.fileName})\n    } else if (modal.fileType === fileType.video.key) {\n        //视频\n        modal.callback(file.fileUrl)\n    } else {\n        //文件\n        modal.callback(file.fileUrl, {text: file.fileName, title: file.fileName})\n    }\n    onCancel()\n}\n//模态框 -> 取消\nconst onCancel = () => {\n    //隐藏文件选择器\n    modal.visible = false\n    //清空回调函数\n    modal.callback = null\n}\n/********************** 文件选择组件结束 **********************/\n\n\n//富文本内容\nconst editorHtml = ref(\"请输入内容\");\n//富文本参数\nconst tinymceInit = {\n    //引入语言包 -> public目录下静态资源\n    language_url: \"/tinymce/skins/langs/zh-Hans.js\",\n    //语言类型 -> 中文\n    language: \"zh-Hans\",\n    //样式 -> public目录下静态资源\n    skin_url: \"/tinymce/skins/ui/oxide\",\n    //以css文件方式自定义可编辑区域的css样式，css文件需自己创建并引入 -> public目录下静态资源\n    content_css: \"/tinymce/skins/content/default/content.css\",\n    //富文本插件\n    plugins: \"link lists image media importcss code table wordcount preview searchreplace fullscreen codesample charmap\",\n    //工具栏\n    toolbar: \"undo redo outdent indent removeformat | bold italic underline strikethrough charmap | fontsizeselect | forecolor backcolor | alignment bullist numlist blockquote codesample table | link image media file | searchreplace fullscreen preview\",\n    //是否禁用\"Powered by TinyMCE\"\n    branding: false,\n    //顶部菜单栏是否显示\n    menubar: false,\n    //底部状态栏是否显示\n    statusbar: true,\n    //文件、图片、视频增加文件选择器处理\n    file_picker_types: 'file image media',\n    //文件选择器处理\n    file_picker_callback: function(callback, value, meta) {\n        //给定回调函数\n        modal.callback = callback\n        //图片\n        if (meta.filetype === 'image') {\n            modal.fileType = fileType.image.key\n        }\n        //视频\n        if (meta.filetype === 'media') {\n            modal.fileType = fileType.video.key\n        }\n        //文件\n        if (meta.filetype === 'file') {\n            modal.fileType = null\n        }\n        //显示文件选择器\n        modal.visible = true\n    },\n    //初始化完成触发\n    init_instance_callback: (editor) => {\n        //console.log(\"富文本编辑器初始化完成：\", editor)\n    },\n    //限制高度\n    height: props.editorHeight,\n    //插入word文档需要该属性\n    //paste_convert_word_fake_lists: false,\n    //自定义图片上传处理\n    images_upload_handler: (blobInfo, progress) => new Promise((resolve, reject) => {\n        //上传进度监控\n        const onUploadProgress = (e) => {\n            progress(e.progress * 100)\n        }\n        //单文件上传\n        const formData = new FormData();\n        formData.append(\"file\", blobInfo.blob())\n        formData.append(\"fileSource\", props.fileSource)\n        if (props.storageId) {\n            formData.append(\"storageId\", props.storageId)\n        }\n        uploadFileApi(formData, onUploadProgress).then(res => {\n            //将上传图片后的返回的url放入resolve\n            resolve(res.fileUrl);\n        }).catch(e => {\n            reject('上传失败')\n        })\n    }),\n    setup: (editor) => {\n        //自定义对齐按钮 - 下拉形式\n        editor.ui.registry.addMenuButton('alignment', {\n            icon: 'align-left',\n            tooltip: '对齐方式',\n            fetch: (callback) => {\n                const items = [\n                    {\n                        type: 'menuitem',\n                        text: '左对齐',\n                        icon: 'align-left',\n                        onAction: () => editor.execCommand('JustifyLeft')\n                    },\n                    {\n                        type: 'menuitem',\n                        text: '居中对齐',\n                        icon: 'align-center',\n                        onAction: () => editor.execCommand('JustifyCenter')\n                    },\n                    {\n                        type: 'menuitem',\n                        text: '右对齐',\n                        icon: 'align-right',\n                        onAction: () => editor.execCommand('JustifyRight')\n                    },\n                    {\n                        type: 'menuitem',\n                        text: '两端对齐',\n                        icon: 'align-justify',\n                        onAction: () => editor.execCommand('JustifyFull')\n                    }\n                ];\n                callback(items);\n            },\n            //图标变换\n            onSetup: (api) => {\n                const iconMap = {\n                    'JustifyLeft': 'align-left',\n                    'JustifyCenter': 'align-center',\n                    'JustifyRight': 'align-right',\n                    'JustifyFull': 'align-justify'\n                };\n                const updateState = () => {\n                    const activeAlignment = Object.keys(iconMap).find(cmd =>\n                            editor.queryCommandState(cmd)\n                    ) || 'JustifyLeft';\n                    api.setIcon(iconMap[activeAlignment])\n                }\n                editor.on('NodeChange', updateState)\n                return () => editor.off('NodeChange', updateState)\n            }\n        })\n    },\n}\n//获取富文本数据 -> 返回html内容和内容中所使用的的图片URL\nconst getEditorContent = () => {\n    return editorHtml.value\n}\n//设置富文本数据\nconst setEditorContent = (content) => {\n    if (content) {\n        editorHtml.value = content\n    } else {\n        editorHtml.value = '请输入内容'\n    }\n}\n//初始化富文本\ntinymce.init({});\n//暴露子组件\ndefineExpose({getEditorContent, setEditorContent})\n</script>\n<style scoped>\n:deep(.tox-promotion) {\n    display: none;\n}\n</style>\n"
  },
  {
    "path": "minimalist-vue3/src/components/uploadFile/index.vue",
    "content": "<template>\n    <div>\n        <a-upload :list-type=\"listType\" :action=\"uploadFileUrl\" :custom-request=\"customUploadFile\"\n                  v-model:file-list=\"fileList\" :accept=\"props.accept\" :limit=\"limit\"\n                  image-preview with-credentials :multiple=\"multiple\" :on-before-remove=\"customRemoveFile\" />\n    </div>\n</template>\n\n<script setup>\nimport {getCurrentInstance, ref, watch } from \"vue\";\nimport {deleteFileApi, uploadFileApi} from \"~/api/file.js\";\nimport {Modal} from \"@arco-design/web-vue\";\n\n//全局实例\nconst {proxy} = getCurrentInstance()\n//接收父组件参数\nconst props = defineProps({\n    //文件来源 -> 若上传文件，需传入此参数，用于标识文件的作用\n    fileSource: {\n        type: Number,\n        default: -1\n    },\n    //存储信息ID -> 标识用哪个存储，不传可使用租户默认存储\n    storageId: {\n        type: String,\n        default: null\n    },\n    //上传文件类型限制\n    accept: {\n        type: String,\n        default: ''\n    },\n    //是否支持多文件上传\n    multiple: {\n        type: Boolean,\n        default: false\n    },\n    //文件列表类型 -> 'text' | 'picture' | 'picture-card'\n    listType: {\n        type: String,\n        default: 'text'\n    },\n    //上传限制\n    limit: {\n        type: Number,\n        default: 5\n    },\n    //文件列表\n    fileList: {\n        type: Array,\n        default: () => []\n    }\n})\n//v-model\nconst fileList = ref([])\n//上传文件地址\nconst uploadFileUrl = import.meta.env.VITE_UPLOAD_FILE_URL\n//自定义上传\nconst customUploadFile = (option) => {\n    //上传进度监控\n    const onUploadProgress = (e) => {\n        option.onProgress(e.progress * 100)\n    }\n    //上传参数\n    const formData = new FormData();\n    formData.append(\"file\", option.fileItem.file);\n    formData.append(\"fileSource\", props.fileSource);\n    if (props.storageId) {\n        formData.append(\"storageId\", props.storageId)\n    }\n    uploadFileApi(formData, onUploadProgress).then(res => {\n        //调用onSuccess方法将响应数据附加到fileItem中的response字段上\n        option.onSuccess(res)\n        proxy.$msg.success(proxy.operationType.upload.success)\n    }).catch(e => {\n        //上传失败\n        option.onError(e)\n    })\n}\n//删除文件\nconst removeFileLoading = ref(false)\nconst customRemoveFile = (fileItem) => {\n    return new Promise((resolve, reject) => {\n        Modal.confirm({\n            title: '提示',\n            content: '是否确认删除文件？',\n            okLoading: removeFileLoading.value,\n            onCancel: () => {\n                removeFileLoading.value = false\n                return true\n            },\n            onBeforeOk: async () => {\n                let fileId = fileItem.response?.fileId\n                if (!fileId) {\n                    resolve(true)\n                } else {\n                    removeFileLoading.value = true\n                    await deleteFileApi(fileItem.response?.fileId)\n                    proxy.$msg.success(proxy.operationType.delete.success)\n                    resolve(true)\n                    removeFileLoading.value = false\n                }\n                return true\n            }\n        });\n    });\n}\n//获取上传文件的ID，逗号分割\nconst getUploadFileId = () => {\n    let uploadFileId = []\n    //上传的文件有response属性，回显的图片只有url\n    let fl = fileList.value\n    for (let i = 0; i < fl.length; i++) {\n        //从response中取文件ID\n        uploadFileId.push(fl[i].response.fileId)\n    }\n    return uploadFileId.join(\",\")\n}\n//暴露子组件\ndefineExpose({getUploadFileId})\n//监听参数变化\nwatch(() => props.fileList, (newVal, oldVal) => {\n    //先清空文件列表\n    fileList.value = []\n    //父组件传递文件列表回显，有值则回显\n    if (props.fileList.length > 0) {\n        fileList.value = props.fileList\n    }\n}, { deep: true, immediate: true })\n</script>\n\n<style scoped></style>\n"
  },
  {
    "path": "minimalist-vue3/src/directives/permission.js",
    "content": "import {hasPerm} from \"~/utils/sys.js\";\n\n/**\n * 自定义指令 v-perm，可传递两类参数，数组和对象\n * v-perm=\"['user:add', 'user:get']\"    -> 数组参数：传入权限编码\n * v-perm=\"{ perms: [], userIds: [] }\"  -> 对象参数：传入权限编码 和 用户ID，为何要传入用户ID请参考 hasPerm方法的注释\n */\nexport default {\n    install(app) {\n        app.directive('perm', {\n            mounted(el, binding) {\n                //权限列表参数\n                let perms = []\n                //用户ID参数\n                let userIds = []\n                let params = binding.value\n                if (params instanceof Array) {\n                    perms = params\n                } else if (params instanceof Object) {\n                    perms = params.perms\n                    userIds = params.userIds\n                } else {\n                    throw new Error('使用v-perm指令时参数错误')\n                }\n                //校验权限\n                let checkPerm = hasPerm(perms, userIds)\n                if (!checkPerm && el?.parentNode) {\n                     el.parentNode.removeChild(el)\n                }\n            }\n        })\n    }\n}\n"
  },
  {
    "path": "minimalist-vue3/src/directives/role.js",
    "content": "import { hasRole } from \"~/utils/sys.js\";\n\n/**\n * 自定义指令 v-role，可传递两类参数，数组和对象\n * v-role=\"['admin', 'pm']\"    -> 数组参数：传入角色编码\n * v-role=\"{ roles: [], userIds: [] }\"  -> 对象参数：传入角色编码 和 用户ID，为何要传入用户ID请参考 hasPerm方法的注释\n */\nexport default {\n    install(app) {\n        app.directive('role', {\n            mounted(el, binding) {\n                //角色列表参数\n                let roles = []\n                //用户ID参数\n                let userIds = []\n                let params = binding.value\n                if (params instanceof Array) {\n                    roles = params\n                } else if (params instanceof Object) {\n                    roles = params.roles\n                    userIds = params.userIds\n                } else {\n                    throw new Error('使用v-role指令时参数错误')\n                }\n                //校验角色\n                let checkRole = hasRole(roles, userIds)\n                if (!checkRole && el?.parentNode) {\n                     el.parentNode.removeChild(el)\n                }\n            }\n        })\n    }\n}\n"
  },
  {
    "path": "minimalist-vue3/src/main.js",
    "content": "import { createApp } from 'vue'\nimport '~/assets/globalStyle.css'\nimport ArcoVue from '@arco-design/web-vue';\nimport '@arco-design/web-vue/dist/arco.css';\nimport ArcoVueIcon from '@arco-design/web-vue/es/icon';\nimport App from './App.vue'\n//windicss\nimport 'virtual:windi.css'\n\n\n\nconst app = createApp(App)\n//全局挂载公共工具类\nimport Msg from '~/utils/msg'\nimport { randomCode, operationType, yesNo, hasPerm, hasRole } from '~/utils/sys'\nimport { LoadDicts, DICT } from '~/utils/dict'\napp.config.globalProperties = {\n    //消息提示\n    $msg: Msg,\n    //生成随即编码\n    randomCode: randomCode,\n    //操作类型\n    operationType: operationType,\n    //'是/否' 枚举\n    yesNo: yesNo,\n    //字典枚举\n    DICT: DICT,\n    //加载字典\n    LoadDicts: LoadDicts,\n    //权限校验\n    hasPerm: hasPerm,\n    //角色校验\n    hasRole: hasRole\n}\n\n//状态管理\nimport pinia from '~/store'\napp.use(pinia)\n\n//路由\nimport Router from './router'\napp.use(Router)\n\n//arco\napp.use(ArcoVue)\n//arco图标\napp.use(ArcoVueIcon)\n\n//字典转换组件\nimport DictValue from '~/components/dictValue/index.vue'\napp.component('dict-convert', DictValue)\n\n//分页组件\nimport Pagination from '~/components/pagination/index.vue'\napp.component('pagination', Pagination)\n\n//自定义权限指令\nimport permission from \"~/directives/permission.js\";\napp.use(permission)\n//自定义角色限指令\nimport role from \"~/directives/role.js\";\napp.use(role)\n\napp.mount('#app')\n"
  },
  {
    "path": "minimalist-vue3/src/pages/404.vue",
    "content": "<template>\n    <div class=\"body\">\n        <div class=\"mars\"></div>\n        <img src=\"../assets/404/404.svg\" class=\"logo-404\" alt=\"\">\n        <img src=\"../assets/404/meteor.svg\" class=\"meteor\" alt=\"\">\n        <p class=\"title\">Oh no!!</p>\n        <p class=\"subtitle\">\n            访问的页面不存在\n        </p>\n        <div align=\"center\">\n            <a class=\"btn-back\" href=\"/\">返回首页</a>\n        </div>\n        <img src=\"../assets/404/astronaut.svg\" class=\"astronaut\" alt=\"\">\n        <img src=\"../assets/404/spaceship.svg\" class=\"spaceship\" alt=\"\">\n    </div>\n</template>\n<script setup>\n\n</script>\n<style scoped>\n@keyframes floating {\n    from { transform: translateY(0px); }\n    65% { transform: translateY(15px); }\n    to { transform: translateY(0px); }\n}\n.body {\n    background-image: url(\"../assets/404/star.svg\"), linear-gradient(to bottom, #05007A, #4D007D);\n    height: 100vh;\n    margin: 0;\n    background-attachment: fixed;\n    overflow: hidden;\n}\n.mars {\n    left: 0;\n    right: 0;\n    bottom: 0;\n    position: absolute;\n    height: 27vmin;\n    background: url(\"../assets/404/mars.svg\") no-repeat bottom center;\n    background-size: cover;\n}\n.logo-404 {\n    position: absolute;\n    margin-left: auto;\n    margin-right: auto;\n    left: 0;\n    right: 0;\n    top: 16vmin;\n    width: 30vmin;\n}\n@media (max-width: 480px) and (min-width: 320px) {\n    .logo-404 {\n        top: 45vmin;\n    }\n}\n.meteor {\n    position: absolute;\n    right: 2vmin;\n    top: 16vmin;\n}\n.title {\n    color: white;\n    font-family: \"Nunito\", sans-serif;\n    font-weight: 600;\n    text-align: center;\n    font-size: 5vmin;\n    margin-top: 31vmin;\n}\n@media (max-width: 480px) and (min-width: 320px) {\n    .title {\n        margin-top: 65vmin;\n    }\n}\n.subtitle {\n    color: white;\n    font-family: \"Nunito\", sans-serif;\n    font-weight: 400;\n    text-align: center;\n    font-size: 3.5vmin;\n    margin-top: 10vmin;\n    margin-bottom: 9vmin;\n}\n.btn-back {\n    border: 1px solid white;\n    color: white;\n    height: 5vmin;\n    padding: 12px;\n    font-family: \"Nunito\", sans-serif;\n    text-decoration: none;\n    border-radius: 5px;\n}\n.btn-back:hover {\n    background: white;\n    color: #4D007D;\n}\n@media (max-width: 480px) and (min-width: 320px) {\n    .btn-back {\n        font-size: 3.5vmin;\n    }\n}\n.astronaut {\n    position: absolute;\n    top: 18vmin;\n    left: 10vmin;\n    height: 30vmin;\n    animation: floating 3s infinite ease-in-out;\n}\n@media (max-width: 480px) and (min-width: 320px) {\n    .astronaut {\n        top: 2vmin;\n    }\n}\n.spaceship {\n    position: absolute;\n    bottom: 15vmin;\n    right: 24vmin;\n}\n@media (max-width: 480px) and (min-width: 320px) {\n    .spaceship {\n        width: 45vmin;\n        bottom: 18vmin;\n    }\n}\n</style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/config/ConfigDetail.vue",
    "content": "<template>\n    <a-spin class=\"w-[100%]\" :size=\"35\" :loading=\"spinLoading\" tip=\"正在处理, 请稍候...\">\n        <a-descriptions :column=\"2\" bordered>\n            <a-descriptions-item label=\"参数名称\">{{ form.configName }}</a-descriptions-item>\n            <a-descriptions-item label=\"参数key\">{{ form.configKey }}</a-descriptions-item>\n            <a-descriptions-item label=\"参数value\">{{ form.configValue }}</a-descriptions-item>\n            <a-descriptions-item label=\"说明\">{{ form.description }}</a-descriptions-item>\n            <a-descriptions-item label=\"参数状态\">\n                <dict-convert :dict-data=\"dicts[proxy.DICT.commonNumberStatus]\" :dict-key=\"form.status\" />\n            </a-descriptions-item>\n        </a-descriptions>\n    </a-spin>\n</template>\n<script setup>\nimport { ref, reactive, getCurrentInstance, watch } from 'vue'\nimport {getConfigByConfigIdApi} from \"~/api/config.js\";\n//全局实例\nconst { proxy } = getCurrentInstance()\n//加载字典\nconst dicts = proxy.LoadDicts([proxy.DICT.commonNumberStatus])\n//加载中...\nconst spinLoading = ref(false)\n//接收父组件参数\nconst props = defineProps({\n    params: {\n        type: Object,\n        default: () => {}\n    }\n})\n//表单\nconst form = reactive({\n    //参数ID\n    configId: null,\n    //参数名称\n    configName: null,\n    //参数key\n    configKey: null,\n    //参数value\n    configValue: null,\n    //参数状态\n    status: null,\n    //说明\n    description: null\n})\n//加载参数详细信息\nconst loadConfigInfo = (configId) => {\n    spinLoading.value = true\n    getConfigByConfigIdApi(configId).then(res => {\n        //数据赋值\n        if (res) {\n            for (let key in res) {\n                if (form.hasOwnProperty(key)) {\n                    form[key] = res[key]\n                }\n            }\n        }\n    }).finally(() => {\n        spinLoading.value = false\n    })\n}\n//监听参数变化\nwatch(() => props.params, (newVal, oldVal) => {\n    //参数ID\n    if (props.params.configId) {\n        //查询数据\n        loadConfigInfo(props.params.configId)\n    }\n}, { deep: true, immediate: true })\n</script>\n<style scoped></style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/config/ConfigEdit.vue",
    "content": "<template>\n    <a-spin class=\"w-[100%]\" :size=\"35\" :loading=\"spinLoading\" tip=\"正在处理, 请稍候...\">\n        <a-form :model=\"form\" ref=\"formRef\" layout=\"vertical\" :rules=\"rules\" auto-label-width>\n            <a-form-item class=\"w-[100%]\" field=\"configName\" label=\"参数名称\" required>\n                <a-input v-model=\"form.configName\" placeholder=\"参数名称\" />\n            </a-form-item>\n            <a-form-item class=\"w-[100%]\" field=\"configKey\" label=\"参数键名\" required tooltip=\"参数的key\">\n                <a-input v-model=\"form.configKey\" placeholder=\"参数键名\" />\n            </a-form-item>\n            <a-form-item class=\"w-[100%]\" field=\"configValue\" label=\"参数键值\" required tooltip=\"参数的value\">\n                <a-textarea v-model=\"form.configValue\" placeholder=\"参数键值\" />\n            </a-form-item>\n            <a-form-item class=\"w-[100%]\" field=\"description\" label=\"说明\">\n                <a-textarea v-model=\"form.description\" placeholder=\"说明\" />\n            </a-form-item>\n            <a-form-item class=\"w-[100%]\" field=\"status\" label=\"参数状态\" required v-if=\"props.params.operationType === proxy.operationType.update.type\">\n                <a-select v-model=\"form.status\" placeholder=\"参数状态\" allow-clear>\n                    <a-option v-for=\"(d, index) in dicts[proxy.DICT.commonNumberStatus]\" :key=\"index\" :value=\"d.dictKey\" :label=\"d.dictValue\" />\n                </a-select>\n            </a-form-item>\n        </a-form>\n\n        <!-- 分割线 -->\n        <a-divider class=\"mt-0\" />\n\n        <div class=\"flex justify-end\">\n            <a-space>\n                <a-button @click=\"cancelBtnClick()\">取消</a-button>\n                <a-button type=\"primary\" @click=\"okBtnClick()\">确定</a-button>\n            </a-space>\n        </div>\n    </a-spin>\n</template>\n\n<script setup>\nimport { ref, reactive, getCurrentInstance, watch } from 'vue'\nimport {addConfigApi, getConfigByConfigIdApi, updateConfigByConfigIdApi} from \"~/api/config.js\";\n//全局实例\nconst {proxy} = getCurrentInstance()\n//加载字典\nconst dicts = proxy.LoadDicts([proxy.DICT.commonNumberStatus])\n//接收父组件参数\nconst props = defineProps({\n    params: {\n        type: Object,\n        default: () => {}\n    }\n})\n//父组件函数\nconst emits = defineEmits(['ok', 'cancel'])\n//加载中...\nconst spinLoading = ref(false)\n//表单ref\nconst formRef = ref(null)\n//表单\nconst form = reactive({\n    //参数ID\n    configId: null,\n    //参数名称\n    configName: null,\n    //参数key\n    configKey: null,\n    //参数value\n    configValue: null,\n    //参数状态\n    status: null,\n    //说明\n    description: null\n})\n//表单校验规则\nconst rules = {\n    configName: [{required: true, message: '参数名称不能为空', trigger: 'submit'}],\n    configKey: [{required: true, message: '参数key不能为空', trigger: 'submit'}],\n    configValue: [{required: true, message: '参数value不能为空', trigger: 'submit'}],\n    status: [{required: true, message: '参数状态不能为空', trigger: 'submit'}]\n}\n//确定 -> 点击\nconst okBtnClick = () => {\n    //表单验证\n    formRef.value.validate((valid) => {\n        if (valid) {return false}\n        //添加\n        if (props.params.operationType === proxy.operationType.add.type) {\n            spinLoading.value = true\n            addConfigApi(form).then(() => {\n                proxy.$msg.success(proxy.operationType.add.success)\n                emits('ok')\n            }).finally(() => {\n                spinLoading.value = false\n            })\n        }\n        //修改\n        if (props.params.operationType === proxy.operationType.update.type) {\n            spinLoading.value = true\n            updateConfigByConfigIdApi(form).then(() => {\n                proxy.$msg.success(proxy.operationType.update.success)\n                emits('ok')\n            }).finally(() => {\n                spinLoading.value = false\n            })\n        }\n    })\n}\n//取消 -> 点击\nconst cancelBtnClick = () => {\n    emits('cancel')\n}\n//加载参数详细信息\nconst loadConfigInfo = (configId) => {\n    spinLoading.value = true\n    getConfigByConfigIdApi(configId).then(res => {\n        //数据赋值\n        if (res) {\n            for (let key in res) {\n                if (form.hasOwnProperty(key)) {\n                    form[key] = res[key]\n                }\n            }\n        }\n    }).finally(() => {\n        spinLoading.value = false\n    })\n}\n//监听参数变化\nwatch(() => props.params, (newVal, oldVal) => {\n    //参数ID\n    if (props.params.configId) {\n        //加载参数信息\n        loadConfigInfo(props.params.configId)\n    }\n}, { deep: true, immediate: true })\n</script>\n<style scoped></style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/config/ConfigMgt.vue",
    "content": "<template>\n    <a-card :body-style=\"{height: 'calc(100vh - 125px)'}\">\n\n        <!-- 数据列表 -->\n        <a-row class=\"w-full h-full flex flex-col overflow-x-auto overflow-y-hidden\">\n            <!-- 查询条件 -->\n            <a-row class=\"w-full\" v-if=\"showSearchRow\">\n                <a-form :model=\"searchForm\" layout=\"inline\" label-align=\"left\" size=\"small\">\n                    <a-form-item field=\"configName\" label=\"参数名称\">\n                        <a-input v-model=\"searchForm.configName\" placeholder=\"参数名称\" />\n                    </a-form-item>\n                    <a-form-item field=\"configKey\" label=\"参数键名\">\n                        <a-input v-model=\"searchForm.configKey\" placeholder=\"参数键名\" />\n                    </a-form-item>\n                    <a-form-item field=\"status\" label=\"参数状态\">\n                        <a-select v-model=\"searchForm.status\" placeholder=\"参数状态\" allow-clear>\n                            <a-option v-for=\"(d, index) in dicts[proxy.DICT.commonNumberStatus]\" :key=\"index\" :value=\"d.dictKey\" :label=\"d.dictValue\" />\n                        </a-select>\n                    </a-form-item>\n                </a-form>\n                <a-row justify=\"center\" class=\"w-full mt-2\">\n                    <a-space>\n                        <a-button type=\"primary\" @click=\"getPageList(false)\">\n                            <template #icon><icon-search /></template>\n                            <template #default>查询</template>\n                        </a-button>\n                        <a-button @click=\"getPageList(true)\">\n                            <template #icon><icon-sync /></template>\n                            <template #default>重置</template>\n                        </a-button>\n                    </a-space>\n                </a-row>\n            </a-row>\n\n            <!-- 分割线 -->\n            <a-divider v-if=\"showSearchRow\" class=\"mt-2\" />\n\n            <!-- 数据操作区 -->\n            <a-row class=\"w-full flex justify-between\">\n                <a-space>\n                    <!-- 添加 -->\n                    <a-button v-perm=\"['basic:config:add']\" type=\"primary\" size=\"small\" @click=\"addBtnClick()\">\n                        <template #icon><icon-plus /></template>\n                        <template #default>添加</template>\n                    </a-button>\n                </a-space>\n                <a-space>\n                    <!-- 刷新 -->\n                    <a-button shape=\"circle\" size=\"small\" @click=\"getPageList(false)\">\n                        <template #icon><icon-refresh /></template>\n                    </a-button>\n                    <!-- 收缩/展开 -->\n                    <a-button shape=\"circle\" size=\"small\" @click=\"showSearchRow = !showSearchRow\">\n                        <template #icon>\n                            <icon-caret-up v-if=\"showSearchRow\" />\n                            <icon-caret-down v-else />\n                        </template>\n                    </a-button>\n                </a-space>\n            </a-row>\n\n            <!-- 数据展示区 -->\n            <a-row class=\"w-full flex-1 mt-3 overflow-y-auto\">\n                <a-table class=\"w-[100%]\" :scroll=\"{ minWidth: 600, y: '100%' }\" :columns=\"datatable.columns\" :data=\"datatable.records\" :loading=\"datatable.loading\" :pagination=\"false\" table-layout-fixed>\n                    <!-- 参数名称 -->\n                    <template #configName=\"{ record }\">\n                        <a-link @click=\"detailBtnClick(record.configId)\" icon>{{ record.configName }}</a-link>\n                    </template>\n                    <!-- 参数状态 -->\n                    <template #status=\"{ record }\">\n                        <dict-convert :dict-data=\"dicts[proxy.DICT.commonNumberStatus]\" :dict-key=\"record.status\" />\n                    </template>\n                    <!-- 操作 -->\n                    <template #operation=\"{ record }\">\n                        <a-button v-perm=\"['basic:config:update']\" type=\"text\" size=\"mini\" @click=\"updateBtnClick(record.configId)\">\n                            <template #icon>\n                                <icon-edit />\n                            </template>\n                            <template #default>修改</template>\n                        </a-button>\n                        <a-popconfirm v-perm=\"['basic:config:delete']\" content=\"确认要删除吗?\" @ok=\"deleteBtnOkClick(record.configId)\">\n                            <a-button type=\"text\" status=\"danger\" size=\"mini\">\n                                <template #icon>\n                                    <icon-delete />\n                                </template>\n                                <template #default>删除</template>\n                            </a-button>\n                        </a-popconfirm>\n                    </template>\n                </a-table>\n            </a-row>\n\n            <!-- 分页 -->\n            <a-row class=\"w-full flex justify-end mt-2\">\n                <pagination v-if=\"datatable.total > 0\"\n                            v-model:page-num=\"searchForm.pageNum\"\n                            v-model:page-size=\"searchForm.pageSize\"\n                            :total=\"datatable.total\" @pagination=\"getPageList(false)\" />\n            </a-row>\n        </a-row>\n\n        <!-- 添加/修改 -->\n        <a-modal v-model:visible=\"modal.visible\" :width=\"modal.width\" :esc-to-close=\"false\" :mask-closable=\"false\" draggable :footer=\"false\">\n            <template #title>{{ modal.title }}</template>\n            <component :is=\"modal.component\" :params=\"modal.params\" @ok=\"onOk\" @cancel=\"onCancel\" v-if=\"modal.visible\" />\n        </a-modal>\n\n    </a-card>\n</template>\n\n<script setup>\nimport {ref, reactive, getCurrentInstance, shallowRef} from 'vue'\nimport {deleteConfigByConfigIdApi, getPageConfigListApi} from \"~/api/config.js\";\nimport ConfigEdit from \"~/pages/basic/config/ConfigEdit.vue\";\nimport ConfigDetail from \"~/pages/basic/config/ConfigDetail.vue\";\n\n//全局实例\nconst {proxy} = getCurrentInstance()\n//加载字典\nconst dicts = proxy.LoadDicts([proxy.DICT.commonNumberStatus])\n//是否展示搜索区域\nconst showSearchRow = ref(true)\n//搜索参数表单\nconst searchForm = reactive({\n    //参数名称\n    configName: null,\n    //参数键名\n    configKey: null,\n    //参数状态\n    status: null,\n    //页码\n    pageNum: 1,\n    //条数\n    pageSize: 10\n})\n//数据列表\nconst datatable = reactive({\n    //列配置\n    columns: [\n        {title: '参数名称', dataIndex: 'configName', slotName: 'configName', align: 'center'},\n        {title: '参数键名', dataIndex: 'configKey', align: 'center'},\n        {title: '参数键值', dataIndex: 'configValue', align: 'center', ellipsis: true, tooltip: true},\n        {title: '说明', dataIndex: 'description', align: 'center', ellipsis: true, tooltip: true},\n        {title: '操作', slotName: 'operation', align: 'center', width: 160, fixed: 'right'}\n    ],\n    //加载\n    loading: false,\n    //数据列表\n    records: [],\n    //总条数\n    total: 0\n})\n//查询数据列表\nconst getPageList = (isReset = false) => {\n    if (isReset) {\n        searchForm.configName = null\n        searchForm.configKey = null\n        searchForm.status = null\n        searchForm.pageNum = 1\n        searchForm.pageSize = 10\n    }\n    datatable.loading = true\n    getPageConfigListApi(searchForm).then(res => {\n        datatable.records = res.records\n        datatable.total = res.total\n    }).finally(() => {\n        datatable.loading = false\n    })\n}\n//公共模态框\nconst modal = reactive({\n    //是否显示\n    visible: false,\n    //模态框标题\n    title: '参数配置',\n    //宽度\n    width: '50%',\n    //传递参数\n    params: {},\n    //组件名称\n    component: null\n});\n//添加按钮 -> 点击事件\nconst addBtnClick = () => {\n    modal.visible = true\n    modal.title = '添加参数'\n    modal.width = '400px'\n    modal.params = { operationType: proxy.operationType.add.type }\n    modal.component = shallowRef(ConfigEdit)\n}\n//表格行数据 \"修改\" -> 点击\nconst updateBtnClick = (configId) => {\n    modal.visible = true\n    modal.title = '修改参数'\n    modal.width = '400px'\n    modal.params = { operationType: proxy.operationType.update.type, configId: configId }\n    modal.component = shallowRef(ConfigEdit)\n}\n//表格行数据 \"查看\" -> 点击\nconst detailBtnClick = (configId) => {\n    modal.visible = true\n    modal.title = '参数配置详细信息'\n    modal.width = '50%'\n    modal.params = { operationType: proxy.operationType.detail.type, configId: configId }\n    modal.component = shallowRef(ConfigDetail)\n}\n//表格行数据 \"删除\" -> 确认\nconst deleteBtnOkClick = (configId) => {\n    deleteConfigByConfigIdApi(configId).then(() => {\n        proxy.$msg.success(proxy.operationType.delete.success)\n        //刷新列表\n        getPageList()\n    })\n}\n//模态框 -> 确认\nconst onOk = () => {\n    modal.visible = false\n    //查询数据列表\n    getPageList()\n}\n//模态框 -> 取消\nconst onCancel = () => {\n    modal.visible = false\n}\n\n//刷新缓存\nconst refreshConfigLoading = ref(false)\nconst refreshConfigBtnClick = () => {\n    refreshConfigLoading.value = true\n    refreshConfigApi().then(res => {\n        proxy.$msg.success(proxy.operationType.operation.success)\n    }).finally(() => {\n        refreshConfigLoading.value = false\n    })\n}\n\n//查询数据列表\ngetPageList()\n</script>\n<style scoped></style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/dept/DeptDetail.vue",
    "content": "<template>\n    <a-spin class=\"w-[100%]\" :size=\"35\" :loading=\"spinLoading\" tip=\"正在处理, 请稍候...\">\n        <a-descriptions :column=\"2\" bordered>\n            <a-descriptions-item label=\"部门名称\">{{ form.deptName }}</a-descriptions-item>\n            <a-descriptions-item label=\"上级部门\">\n                <template v-if=\"form.parentDeptId === '0'\">\n                    顶级\n                </template>\n                <template v-else>\n                    <dict-convert :dict-data=\"dicts[proxy.DICT.deptList]\" :dict-key=\"form.parentDeptId\" />\n                </template>\n            </a-descriptions-item>\n            <a-descriptions-item label=\"排序\">{{ form.deptSort }}</a-descriptions-item>\n            <a-descriptions-item label=\"部门负责人\">\n                <dict-convert :dict-data=\"dicts[proxy.DICT.userList]\" :dict-key=\"form.deptLeader\" />\n            </a-descriptions-item>\n            <a-descriptions-item label=\"部门电话\">{{ form.phone }}</a-descriptions-item>\n            <a-descriptions-item label=\"部门邮箱\">{{ form.email }}</a-descriptions-item>\n            <a-descriptions-item label=\"部门状态\">\n                <dict-convert :dict-data=\"dicts[proxy.DICT.commonNumberStatus]\" :dict-key=\"form.status\" />\n            </a-descriptions-item>\n        </a-descriptions>\n    </a-spin>\n</template>\n\n<script setup>\nimport { ref, reactive, getCurrentInstance, watch } from 'vue'\nimport {getDeptByDeptIdApi} from \"~/api/dept.js\";\n\n//全局实例\nconst { proxy } = getCurrentInstance()\n//加载字典\nconst dicts = proxy.LoadDicts([proxy.DICT.commonNumberStatus, proxy.DICT.userList, proxy.DICT.deptList])\n//加载中...\nconst spinLoading = ref(false)\n//接收父组件参数\nconst props = defineProps({\n    params: {\n        type: Object,\n        default: () => {}\n    }\n})\n//表单\nconst form = reactive({\n    //部门ID\n    deptId: null,\n    //上级部门ID\n    parentDeptId: null,\n    //部门名称\n    deptName: null,\n    //排序值\n    deptSort: null,\n    //部门负责人\n    deptLeader: null,\n    //部门电话\n    phone: null,\n    //部门邮箱\n    email: null,\n    //部门状态\n    status: null,\n})\n//加载部门详细信息\nconst loadDeptInfo = (deptId) => {\n    spinLoading.value = true\n    getDeptByDeptIdApi(deptId).then(res => {\n        //数据赋值\n        if (res) {\n            for (let key in res) {\n                if (form.hasOwnProperty(key)) {\n                    form[key] = res[key]\n                }\n            }\n        }\n    }).finally(() => {\n        spinLoading.value = false\n    })\n}\n//监听参数变化\nwatch(() => props.params, (newVal, oldVal) => {\n    //部门ID\n    if (props.params.deptId) {\n        //查询数据\n        loadDeptInfo(props.params.deptId)\n    }\n}, { deep: true, immediate: true })\n</script>\n\n<style scoped>\n\n</style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/dept/DeptEdit.vue",
    "content": "<template>\n    <a-spin class=\"w-[100%]\" :size=\"35\" :loading=\"loading\" tip=\"正在处理, 请稍候...\">\n        <a-form :model=\"form\" ref=\"formRef\" layout=\"vertical\" :rules=\"rules\" auto-label-width>\n            <div class=\"flex justify-between\" style=\"flex-wrap: wrap;\">\n                <a-form-item class=\"w-[100%]\" field=\"parentDeptId\" label=\"上级部门\" tooltip=\"选择全部表示顶级部门\">\n                    <a-tree-select v-model=\"form.parentDeptId\" :data=\"deptTree\" placeholder=\"上级部门\" allow-clear\n                        :fieldNames=\"{key: 'deptId', title: 'deptName', children: 'children'}\" />\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" field=\"deptName\" label=\"部门名称\" required>\n                    <a-input v-model=\"form.deptName\" placeholder=\"部门名称\" />\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" field=\"deptSort\" label=\"排序值\" required tooltip=\"展示顺序，按照数值升序排序\">\n                    <a-input-number :min=\"0\" v-model=\"form.deptSort\" placeholder=\"排序值\" />\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" field=\"deptLeader\" label=\"部门负责人\" required>\n                    <a-select v-model=\"form.deptLeader\" placeholder=\"部门负责人\" allow-clear allow-search>\n                        <a-option v-for=\"(d, index) in dicts[proxy.DICT.userList]\" :key=\"index\" :value=\"String(d.dictKey)\" :label=\"d.dictValue\" />\n                    </a-select>\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" field=\"phone\" label=\"部门电话\" required>\n                    <a-input v-model=\"form.phone\" placeholder=\"部门电话\" />\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" field=\"email\" label=\"部门邮箱\">\n                    <a-input v-model=\"form.email\" placeholder=\"部门邮箱\" />\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" field=\"status\" label=\"部门状态\" required v-if=\"props.params.operationType === proxy.operationType.update.type\">\n                    <a-select v-model=\"form.status\" placeholder=\"部门状态\" allow-clear>\n                        <a-option v-for=\"(d, index) in dicts[proxy.DICT.commonNumberStatus]\" :key=\"index\" :value=\"d.dictKey\" :label=\"d.dictValue\" />\n                    </a-select>\n                </a-form-item>\n            </div>\n        </a-form>\n\n        <!-- 分割线 -->\n        <a-divider class=\"mt-0\" />\n\n        <div class=\"flex justify-end\">\n            <a-space>\n                <a-button @click=\"cancelBtnClick\">取消</a-button>\n                <a-button type=\"primary\" @click=\"okBtnClick\">确定</a-button>\n            </a-space>\n        </div>\n    </a-spin>\n</template>\n\n<script setup>\nimport {ref, reactive, getCurrentInstance, watch, onMounted} from 'vue'\nimport { getDeptListApi, getDeptByDeptIdApi, addDeptApi, updateDeptByDeptIdApi } from '~/api/dept'\n\n//全局实例\nconst {proxy} = getCurrentInstance()\n//加载字典\nconst dicts = proxy.LoadDicts([proxy.DICT.commonNumberStatus, proxy.DICT.userList])\n//接收父组件参数\nconst props = defineProps({\n    params: {\n        type: Object,\n        default: () => {}\n    }\n})\n//父组件函数\nconst emits = defineEmits(['ok', 'cancel'])\n//加载中...\nconst loading = ref(false)\n//表单ref\nconst formRef = ref(null)\n//表单\nconst form = reactive({\n    //部门ID\n    deptId: null,\n    //上级部门ID\n    parentDeptId: \"0\",\n    //部门名称\n    deptName: null,\n    //排序值\n    deptSort: 0,\n    //部门负责人\n    deptLeader: null,\n    //部门电话\n    phone: null,\n    //部门邮箱\n    email: null,\n    //部门状态\n    status: null,\n})\n//表单验证规则\nconst rules = {\n    parentDeptId: [{required: true, message: '上级部门不能为空', trigger: 'submit'}],\n    deptName: [{required: true, message: '部门名称不能为空', trigger: 'submit'}],\n    deptSort: [{required: true, message: '排序值不能为空', trigger: 'submit'}],\n    deptLeader: [{required: true, message: '部门负责人不能为空', trigger: 'submit'}],\n    phone: [{required: true, message: '部门电话不能为空', trigger: 'submit'}],\n    email: [{required: true, message: '部门邮箱不能为空', trigger: 'submit'}],\n}\n//确定 -> 点击\nconst okBtnClick = () => {\n    //表单验证\n    formRef.value.validate((valid) => {\n        if (valid) {return false}\n        //添加\n        if (props.params.operationType === proxy.operationType.add.type) {\n            loading.value = true\n            addDeptApi(form).then(() => {\n                proxy.$msg.success(proxy.operationType.add.success)\n                emits('ok')\n            }).finally(() => {\n                loading.value = false\n            })\n        }\n        //修改\n        if (props.params.operationType === proxy.operationType.update.type) {\n            loading.value = true\n            updateDeptByDeptIdApi(form).then(() => {\n                proxy.$msg.success(proxy.operationType.update.success)\n                emits('ok')\n            }).finally(() => {\n                loading.value = false\n            })\n        }\n    })\n}\n//取消 -> 点击\nconst cancelBtnClick = () => {\n    emits('cancel')\n}\n//部门树数据\nconst deptTree = ref([])\n//获取部门数据列表\nconst getDeptTree = () => {\n    getDeptListApi({}).then(res => {\n        //部门树数据处理，将数据挂载到\"全部\"下边\n        deptTree.value = [{deptId: '0', deptName: '全部', children: res}]\n    })\n}\n//加载部门详细信息\nconst loadDeptInfo = (deptId) => {\n    loading.value = true\n    getDeptByDeptIdApi(deptId).then(res => {\n        //数据赋值\n        if (res) {\n            for (let key in res) {\n                if (form.hasOwnProperty(key)) {\n                    form[key] = res[key]\n                }\n            }\n        }\n    }).finally(() => {\n        loading.value = false\n    })\n}\n//监听参数变化\nwatch(() => props.params, (newVal, oldVal) => {\n    //如果有deptId参数，则是 \"修改\"\n    if (props.params.deptId) {\n        loadDeptInfo(props.params.deptId)\n    }\n    //如果有parentDeptId参数，则是 表格行 \"添加\"\n    if (props.params.parentDeptId) {\n        //给上级部门ID赋值\n        form.parentDeptId = props.params.parentDeptId\n    }\n    //加载部门树\n    getDeptTree()\n}, { deep: true, immediate: true })\n</script>\n<style scoped></style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/dept/DeptMgt.vue",
    "content": "<template>\n    <a-card :body-style=\"{height: 'calc(100vh - 125px)'}\">\n\n        <!-- 数据列表 -->\n        <a-row class=\"w-full h-full flex flex-col overflow-x-auto overflow-y-hidden\">\n            <!-- 查询条件 -->\n            <a-row class=\"w-full\" v-if=\"showSearchRow\">\n                <a-form :model=\"searchForm\" layout=\"inline\" label-align=\"left\" size=\"small\">\n                    <a-form-item field=\"deptName\" label=\"部门名称\">\n                        <a-input v-model=\"searchForm.deptName\" placeholder=\"部门名称\" allow-clear />\n                    </a-form-item>\n                    <a-form-item field=\"status\" label=\"部门状态\">\n                        <a-select v-model=\"searchForm.status\" placeholder=\"部门状态\" allow-clear>\n                            <a-option v-for=\"(d, index) in dicts[proxy.DICT.commonNumberStatus]\" :key=\"index\" :value=\"d.dictKey\" :label=\"d.dictValue\" />\n                        </a-select>\n                    </a-form-item>\n                </a-form>\n                <a-row justify=\"center\" class=\"w-full mt-2\">\n                    <a-space>\n                        <a-button type=\"primary\" @click=\"getList(false)\">\n                            <template #icon><icon-search /></template>\n                            <template #default>查询</template>\n                        </a-button>\n                        <a-button @click=\"getList(true)\">\n                            <template #icon><icon-sync /></template>\n                            <template #default>重置</template>\n                        </a-button>\n                    </a-space>\n                </a-row>\n            </a-row>\n\n            <!-- 分割线 -->\n            <a-divider v-if=\"showSearchRow\" class=\"mt-2\" />\n\n            <!-- 数据操作区 -->\n            <a-row class=\"w-full flex justify-between\">\n                <a-space>\n                    <!-- 添加 -->\n                    <a-button v-perm=\"['basic:dept:add']\" type=\"primary\" size=\"small\" @click=\"addBtnClick()\">\n                        <template #icon><icon-plus /></template>\n                        <template #default>添加</template>\n                    </a-button>\n                    <!-- 展开/折叠 -->\n                    <a-button size=\"small\" @click=\"treeExpand = !treeExpand\">\n                        <template #icon><icon-swap /></template>\n                        <template #default>展开/折叠</template>\n                    </a-button>\n                </a-space>\n                <a-space>\n                    <!-- 刷新 -->\n                    <a-button shape=\"circle\" size=\"small\" @click=\"getList(false)\">\n                        <template #icon><icon-refresh /></template>\n                    </a-button>\n                    <!-- 收缩/展开 -->\n                    <a-button shape=\"circle\" size=\"small\" @click=\"showSearchRow = !showSearchRow\">\n                        <template #icon>\n                            <icon-caret-up v-if=\"showSearchRow\" />\n                            <icon-caret-down v-else />\n                        </template>\n                    </a-button>\n                </a-space>\n            </a-row>\n\n            <!-- 数据展示区 -->\n            <a-row class=\"w-full flex-1 mt-3 overflow-y-auto\">\n                <a-table ref=\"tableRef\" class=\"w-[100%]\" :scroll=\"{y: '100%'}\" :columns=\"datatable.columns\" :data=\"datatable.records\" :loading=\"datatable.loading\" row-key=\"deptId\" :pagination=\"false\" table-layout-fixed>\n                    <!-- 部门名称 -->\n                    <template #deptName=\"{ record }\">\n                        <a-link @click=\"detailBtnClick(record.deptId)\" icon>{{ record.deptName }}</a-link>\n                    </template>\n                    <!-- 部门负责人 -->\n                    <template #deptLeader=\"{ record }\">\n                        <dict-convert :dict-data=\"dicts[proxy.DICT.userList]\" :dict-key=\"record.deptLeader\" />\n                    </template>\n                    <!-- 部门状态 -->\n                    <template #status=\"{ record }\">\n                        <dict-convert :dict-data=\"dicts[proxy.DICT.commonNumberStatus]\" :dict-key=\"record.status\" />\n                    </template>\n                    <!-- 操作 -->\n                    <template #operation=\"{ record }\">\n                        <a-button v-perm=\"['basic:dept:add']\" type=\"text\" size=\"mini\" @click=\"addRowBtnClick(record)\" style=\"padding: 0 5px\">\n                            <template #icon>\n                                <icon-plus />\n                            </template>\n                            <template #default>添加</template>\n                        </a-button>\n                        <a-button v-perm=\"['basic:dept:update']\" type=\"text\" size=\"mini\" @click=\"updateBtnClick(record.deptId)\" style=\"padding: 0 5px\">\n                            <template #icon>\n                                <icon-edit />\n                            </template>\n                            <template #default>修改</template>\n                        </a-button>\n                        <a-popconfirm content=\"确认要删除吗?\" @ok=\"deleteBtnOkClick(record)\">\n                            <a-button v-perm=\"['basic:dept:delete']\" type=\"text\" status=\"danger\" size=\"mini\" style=\"padding: 0 5px\">\n                                <template #icon>\n                                    <icon-delete />\n                                </template>\n                                <template #default>删除</template>\n                            </a-button>\n                        </a-popconfirm>\n                    </template>\n                </a-table>\n            </a-row>\n        </a-row>\n\n\n        <!-- 添加/修改 -->\n        <a-modal v-model:visible=\"modal.visible\" width=\"50%\" :esc-to-close=\"false\" :mask-closable=\"false\" draggable :footer=\"false\">\n            <template #title>{{ modal.title }}</template>\n            <component :is=\"modal.component\" :params=\"modal.params\" @ok=\"onOk\" @cancel=\"onCancel\" v-if=\"modal.visible\" />\n        </a-modal>\n    </a-card>\n</template>\n\n<script setup>\nimport {ref, reactive, getCurrentInstance, shallowRef, watch, onMounted} from 'vue'\nimport DeptEdit from \"~/pages/basic/dept/DeptEdit.vue\";\nimport DeptDetail from \"~/pages/basic/dept/DeptDetail.vue\";\nimport {getDeptListApi, deleteDeptByDeptIdApi} from '~/api/dept'\n\n//全局实例\nconst {proxy} = getCurrentInstance()\n//加载字典\nconst dicts = proxy.LoadDicts([proxy.DICT.commonNumberStatus, proxy.DICT.userList])\n//是否展示搜索区域\nconst showSearchRow = ref(true)\n//搜索参数表单\nconst searchForm = reactive({\n    //部门名称\n    deptName: null,\n    //部门状态\n    status: null\n})\n//表格\nconst tableRef = ref()\n//数据列表\nconst datatable = reactive({\n    //列配置\n    columns: [\n        {title: '部门名称', dataIndex: 'deptName', slotName: 'deptName', align: 'left', width: 250, headerCellClass: 'w-[100%] flex justify-center'},\n        {title: '负责人', dataIndex: 'deptLeader', slotName: 'deptLeader', align: 'center'},\n        {title: '排序', dataIndex: 'deptSort', align: 'center', width: 80},\n        {title: '电话', dataIndex: 'phone', align: 'center', ellipsis: true, tooltip: true},\n        {title: '邮箱', dataIndex: 'email', align: 'center', ellipsis: true, tooltip: true},\n        {title: '部门状态', dataIndex: 'status', slotName: 'status', align: 'center', width: 100},\n        {title: '操作', slotName: 'operation', align: 'center', width: 200, fixed: 'right'}\n    ],\n    //加载\n    loading: false,\n    //数据列表\n    records: []\n})\n//查询数据列表\nconst getList = (isReset = false) => {\n    //重置查询条件\n    if (isReset) {\n        searchForm.deptName = null\n        searchForm.status = null\n    }\n    datatable.loading = true\n    getDeptListApi(searchForm).then(res => {\n        //table数据赋值\n        datatable.records = res\n    }).finally(() => {\n        datatable.loading = false\n    })\n}\n//公共模态框\nconst modal = reactive({\n    //是否显示\n    visible: false,\n    //模态框标题\n    title: '部门管理',\n    //传递参数\n    params: {},\n    //组件名称\n    component: null\n});\n//添加按钮 -> 点击事件\nconst addBtnClick = () => {\n    modal.visible = true\n    modal.title = '添加部门'\n    modal.params = { operationType: proxy.operationType.add.type }\n    modal.component = shallowRef(DeptEdit)\n}\n//展开/折叠 - 默认折叠\nconst treeExpand = ref(false)\n//监听展开/折叠\nwatch(() => treeExpand.value, (newVal, oldVal) => {\n    if (tableRef.value) {\n        tableRef.value.expandAll(treeExpand.value)\n    }\n}, { deep: true, immediate: true })\n\n//表格行数据 \"添加\" -> 点击\nconst addRowBtnClick = (record) => {\n    modal.visible = true\n    modal.title = '添加部门'\n    modal.params = { operationType: proxy.operationType.add.type, parentDeptId: record.deptId }\n    modal.component = shallowRef(DeptEdit)\n}\n//表格行数据 \"删除\" -> 确认\nconst deleteBtnOkClick = (record) => {\n    if (record.children && record.children.length > 0) {\n        proxy.$msg.error('该部门下包含下级部门，请先删除下级部门')\n        return\n    }\n    deleteDeptByDeptIdApi(record.deptId).then(() => {\n        proxy.$msg.success(proxy.operationType.delete.success)\n        //刷新列表\n        getList()\n    })\n}\n//表格行数据 \"修改\" -> 点击\nconst updateBtnClick = (deptId) => {\n    modal.visible = true\n    modal.title = '修改部门'\n    modal.params = { operationType: proxy.operationType.update.type, deptId: deptId }\n    modal.component = shallowRef(DeptEdit)\n}\n//表格行数据 \"查看\" -> 点击\nconst detailBtnClick = (deptId) => {\n    modal.visible = true\n    modal.title = '部门详细信息'\n    modal.params = { operationType: proxy.operationType.detail.type, deptId: deptId }\n    modal.component = shallowRef(DeptDetail)\n}\n//模态框 -> 确认\nconst onOk = () => {\n    modal.visible = false\n    //查询数据列表\n    getList()\n}\n//模态框 -> 取消\nconst onCancel = () => {\n    modal.visible = false\n}\n//初始化\nonMounted(() => {\n    //查询数据列表\n    getList()\n})\n</script>\n<style scoped></style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/dict/DictDetail.vue",
    "content": "<template>\n    <a-spin class=\"w-[100%]\" :size=\"35\" :loading=\"spinLoading\" tip=\"正在处理, 请稍候...\">\n        <a-descriptions :column=\"2\" bordered>\n            <a-descriptions-item label=\"字典名称\">{{ form.dictName }}</a-descriptions-item>\n            <a-descriptions-item label=\"字典类型\">{{ form.dictType }}</a-descriptions-item>\n            <a-descriptions-item label=\"字典描述\">{{ form.dictDesc }}</a-descriptions-item>\n        </a-descriptions>\n\n        <!-- 分割线 -->\n        <a-divider orientation=\"center\">字典数据</a-divider>\n\n        <a-descriptions :column=\"5\" bordered>\n            <template v-for=\"(item, index) in form.dictDataList\" :key=\"index\">\n                <a-descriptions-item label=\"字典Key\">{{ item.dictKey }}</a-descriptions-item>\n                <a-descriptions-item label=\"字典Value\">{{ item.dictValue }}</a-descriptions-item>\n                <a-descriptions-item label=\"排序值\">{{ item.dictOrder }}</a-descriptions-item>\n                <a-descriptions-item label=\"字典样式\">\n                    <template v-if=\"item.dictClass\">\n                        <dict-convert :dict-data=\"dicts[proxy.DICT.dictClass]\" :dict-key=\"item.dictClass\" />\n                    </template>\n                    <template v-else>\n                        无样式\n                    </template>\n                </a-descriptions-item>\n                <a-descriptions-item label=\"字典状态\">\n                    <dict-convert :dict-data=\"dicts[proxy.DICT.commonNumberStatus]\" :dict-key=\"item.status\" />\n                </a-descriptions-item>\n            </template>\n        </a-descriptions>\n    </a-spin>\n</template>\n\n<script setup>\nimport { ref, reactive, getCurrentInstance, watch } from 'vue'\nimport { getDictByDictTypeApi } from '~/api/dict'\n\n//全局实例\nconst { proxy } = getCurrentInstance()\n//加载字典\nconst dicts = proxy.LoadDicts([proxy.DICT.commonNumberStatus, proxy.DICT.dictClass])\n//接收父组件参数\nconst props = defineProps({\n    params: {\n        type: Object,\n        default: () => {}\n    }\n})\n//表单\nconst form = reactive({\n    //字典名称\n    dictName: null,\n    //字典类型\n    dictType: null,\n    //字典描述\n    dictDesc: null,\n    //字典数据\n    dictDataList: []\n})\n//加载中...\nconst spinLoading = ref(false)\n//根据字典类型加载字典数据\nconst loadDictInfo = (dictType) => {\n    //根据类型查询字典\n    spinLoading.value = true\n    getDictByDictTypeApi(dictType).then(res => {\n        if (res && res.dictDataList) {\n            //数据赋值\n            form.dictName = res.dictName\n            form.dictType = res.dictType\n            form.dictDesc = res.dictDesc\n            form.dictDataList = res.dictDataList\n        }\n    }).finally(() => {\n        spinLoading.value = false\n    })\n}\n//监听参数变化\nwatch(() => props.params, (newVal, oldVal) => {\n    //字典类型\n    if (props.params.dictType) {\n        //查询数据\n        loadDictInfo(props.params.dictType)\n    }\n}, { deep: true, immediate: true })\n</script>\n\n<style scoped>\n\n</style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/dict/DictEdit.vue",
    "content": "<template>\n    <a-spin class=\"w-[100%]\" :size=\"35\" :loading=\"spinLoading\" tip=\"正在处理, 请稍候...\">\n        <a-form :model=\"form\" layout=\"vertical\" ref=\"formRef\" :rules=\"rules\" auto-label-width>\n            <a-row :gutter=\"12\">\n                <a-col :span=\"12\">\n                    <a-form-item field=\"dictName\" label=\"字典名称\" required>\n                        <a-input v-model=\"form.dictName\" placeholder=\"字典名称\" />\n                    </a-form-item>\n                </a-col>\n                <a-col :span=\"12\">\n                    <a-form-item field=\"dictType\" label=\"字典类型\" required>\n                        <a-input v-model=\"form.dictType\" placeholder=\"字典类型\" />\n                    </a-form-item>\n                </a-col>\n            </a-row>\n            <a-row :gutter=\"2\">\n                <a-form-item field=\"dictDesc\" label=\"字典描述\">\n                    <a-textarea v-model=\"form.dictDesc\" placeholder=\"字典描述\" />\n                </a-form-item>\n            </a-row>\n\n            <!-- 分割线 -->\n            <div class=\"w-[calc(100%-100px)] flex justify-between items-center\">\n                <a-divider orientation=\"center\">字典数据</a-divider>\n                <a-button class=\"ml-5 w-[100px]\" type=\"primary\" @click=\"addDictDataBtnClick()\" size=\"mini\">\n                    <template #icon><icon-plus /></template>\n                    <span>添加</span>\n                </a-button>\n            </div>\n\n            <a-scrollbar class=\"w-[100%] max-h-[250px] overflow-auto\" type=\"track\">\n                <div class=\"w-[100%]\">\n                    <a-row class=\"w-[100%]\" :gutter=\"12\" v-for=\"(dict, index) in form.dictDataList\" :key=\"index\" justify=\"space-between\">\n                        <a-col :span=\"props.params.operationType === proxy.operationType.update.type ? 6 : 8\">\n                            <a-form-item field=\"dictKey\" label=\"字典key\">\n                                <a-input v-model=\"dict.dictKey\" placeholder=\"字典key\" />\n                            </a-form-item>\n                        </a-col>\n                        <a-col :span=\"props.params.operationType === proxy.operationType.update.type ? 6 : 8\">\n                            <a-form-item field=\"dictValue\" label=\"字典Value\">\n                                <a-input v-model=\"dict.dictValue\" placeholder=\"字典Value\" />\n                            </a-form-item>\n                        </a-col>\n                        <a-col :span=\"4\">\n                            <a-form-item field=\"dictClass\" label=\"字典样式\">\n                                <a-select v-model=\"dict.dictClass\" placeholder=\"字典样式\" allow-clear allow-search>\n                                    <a-option v-for=\"(item, index) in dicts[proxy.DICT.dictClass]\" :key=\"index\" :value=\"item.dictKey\" :label=\"item.dictValue\"  />\n                                </a-select>\n                            </a-form-item>\n                        </a-col>\n                        <a-col :span=\"2\">\n                            <a-form-item field=\"dictOrder\" label=\"排序\">\n                                <a-input-number v-model=\"dict.dictOrder\" placeholder=\"排序\" :min=\"0\" />\n                            </a-form-item>\n                        </a-col>\n                        <a-col :span=\"3\" v-if=\"props.params.operationType === proxy.operationType.update.type\">\n                            <a-form-item field=\"status\" label=\"状态\">\n                                <a-select v-model=\"dict.status\" placeholder=\"状态\" allow-clear>\n                                    <a-option v-for=\"(item, index) in dicts[proxy.DICT.commonNumberStatus]\" :key=\"index\" :value=\"item.dictKey\" :label=\"item.dictValue\" />\n                                </a-select>\n                            </a-form-item>\n                        </a-col>\n                        <a-col :span=\"2\">\n                            <a-popconfirm content=\"确认要删除吗?\" @ok=\"delDictDataBtnClick(dict)\">\n                                <a-button class=\"mt-[1.9rem] flex justify-center\" type=\"primary\" status=\"danger\">\n                                    <template #icon><icon-minus /></template>\n                                </a-button>\n                            </a-popconfirm>\n                        </a-col>\n                    </a-row>\n                </div>\n            </a-scrollbar>\n        </a-form>\n\n        <!-- 分割线 -->\n        <a-divider class=\"mt-0\" />\n\n        <div class=\"flex justify-end\">\n            <a-space>\n                <a-button @click=\"cancelBtnClick\">取消</a-button>\n                <a-button type=\"primary\" @click=\"okBtnClick\">确定</a-button>\n            </a-space>\n        </div>\n    </a-spin>\n</template>\n\n<script setup>\nimport { ref, reactive, getCurrentInstance, watch } from 'vue'\nimport { addDictApi, updateDictApi, getDictByDictTypeApi, deleteDictByDictIdApi } from '~/api/dict'\nimport {status} from \"~/utils/sys.js\";\n\n//全局实例\nconst { proxy } = getCurrentInstance()\n//加载字典\nconst dicts = proxy.LoadDicts([proxy.DICT.commonNumberStatus, proxy.DICT.dictClass])\n//接收父组件参数\nconst props = defineProps({\n    params: {\n        type: Object,\n        default: () => {}\n    }\n})\n//父组件函数\nconst emits = defineEmits(['ok', 'cancel'])\n//字典空数据\nconst emptyDictData = {rowKey: proxy.randomCode(18), dictId: null, dictKey: null, dictValue: null, dictOrder: 0, dictClass: null, status: status.status_1.key}\n//表单ref\nconst formRef = ref(null)\n//表单\nconst form = reactive({\n    //字典名称\n    dictName: null,\n    //字典类型\n    dictType: null,\n    //字典描述\n    dictDesc: null,\n    //字典数据\n    dictDataList: [JSON.parse(JSON.stringify(emptyDictData))]\n})\n//表单验证规则\nconst rules = {\n    dictName: [{required: true, message: '字典名称不能为空', trigger: 'submit'}],\n    dictType: [{required: true, message: '字典类型不能为空', trigger: 'submit'}]\n}\n//添加字典数据按钮 -> 点击事件\nconst addDictDataBtnClick = () => {\n    //添加时，dictId为null，新增rowKey用于标识新增的数据\n    let dictData = JSON.parse(JSON.stringify(emptyDictData))\n    dictData['rowKey'] = proxy.randomCode(18)\n    form.dictDataList.push(dictData)\n}\n//删除字典数据按钮 -> 点击事件\nconst delDictDataBtnClick = (dict) => {\n    if (dict.dictId) {\n        //有字典ID，已存在的数据，删除数据\n        deleteDictByDictIdApi(dict.dictId).then(() => {\n            proxy.$msg.success(proxy.operationType.delete.success)\n            //从数组中，根据dictId剔除该条数据\n            for (let i = 0;i < form.dictDataList.length;i++) {\n                if (dict.dictId === form.dictDataList[i].dictId) {\n                    form.dictDataList.splice(i, 1)\n                    break\n                }\n            }\n        })\n    } else if (dict.rowKey) {\n        //无字典ID，有rowKey，从数组中，根据rowKey剔除该条数据\n        for (let i = 0;i < form.dictDataList.length;i++) {\n            if (dict.rowKey === form.dictDataList[i].rowKey) {\n                form.dictDataList.splice(i, 1)\n                break\n            }\n        }\n        proxy.$msg.success(proxy.operationType.delete.success)\n    }\n}\n//加载中...\nconst spinLoading = ref(false)\n//根据字典类型加载字典数据\nconst loadDictInfo = (dictType) => {\n    //根据类型查询字典\n    spinLoading.value = true\n    getDictByDictTypeApi(dictType).then(res => {\n        if (res && res.dictDataList) {\n            //数据赋值\n            form.dictName = res.dictName\n            form.dictType = res.dictType\n            form.dictDesc = res.dictDesc\n            form.dictDataList = res.dictDataList\n        }\n    }).finally(() => {\n        spinLoading.value = false\n    })\n}\n//确定 -> 点击\nconst okBtnClick = () => {\n    //添加\n    if (props.params.operationType === proxy.operationType.add.type) {\n        spinLoading.value = true\n        addDictApi(form).then(() => {\n            proxy.$msg.success(proxy.operationType.add.success)\n            emits('ok')\n        }).finally(() => {\n            spinLoading.value = false\n        })\n    }\n    //修改\n    if (props.params.operationType === proxy.operationType.update.type) {\n        spinLoading.value = true\n        updateDictApi(form).then(() => {\n            proxy.$msg.success(proxy.operationType.update.success)\n            emits('ok')\n        }).finally(() => {\n            spinLoading.value = false\n        })\n    }\n}\n//取消 -> 点击\nconst cancelBtnClick = () => {\n    emits('cancel')\n}\n//监听参数变化\nwatch(() => props.params, (newVal, oldVal) => {\n    //字典类型\n    if (props.params.dictType) {\n        loadDictInfo(props.params.dictType)\n    }\n}, { deep: true, immediate: true })\n</script>\n\n<style scoped></style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/dict/DictMgt.vue",
    "content": "<template>\n    <a-card class=\"p-0\" :body-style=\"{height: 'calc(100vh - 125px)'}\">\n\n        <!-- 数据列表 -->\n        <a-row class=\"w-full h-full flex flex-col overflow-x-auto overflow-y-hidden\">\n            <!-- 查询条件 -->\n            <a-row class=\"w-full\" v-if=\"showSearchRow\">\n                <a-form :model=\"form\" layout=\"inline\" label-align=\"left\" size=\"small\">\n                    <a-form-item field=\"permName\" label=\"字典名称\">\n                        <a-input v-model=\"form.dictName\" placeholder=\"字典名称\" allow-clear />\n                    </a-form-item>\n                    <a-form-item field=\"permType\" label=\"字典类型\">\n                        <a-input v-model=\"form.dictType\" placeholder=\"字典类型\" allow-clear />\n                    </a-form-item>\n                </a-form>\n                <a-row justify=\"center\" class=\"w-full mt-2\">\n                    <a-space>\n                        <a-button type=\"primary\" @click=\"getPageList(false)\">\n                            <template #icon><icon-search /></template>\n                            <template #default>查询</template>\n                        </a-button>\n                        <a-button @click=\"getPageList(true)\">\n                            <template #icon><icon-sync /></template>\n                            <template #default>重置</template>\n                        </a-button>\n                    </a-space>\n                </a-row>\n            </a-row>\n\n            <!-- 分割线 -->\n            <a-divider v-if=\"showSearchRow\" class=\"mt-2\" />\n\n            <!-- 数据操作区 -->\n            <a-row class=\"w-full flex justify-between\">\n                <a-space>\n                    <!-- 添加 -->\n                    <a-button v-perm=\"['basic:dict:add']\" type=\"primary\" size=\"small\" @click=\"addBtnClick()\">\n                        <template #icon><icon-plus /></template>\n                        <template #default>添加</template>\n                    </a-button>\n                </a-space>\n                <a-space>\n                    <!-- 刷新 -->\n                    <a-button shape=\"circle\" size=\"small\" @click=\"getPageList(false)\">\n                        <template #icon><icon-refresh /></template>\n                    </a-button>\n                    <!-- 收缩/展开 -->\n                    <a-button shape=\"circle\" size=\"small\" @click=\"showSearchRow = !showSearchRow\">\n                        <template #icon>\n                            <icon-caret-up v-if=\"showSearchRow\" />\n                            <icon-caret-down v-else />\n                        </template>\n                    </a-button>\n                </a-space>\n            </a-row>\n\n            <!-- 数据展示区 -->\n            <a-row class=\"w-full flex-1 mt-3 overflow-y-auto\">\n                <a-table class=\"w-[100%]\" :scroll=\"{ y: '100%' }\" :columns=\"datatable.columns\" :data=\"datatable.records\" :loading=\"datatable.loading\" :pagination=\"false\" table-layout-fixed>\n                    <!-- 字典名称 -->\n                    <template #dictName=\"{ record }\">\n                        <a-link @click=\"detailBtnClick(record.dictType)\" icon>{{ record.dictName }}</a-link>\n                    </template>\n                    <!-- 操作 -->\n                    <template #operation=\"{ record }\">\n                        <a-button v-perm=\"['basic:dict:update']\" type=\"text\" size=\"mini\" @click=\"updateBtnClick(record.dictType)\">\n                            <template #icon>\n                                <icon-edit />\n                            </template>\n                            <template #default>修改</template>\n                        </a-button>\n                        <a-popconfirm content=\"确认要删除吗?\" @ok=\"deleteBtnOkClick(record.dictType)\">\n                            <a-button v-perm=\"['basic:dict:delete']\" type=\"text\" status=\"danger\" size=\"mini\">\n                                <template #icon>\n                                    <icon-delete />\n                                </template>\n                                <template #default>删除</template>\n                            </a-button>\n                        </a-popconfirm>\n                    </template>\n                </a-table>\n            </a-row>\n\n            <!-- 分页 -->\n            <a-row class=\"w-full flex justify-end mt-2\">\n                <pagination v-if=\"datatable.total > 0\"\n                            v-model:page-num=\"form.pageNum\"\n                            v-model:page-size=\"form.pageSize\"\n                            :total=\"datatable.total\" @pagination=\"getPageList(false)\" />\n            </a-row>\n        </a-row>\n\n        <!-- 添加/修改 -->\n        <a-modal v-model:visible=\"modal.visible\" width=\"60%\" :esc-to-close=\"false\" :mask-closable=\"false\" draggable :footer=\"false\">\n            <template #title>{{ modal.title }}</template>\n            <component :is=\"modal.component\" :params=\"modal.params\" @ok=\"onOk\" @cancel=\"onCancel\" v-if=\"modal.visible\" />\n        </a-modal>\n\n    </a-card>\n</template>\n\n<script setup>\nimport { ref, reactive, shallowRef, getCurrentInstance } from 'vue'\nimport { getPageDictListApi, deleteDictByDictTypeApi } from '~/api/dict.js'\nimport DictEdit from '~/pages/basic/dict/DictEdit.vue'\nimport DictDetail from '~/pages/basic/dict/DictDetail.vue'\n\n//全局实例\nconst {proxy} = getCurrentInstance()\n//是否展示搜索区域\nconst showSearchRow = ref(true)\n//搜索参数表单\nconst form = reactive({\n    //权限名称\n    dictName: null,\n    //字典类型\n    dictType: null,\n    //页码\n    pageNum: 1,\n    //条数\n    pageSize: 10\n})\n//数据列表\nconst datatable = reactive({\n    //列配置\n    columns: [\n        {title: '字典名称', dataIndex: 'dictName', slotName: 'dictName', align: 'center'},\n        {title: '字典类型', dataIndex: 'dictType', align: 'center'},\n        {title: '字典描述', dataIndex: 'dictDesc', align: 'center', ellipsis: true, tooltip: true},\n        {title: '操作', slotName: 'operation', align: 'center', width: 160, fixed: 'right'}\n    ],\n    //加载\n    loading: false,\n    //数据列表\n    records: [],\n    //总条数\n    total: 0\n})\n//查询数据列表\nconst getPageList = (isReset = false) => {\n    //重置查询条件\n    if (isReset) {\n        form.dictName = null\n        form.dictType = null\n        form.pageNum = 1\n        form.pageSize = 10\n    }\n    datatable.loading = true\n    getPageDictListApi(form).then(res => {\n        datatable.records = res.records\n        datatable.total = res.total\n    }).finally(() => {\n        datatable.loading = false\n    })\n}\n//表格行数据 \"删除\" -> 确认\nconst deleteBtnOkClick = (dictType) => {\n    deleteDictByDictTypeApi(dictType).then(() => {\n        proxy.$msg.success(proxy.operationType.delete.success)\n        //刷新列表\n        getPageList()\n    })\n}\n//公共模态框\nconst modal = reactive({\n    //是否显示\n    visible: false,\n    //模态框标题\n    title: '字典管理',\n    //传递参数\n    params: {},\n    //组件名称\n    component: null\n})\n//表格行数据 \"修改\" -> 点击\nconst updateBtnClick = (dictType) => {\n    modal.visible = true\n    modal.title = '修改字典'\n    modal.params = { operationType: proxy.operationType.update.type, dictType: dictType }\n    modal.component = shallowRef(DictEdit)\n}\n//添加按钮 -> 点击\nconst addBtnClick = () => {\n    modal.visible = true\n    modal.title = '添加字典'\n    modal.params = { operationType: proxy.operationType.add.type }\n    modal.component = shallowRef(DictEdit)\n}\n//表格行数据 \"查看\" -> 点击\nconst detailBtnClick = (dictType) => {\n    modal.visible = true\n    modal.title = '字典详细信息'\n    modal.params = { operationType: proxy.operationType.detail.type, dictType: dictType }\n    modal.component = shallowRef(DictDetail)\n}\n//模态框 -> 确认\nconst onOk = () => {\n    modal.visible = false\n    //查询数据列表\n    getPageList()\n}\n//模态框 -> 取消\nconst onCancel = () => {\n    modal.visible = false\n}\n\n//查询数据列表\ngetPageList()\n</script>\n<style scoped></style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/file/FileMgt.vue",
    "content": "<template>\n    <a-card :body-style=\"{height: 'calc(100vh - 125px)'}\">\n        <div class=\"w-full h-full flex justify-between\">\n            <!-- 按文件来源将文件分类 -->\n            <a-row class=\"h-full border-r pr-3\">\n                <a-list class=\"w-[240px]\" size=\"small\" hoverable>\n                    <a-list-item class=\"cursor-pointer\" @click=\"fileSourceClick(null)\"\n                                 :class=\"searchForm.fileSource === null ? 'file-source-active' : ''\">全部文件</a-list-item>\n                    <a-list-item\n                            class=\"cursor-pointer\"\n                            @click=\"fileSourceClick(d.dictKey)\"\n                            :class=\"searchForm.fileSource === d.dictKey ? 'file-source-active' : ''\"\n                            v-for=\"(d, index) in dicts[proxy.DICT.fileSource]\"\n                            :key=\"index\">\n                        {{d.dictValue}}\n                    </a-list-item>\n                </a-list>\n            </a-row>\n\n            <!-- 文件数据 -->\n            <a-row class=\"w-full flex flex-1 flex-col pl-3 overflow-x-auto overflow-y-hidden\">\n                <!-- 查询条件 -->\n                <a-row class=\"w-full\" v-if=\"showSearchRow\">\n                    <a-form :model=\"searchForm\" layout=\"inline\" label-align=\"left\" size=\"small\">\n                        <a-form-item field=\"fileName\" label=\"文件名称\">\n                            <a-input v-model=\"searchForm.fileName\" placeholder=\"文件名称\" />\n                        </a-form-item>\n                        <a-form-item field=\"status\" label=\"文件状态\">\n                            <a-select v-model=\"searchForm.status\" placeholder=\"文件状态\" allow-clear>\n                                <a-option v-for=\"(d, index) in dicts[proxy.DICT.commonNumberStatus]\" :key=\"index\" :value=\"d.dictKey\" :label=\"d.dictValue\" />\n                            </a-select>\n                        </a-form-item>\n                    </a-form>\n                    <a-row justify=\"center\" class=\"w-full mt-2\">\n                        <a-space>\n                            <a-button type=\"primary\" @click=\"getPageList(false)\">\n                                <template #icon><icon-search /></template>\n                                <template #default>查询</template>\n                            </a-button>\n                            <a-button @click=\"getPageList(true)\">\n                                <template #icon><icon-sync /></template>\n                                <template #default>重置</template>\n                            </a-button>\n                        </a-space>\n                    </a-row>\n                </a-row>\n\n                <!-- 分割线 -->\n                <a-divider v-if=\"showSearchRow\" class=\"mt-2\" />\n\n                <!-- 数据操作区 -->\n                <a-row class=\"w-full flex justify-between\">\n                    <a-space>\n                        <!-- 文件选择组件演示 - 可供其他页面调用 -->\n                        <a-button type=\"primary\" size=\"small\" @click=\"selectFileBtnClick()\">\n                            <template #default>文件选择组件演示</template>\n                        </a-button>\n                    </a-space>\n                    <a-space>\n                        <!-- 刷新 -->\n                        <a-button shape=\"circle\" size=\"small\" @click=\"getPageList(false)\">\n                            <template #icon><icon-refresh /></template>\n                        </a-button>\n                        <!-- 收缩/展开 -->\n                        <a-button shape=\"circle\" size=\"small\" @click=\"showSearchRow = !showSearchRow\">\n                            <template #icon>\n                                <icon-caret-up v-if=\"showSearchRow\" />\n                                <icon-caret-down v-else />\n                            </template>\n                        </a-button>\n                    </a-space>\n                </a-row>\n\n                <!-- 数据展示区 -->\n                <a-row class=\"w-full flex-1 mt-3 overflow-y-auto\">\n                    <a-table class=\"w-[100%]\" :scroll=\"{ minWidth: 600, y: '100%' }\" :columns=\"datatable.columns\" :data=\"datatable.records\" :loading=\"datatable.loading\" :pagination=\"false\" table-layout-fixed>\n                        <!-- 文件名称 -->\n                        <template #fileName=\"{ record }\">\n                            <a-tooltip :content=\"record.fileName\">\n                                <div class=\"w-[190px] flex items-center truncate\">\n                                    <!-- 文件类型图标 -->\n                                    <img v-if=\"record.fileType.includes('image')\" style=\"width: 20px\" src=\"../../../assets/file-icon/image.svg\" alt=\"\">\n                                    <img v-else style=\"width: 20px\" src=\"../../../assets/file-icon/unknown.svg\" alt=\"\">\n                                    <!-- 文件名称 -->\n                                    <span class=\"ml-2\">{{ record.fileName }}</span>\n                                </div>\n                            </a-tooltip>\n                        </template>\n                        <!-- 文件来源 -->\n                        <template #fileSource=\"{ record }\">\n                            <dict-convert :dict-data=\"dicts[proxy.DICT.fileSource]\" :dict-key=\"record.fileSource\" />\n                        </template>\n                        <!-- 存储方式 -->\n                        <template #storageId=\"{ record }\">\n                            <dict-convert :dict-data=\"dicts[proxy.DICT.storageList]\" :dict-key=\"record.storageId\" />\n                        </template>\n                        <!-- 文件状态 -->\n                        <template #status=\"{ record }\">\n                            <dict-convert :dict-data=\"dicts[proxy.DICT.commonNumberStatus]\" :dict-key=\"record.status\" />\n                        </template>\n                        <!-- 操作 -->\n                        <template #operation=\"{ record }\">\n                            <a-button v-perm=\"['basic:file:download']\" type=\"text\" size=\"mini\" @click=\"downloadBtnClick(record)\">\n                                <template #icon>\n                                    <icon-download />\n                                </template>\n                                <template #default>下载</template>\n                            </a-button>\n                            <a-popconfirm content=\"确认要删除吗?\" @ok=\"deleteBtnOkClick(record.fileId)\">\n                                <a-button v-perm=\"['basic:file:delete']\" type=\"text\" status=\"danger\" size=\"mini\">\n                                    <template #icon>\n                                        <icon-delete />\n                                    </template>\n                                    <template #default>删除</template>\n                                </a-button>\n                            </a-popconfirm>\n                        </template>\n                    </a-table>\n                </a-row>\n\n                <!-- 分页 -->\n                <a-row class=\"w-full flex justify-end mt-2\">\n                    <pagination v-if=\"datatable.total > 0\"\n                        v-model:page-num=\"searchForm.pageNum\"\n                        v-model:page-size=\"searchForm.pageSize\"\n                        :total=\"datatable.total\" @pagination=\"getPageList(false)\" />\n                </a-row>\n            </a-row>\n        </div>\n\n        <!-- 文件选择组件演示 -->\n        <a-modal v-model:visible=\"modal.visible\" width=\"70%\" :esc-to-close=\"false\" :mask-closable=\"false\" draggable :footer=\"false\">\n            <template #title>{{ modal.title }}</template>\n            <file-select :file-type=\"modal.fileType\" :limit=\"modal.limit\" @ok=\"onOk\" @cancel=\"onCancel\" v-if=\"modal.visible\" />\n        </a-modal>\n\n    </a-card>\n</template>\n\n<script setup>\nimport {ref, reactive, getCurrentInstance, shallowRef, onMounted} from 'vue'\nimport { getPageFileListApi, deleteFileApi } from \"~/api/file.js\";\nimport FileSelect from \"~/pages/basic/file/FileSelect.vue\";\n\n\n/********************** 文件选择组件演示 **********************/\nconst modal = reactive({\n    //是否显示\n    visible: false,\n    //模态框标题\n    title: '文件选择',\n    //文件类型\n    fileType: '',\n    //可以选择几个文件\n    limit: 1,\n});\n//模态框 -> 确认\nconst onOk = (selectedFiles) => {\n    modal.visible = false\n    //TODO 拿到选择的文件-可自定实现逻辑\n    console.info(selectedFiles)\n}\n//模态框 -> 取消\nconst onCancel = () => {\n    modal.visible = false\n}\n//点击选择\nconst selectFileBtnClick = (() => {\n    modal.visible = true\n    //modal.fileType = fileType.image.key\n})\n/********************** 文件选择组件演示结束 **********************/\n\n\n//全局实例\nconst {proxy} = getCurrentInstance()\n//加载字典\nconst dicts = proxy.LoadDicts([proxy.DICT.commonNumberStatus, proxy.DICT.fileSource, proxy.DICT.storageList])\n//是否展示搜索区域\nconst showSearchRow = ref(true)\n//搜索参数表单\nconst searchForm = reactive({\n    //文件来源\n    fileSource: null,\n    //文件名称\n    fileName: null,\n    //文件状态\n    status: null,\n    //页码\n    pageNum: 1,\n    //条数\n    pageSize: 10\n})\n//数据列表\nconst datatable = reactive({\n    //列配置\n    columns: [\n        {title: '文件名称', dataIndex: 'fileName', align: 'center', width: 250, ellipsis: true, tooltip: true},\n        {title: '文件大小', dataIndex: 'fileSize', align: 'center', width: 120, ellipsis: true, tooltip: true},\n        {title: '文件来源', dataIndex: 'fileSource', slotName: 'fileSource', align: 'center', width: 120, ellipsis: true, tooltip: true},\n        {title: '存储方式', dataIndex: 'storageId', slotName: 'storageId', align: 'center', width: 120, ellipsis: true, tooltip: true},\n        {title: '文件状态', dataIndex: 'status', slotName: 'status', align: 'center', width: 100, ellipsis: true, tooltip: true},\n        {title: '备注', dataIndex: 'remark', align: 'center', width: 200, ellipsis: true, tooltip: true},\n        {title: '操作', slotName: 'operation', align: 'center', width: 160, fixed: 'right'}\n    ],\n    //加载\n    loading: false,\n    //数据列表\n    records: [],\n    //总条数\n    total: 0\n})\n//查询数据列表\nconst getPageList = (isReset = false) => {\n    if (isReset) {\n        searchForm.fileName = null\n        searchForm.status = null\n        searchForm.pageNum = 1\n        searchForm.pageSize = 10\n    }\n    datatable.loading = true\n    getPageFileListApi(searchForm).then(res => {\n        datatable.records = res.records\n        datatable.total = res.total\n    }).finally(() => {\n        datatable.loading = false\n    })\n}\n//表格行数据 \"下载\" -> 点击\nconst downloadBtnClick = (record) => {\n    window.open(record.fileUrl, '_blank')\n}\n//表格行数据 \"删除\" -> 确认\nconst deleteBtnOkClick = (fileId) => {\n    deleteFileApi(fileId).then(() => {\n        proxy.$msg.success(proxy.operationType.delete.success)\n        //刷新列表\n        getPageList()\n    })\n}\n\n//文件来源 -> 点击\nconst fileSourceClick = (fileSource) => {\n    //fileSource = null，查询全部\n    searchForm.fileSource = fileSource\n    //查询数据列表\n    getPageList()\n}\n\n//查询数据列表\ngetPageList()\n</script>\n<style scoped>\n/* 文件来源选中 */\n.file-source-active {\n    background-color: var(--color-fill-1);color: rgb(var(--arcoblue-6))\n}\n</style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/file/FileSelect.vue",
    "content": "<template>\n    <a-spin class=\"w-[100%]\" :size=\"35\" :loading=\"spinLoading\" tip=\"正在处理, 请稍候...\">\n        <div class=\"w-full h-full flex justify-between file-select pr-3\">\n            <!-- 按文件来源将文件分类 -->\n            <a-row class=\"h-full border-r pr-3\">\n                <a-list class=\"w-[240px]\" size=\"small\" hoverable>\n                    <a-list-item class=\"cursor-pointer\" @click=\"fileSourceClick(null)\"\n                                 :class=\"searchForm.fileSource === null ? 'file-source-active' : ''\">全部文件</a-list-item>\n                    <a-list-item\n                            class=\"cursor-pointer\"\n                            @click=\"fileSourceClick(d.dictKey)\"\n                            :class=\"searchForm.fileSource === d.dictKey ? 'file-source-active' : ''\"\n                            v-for=\"(d, index) in dicts[proxy.DICT.fileSource]\"\n                            :key=\"index\">\n                        {{d.dictValue}}\n                    </a-list-item>\n                </a-list>\n            </a-row>\n\n            <!-- 文件数据 -->\n            <a-row class=\"w-full flex flex-1 flex-col pl-3 overflow-x-auto overflow-y-hidden\">\n\n                <!-- 文件名称搜索 -->\n                <a-space class=\"w-full flex justify-between mb-3 file-search-container\">\n                    <a-upload :action=\"uploadFileUrl\" multiple with-credentials\n                              :accept=\"accept\"\n                              :custom-request=\"customUploadFile\" :show-file-list=\"false\" />\n                    <a-input-search v-model=\"searchForm.fileName\" :style=\"{width:'180px'}\" placeholder=\"请输入文件名称\" search-button @search=\"getPageList\"/>\n                </a-space>\n\n                <!-- 文件列表 -->\n                <div class=\"parent-container\">\n                    <div class=\"image-container\" v-if=\"datatable.records.length > 0\">\n                        <div class=\"image-item\" v-for=\"file in datatable.records\">\n                            <div class=\"image-wrapper\">\n                                <!-- 图片 -->\n                                <img :src=\"file.fileThUrl\" alt=\"\" @click=\"selectFileBtnClick(file)\" v-if=\"fileAccept.img.includes(file.fileTypeSuffix)\">\n                                <!-- 视频 -->\n                                <div class=\"w-[100%] h-[100%] file-video\" :id=\"file.fileId\" @click.stop=\"selectFileBtnClick(file)\" v-else-if=\"fileAccept.video.includes(file.fileTypeSuffix)\"></div>\n                                <!-- 其他文件 -->\n                                <img class=\"p-8\" src=\"../../../assets/default-file-icon.png\" @click=\"selectFileBtnClick(file)\" alt=\"\" v-else>\n\n                                <!-- 选中效果 -->\n                                <div class=\"flex items-center justify-center\" v-if=\"checkSelect(file) >= 0\"\n                                     :class=\"checkSelect(file) >= 0 ? 'select-file-active' : ''\"\n                                     @click=\"selectFileBtnClick(file)\">\n                                    <icon-check class=\"text-6xl\" style=\"color: var(--color-text-4)\" />\n                                </div>\n                            </div>\n                            <a-tooltip :content=\"file.fileName\">\n                                <div class=\"image-title\">{{file.fileName}}</div>\n                            </a-tooltip>\n                        </div>\n                    </div>\n                    <a-empty class=\"mt-5\" v-else />\n                </div>\n\n                <!-- 分页 -->\n                <a-row class=\"w-full flex justify-end mt-5\">\n                    <pagination v-if=\"datatable.total > 0\"\n                                v-model:page-num=\"searchForm.pageNum\"\n                                v-model:page-size=\"searchForm.pageSize\"\n                                :total=\"datatable.total\" @pagination=\"getPageList()\" />\n                </a-row>\n            </a-row>\n\n        </div>\n\n        <!-- 分割线 -->\n        <a-divider />\n\n        <div class=\"flex justify-end\">\n            <a-space>\n                <a-button @click=\"cancelBtnClick()\">取消</a-button>\n                <a-button type=\"primary\" @click=\"okBtnClick()\">确定</a-button>\n            </a-space>\n        </div>\n    </a-spin>\n</template>\n<script setup>\nimport {getCurrentInstance, nextTick, reactive, ref, watch} from \"vue\";\nimport {getPageFileListApi, uploadFileApi} from \"~/api/file.js\";\nimport {status, fileType, fileAccept, videoTypeHandler} from \"~/utils/sys.js\";\nimport Player from 'xgplayer';\nimport 'xgplayer/dist/index.min.css';\n\n//全局实例\nconst {proxy} = getCurrentInstance()\n//加载字典\nconst dicts = proxy.LoadDicts([proxy.DICT.commonNumberStatus, proxy.DICT.fileSource])\n//允许上传的文件类型\nconst accept = ref('')\n//接收父组件参数\nconst props = defineProps({\n    //可以选择几个文件\n    limit: {\n        type: Number,\n        default: () => 1\n    },\n    //文件类型\n    fileType: {\n        type: String,\n        default: () => null\n    }\n})\n//加载中...\nconst spinLoading = ref(false)\n//搜索参数表单\nconst searchForm = reactive({\n    //文件来源\n    fileSource: null,\n    //文件类型\n    fileType: null,\n    //文件名称\n    fileName: null,\n    //文件状态 = 正常\n    status: null,\n    //页码\n    pageNum: 1,\n    //条数\n    pageSize: 10\n})\n//数据列表\nconst datatable = reactive({\n    //数据列表\n    records: [],\n    //总条数\n    total: 0\n})\n//文件来源 -> 点击\nconst fileSourceClick = (fileSource) => {\n    //fileSource = null，查询全部\n    searchForm.fileSource = fileSource\n    //查询数据列表\n    getPageList()\n}\n//查询数据列表\nconst getPageList = () => {\n    spinLoading.value = true\n    getPageFileListApi(searchForm).then(res => {\n        datatable.records = res.records\n        datatable.total = res.total\n        //渲染视频 - 需在dom加载后渲染\n        nextTick(() => {\n            renderVideo(datatable.records)\n        })\n    }).finally(() => {\n        spinLoading.value = false\n    })\n}\n\n//选中的文件\nconst selectFile = ref([])\n//点击选中\nconst selectFileBtnClick = (file) => {\n    //检查可以选择几个文件 - 默认一个\n    let count = props.limit\n    //检查之前是否选中\n    let selectIndex = checkSelect(file)\n    //之前已选中，再次点击说明要取消掉\n    if (selectIndex >= 0) {\n        selectFile.value.splice(selectIndex, 1)\n    } else {\n        //之前未选中，检查是否达到最大个数\n        if (selectFile.value.length < count) {\n            //未达到最大个数，直接选择\n            selectFile.value.push(file)\n        } else {\n            //达到最大个数，校验\n            if (count === 1) {\n                //最大个数=1，直接替换\n                selectFile.value = [file]\n            } else {\n                //最大个数 > 1，提示最多可以选择 count 个\n                proxy.$msg.error(`最多可以选择${count}个文件`)\n            }\n        }\n    }\n}\n//检查是否选中\nconst checkSelect = (file) => {\n    let selectIndex = -1\n    for (let i = 0;i < selectFile.value.length;i++) {\n        //之前已选中\n        if (selectFile.value[i].fileId === file.fileId) {\n            selectIndex = i\n            break\n        }\n    }\n    return selectIndex\n}\n//父组件函数\nconst emits = defineEmits(['ok', 'cancel'])\n//取消 -> 点击\nconst cancelBtnClick = () => {\n    emits('cancel')\n}\n//确定 -> 点击\nconst okBtnClick = () => {\n    emits(\"ok\", selectFile.value)\n}\n\n//上传文件地址\nconst uploadFileUrl = import.meta.env.VITE_UPLOAD_FILE_URL\n//自定义上传\nconst customUploadFile = (option) => {\n    //上传进度监控\n    const onUploadProgress = (e) => {\n        option.onProgress(e.progress * 100)\n    }\n    //上传参数\n    const formData = new FormData();\n    formData.append(\"file\", option.fileItem.file);\n    formData.append(\"fileSource\", -1);\n    if (props.storageId) {\n        formData.append(\"storageId\", props.storageId)\n    }\n    spinLoading.value = true\n    uploadFileApi(formData, onUploadProgress).then(res => {\n        //调用onSuccess方法将响应数据附加到fileItem中的response字段上\n        option.onSuccess(res)\n        proxy.$msg.success(proxy.operationType.upload.success)\n        //刷新文件列表\n        getPageList()\n    }).catch(e => {\n        //上传失败\n        option.onError(e)\n    }).finally(() => {\n        spinLoading.value = false\n    })\n}\n\n//渲染视频\nconst renderVideoCache = ref([])\nconst renderVideo = (data) => {\n    //渲染前销毁\n    for (const player of renderVideoCache.value) {\n        player.destroy()\n    }\n    renderVideoCache.value = []\n    //渲染视频\n    for (const file of data) {\n        if (fileAccept.video.includes(file.fileTypeSuffix)) {\n            let player = new Player({\n                id: file.fileId,\n                url: file.fileUrl,\n                height: '100%',\n                width: '100%',\n                //关闭双击播放器进入全屏\n                closeVideoDblclick: true,\n                //关闭单击播放器区域切换播放/暂停\n                closeVideoClick: true\n            })\n            renderVideoCache.value.push(player)\n        }\n    }\n}\n\n//监听参数变化\nwatch(() => props.fileType, (newVal, oldVal) => {\n    //文件类型\n    if (props.fileType) {\n        searchForm.fileType = props.fileType\n\n        //允许上传的文件类型\n        //图片文件\n        if (props.fileType === fileType.image.key) {\n            accept.value = fileAccept.img\n        }\n        //视频文件\n        else if (props.fileType === fileType.video.key) {\n            accept.value = fileAccept.video\n        }\n        //所有文件\n        else {\n            accept.value = ''\n        }\n    }\n    //加载文件列表\n    getPageList()\n}, { deep: true, immediate: true })\n</script>\n<style scoped>\n/* 父容器设置（示例） */\n.parent-container {\n    width: 100%;\n    min-height: 200px; /* 父容器需要明确高度 */\n    max-height: 700px;\n}\n/* 图片容器 */\n.image-container {\n    display: grid;\n    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));\n    gap: 15px;\n    height: 100%; /* 继承父容器高度 */\n    overflow-y: auto; /* 纵向滚动条 */\n    box-sizing: border-box;\n}\n/* 自定义滚动条样式 */\n.image-container::-webkit-scrollbar {\n    width: 8px;\n}\n.image-container::-webkit-scrollbar-track {\n    background: #f1f1f1;\n}\n.image-container::-webkit-scrollbar-thumb {\n    background: #888;\n    border-radius: 4px;\n}\n/* 图片项样式保持之前版本 */\n.image-item {\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    gap: 8px;\n    padding-bottom: 10px; /* 防止底部被裁切 */\n}\n.image-wrapper {\n    position: relative;\n    width: 100%;\n}\n.image-wrapper::before {\n    content: '';\n    display: block;\n    padding-top: 100%;\n}\n.image-wrapper img,.file-video {\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    object-fit: cover;\n    border-radius: 8px;\n    box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n    transition: transform 0.3s ease;\n}\n/* 可选悬停动画 */\n.image-wrapper img:hover {\n    cursor: pointer;\n}\n/* 可选悬停动画 */\n.image-wrapper .file-video:hover {\n    cursor: pointer;\n}\n.image-title {\n    width: 100%;\n    font-size: 14px;\n    color: #333;\n    text-align: center;\n    white-space: nowrap;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    padding: 0 5px;\n}\n/* 响应式调整 */\n@media (max-width: 768px) {\n    .image-container {\n        grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));\n    }\n    .parent-container {\n        height: 400px; /* 移动端调整高度 */\n    }\n    .image-title {\n        font-size: 12px;\n    }\n    .file-search-container {\n        display: none;\n    }\n}\n/* 点击选中文件 */\n.select-file-active {\n    width: 100%;\n    height: 100%;\n    cursor: pointer;\n    background: var(--color-text-1);\n    opacity: 0.7;\n    position: absolute;\n    z-index: 99;\n    top: 0;\n}\n/* 文件来源选中 */\n.file-select .file-source-active {\n    background-color: var(--color-fill-1);color: rgb(var(--arcoblue-6))\n}\n</style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/notice/NoticeDetail.vue",
    "content": "<template>\n    <a-spin class=\"w-[100%]\" :size=\"35\" :loading=\"spinLoading\" tip=\"正在处理, 请稍候...\">\n        <a-space size=\"medium\" direction=\"vertical\" fill>\n            <div class=\"flex items-center justify-center text-3xl\">{{ form.noticeTitle }}</div>\n            <div class=\"flex items-center justify-center\">\n                <a-space size=\"medium\">\n                    <template #split>\n                        <a-divider direction=\"vertical\" />\n                    </template>\n                    <span>创建人：<dict-convert :dict-data=\"dicts[proxy.DICT.userList]\" :dict-key=\"form.createId\" /></span>\n                    <span>创建时间：{{ form.createTime }}</span>\n                    <span>发布人：<dict-convert :dict-data=\"dicts[proxy.DICT.userList]\" :dict-key=\"form.publishAuthorId\" /></span>\n                    <span>发布部门：<dict-convert :dict-data=\"dicts[proxy.DICT.deptList]\" :dict-key=\"form.publishDeptId\" /></span>\n                    <span>发布时间：{{ form.publishTime }}</span>\n                </a-space>\n            </div>\n            <div class=\"flex items-center justify-center\">\n                <a-space size=\"medium\">\n                    <template #split>\n                        <a-divider direction=\"vertical\" />\n                    </template>\n                    <span>公告类型：<dict-convert :dict-data=\"dicts[proxy.DICT.noticeType]\" :dict-key=\"form.noticeType\" /></span>\n                    <span>排序：{{ form.noticeSort }}</span>\n                    <span>是否置顶：<dict-convert :dict-data=\"dicts[proxy.DICT.yesNo]\" :dict-key=\"form.noticeTop\" /></span>\n                    <span>公告状态：<dict-convert :dict-data=\"dicts[proxy.DICT.status]\" :dict-key=\"form.commonNumberStatus\" /></span>\n                    <span>是否外链：<dict-convert :dict-data=\"dicts[proxy.DICT.yesNo]\" :dict-key=\"form.noticeOutChain\" /></span>\n                    <span>外链URL：\n                        <template v-if=\"form.noticeLink\">\n                            <a-tooltip :content=\"form.noticeLink\">\n                                <a-link :href=\"form.noticeLink\">点击跳转</a-link>\n                            </a-tooltip>\n                        </template>\n                        <template v-else>无</template>\n                    </span>\n                </a-space>\n            </div>\n            <div class=\"flex items-center justify-center\">\n                封面图：\n                <template v-if=\"form.noticePicFile\">\n                    <a-image-preview-group infinite>\n                        <a-space>\n                            <a-image v-for=\"(image, index) in form.noticePicFile\" :key=\"index\" :src=\"image.fileUrl\" width=\"200\" class=\"cursor-pointer\" />\n                        </a-space>\n                    </a-image-preview-group>\n                </template>\n                <template v-else>无</template>\n            </div>\n            <div class=\"flex justify-center\">\n                <div class=\"w-[70%]\" v-html=\"form.noticeContent\"></div>\n            </div>\n        </a-space>\n    </a-spin>\n</template>\n\n<script setup>\nimport { ref, reactive, getCurrentInstance, watch } from 'vue'\nimport {getNoticeByNoticeIdApi} from \"~/api/notice.js\";\nimport 'tinymce/skins/ui/oxide/content.css'\nimport 'tinymce/skins/ui/oxide/skin.css'\n\n//全局实例\nconst { proxy } = getCurrentInstance()\n//加载字典\nconst dicts = proxy.LoadDicts([proxy.DICT.commonNumberStatus, proxy.DICT.noticeType, proxy.DICT.userList, proxy.DICT.deptList, proxy.DICT.yesNo])\n//加载中...\nconst spinLoading = ref(false)\n//接收父组件参数\nconst props = defineProps({\n    params: {\n        type: Object,\n        default: () => {}\n    }\n})\n//表单\nconst form = reactive({\n    //系统公告ID\n    noticeId: null,\n    //系统公告标题\n    noticeTitle: null,\n    //系统公告类型\n    noticeType: null,\n    //系统公告内容\n    noticeContent: null,\n    //公告封面图\n    noticePicFile: null,\n    //是否置顶\n    noticeTop: null,\n    //排序值\n    noticeSort: null,\n    //是否外链\n    noticeOutChain: null,\n    //外链URL\n    noticeLink: null,\n    //发布部门\n    publishDeptId: null,\n    //发布人\n    publishAuthorId: null,\n    //发布时间\n    publishTime: null,\n    //系统公告状态\n    status: null,\n    //创建人\n    createId: null,\n    //创建时间\n    createTime: null\n})\n//加载公告详细信息\nconst loadNoticeInfo = (noticeId) => {\n    spinLoading.value = true\n    getNoticeByNoticeIdApi(noticeId).then(res => {\n        //数据赋值\n        if (res) {\n            for (let key in res) {\n                if (form.hasOwnProperty(key)) {\n                    form[key] = res[key]\n                }\n            }\n        }\n    }).finally(() => {\n        spinLoading.value = false\n    })\n}\n//监听参数变化\nwatch(() => props.params, (newVal, oldVal) => {\n    //公告ID\n    if (props.params.noticeId) {\n        //查询数据\n        loadNoticeInfo(props.params.noticeId)\n    }\n}, { deep: true, immediate: true })\n</script>\n<style scoped></style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/notice/NoticeEdit.vue",
    "content": "<template>\n    <a-spin class=\"w-[100%]\" :size=\"35\" :loading=\"spinLoading\" tip=\"正在处理, 请稍候...\">\n        <a-form :model=\"form\" ref=\"formRef\" :rules=\"rules\" auto-label-width>\n            <div class=\"flex justify-between\" style=\"flex-wrap: wrap;\">\n                <a-form-item class=\"w-[100%]\" field=\"noticeTitle\" label=\"公告标题\" required>\n                    <a-input v-model=\"form.noticeTitle\" placeholder=\"公告标题\" />\n                </a-form-item>\n                <a-form-item class=\"w-[33%]\" field=\"noticeType\" label=\"公告类型\" required>\n                    <a-select v-model=\"form.noticeType\" placeholder=\"公告类型\" allow-clear>\n                        <a-option v-for=\"(d, index) in dicts[proxy.DICT.noticeType]\" :key=\"index\" :value=\"d.dictKey\" :label=\"d.dictValue\" />\n                    </a-select>\n                </a-form-item>\n                <a-form-item class=\"w-[33%]\" field=\"publishAuthorId\" label=\"发布人\" required>\n                    <a-select v-model=\"form.publishAuthorId\" placeholder=\"发布人\" allow-clear allow-search>\n                        <a-option v-for=\"(d, index) in dicts[proxy.DICT.userList]\" :key=\"index\" :value=\"String(d.dictKey)\" :label=\"d.dictValue\" />\n                    </a-select>\n                </a-form-item>\n                <a-form-item class=\"w-[33%]\" field=\"publishDeptId\" label=\"发布部门\" tooltip=\"表示发布此公告的部门，可为空\">\n                    <a-tree-select v-model=\"form.publishDeptId\" :data=\"deptTree\" placeholder=\"发布部门\" allow-clear\n                                   :fieldNames=\"{key: 'deptId', title: 'deptName', children: 'children'}\" />\n                </a-form-item>\n                <a-form-item class=\"w-[33%]\" field=\"noticeTop\" label=\"是否置顶\" tooltip=\"置顶会让此公告展示在最前面\">\n                    <a-radio-group v-model=\"form.noticeTop\" type=\"button\">\n                        <a-radio v-for=\"(item, index) in dicts[proxy.DICT.yesNo]\" :key=\"index\" :value=\"item.dictKey\">{{ item.dictValue }}</a-radio>\n                    </a-radio-group>\n                </a-form-item>\n                <a-form-item class=\"w-[33%]\" field=\"noticeOutChain\" label=\"是否外链\" tooltip=\"表示此公告是一个外部链接，展示时点击会打开新页面跳转\">\n                    <a-radio-group v-model=\"form.noticeOutChain\" type=\"button\">\n                        <a-radio v-for=\"(item, index) in dicts[proxy.DICT.yesNo]\" :key=\"index\" :value=\"item.dictKey\">{{ item.dictValue }}</a-radio>\n                    </a-radio-group>\n                </a-form-item>\n                <a-form-item class=\"w-[33%]\" field=\"noticeLink\" label=\"外链URL\" tooltip=\"`是否外链`选择`是`，跳转的URL\">\n                    <a-input v-model=\"form.noticeLink\" placeholder=\"外部链接URL\" />\n                </a-form-item>\n                <a-form-item class=\"w-[33%]\" field=\"noticeSort\" label=\"排序值\" tooltip=\"公告的展示顺序，数值越小排序越靠前\">\n                    <a-input-number :min=\"0\" v-model=\"form.noticeSort\" placeholder=\"排序值\" />\n                </a-form-item>\n                <a-form-item class=\"w-[33%]\" field=\"noticeTimeInterval\" label=\"延期发布\" tooltip=\"表示想让此公告在何时发布，比如选择明天10点，则此公告将在明天10点发布。不选择将立即发布\">\n                    <a-date-picker class=\"w-[100%]\" v-model=\"form.noticeTimeInterval\" show-time format=\"YYYY-MM-DD HH:mm:ss\" disabled-input placeholder=\"延期发布时间\" />\n                </a-form-item>\n                <a-form-item class=\"w-[33%]\" field=\"status\" label=\"公告状态\" required>\n                    <a-select v-model=\"form.status\" placeholder=\"公告状态\" allow-clear>\n                        <a-option v-for=\"(d, index) in dicts[proxy.DICT.commonNumberStatus]\" :key=\"index\" :value=\"d.dictKey\" :label=\"d.dictValue\" />\n                    </a-select>\n                </a-form-item>\n                <a-form-item class=\"w-[100%]\" field=\"noticePicFile\" label=\"公告封面图\">\n                    <!-- 图片列表容器 -->\n                    <div class=\"image-grid\">\n                        <!-- 图片项 -->\n                        <div v-for=\"(img, index) in form.noticePicFile\" :key=\"index\" class=\"image-item\" v-if=\"form.noticePicFile.length > 0\">\n                            <!-- 图片 -->\n                            <img :src=\"img.fileThUrl\" :alt=\"img.fileName\" class=\"image-content\"  />\n\n                            <div class=\"action-buttons\">\n                                <a-space>\n                                    <!-- 预览 -->\n                                    <div class=\"icon-btn\" @click=\"img.visible = true\">\n                                        <span><icon-zoom-in /></span>\n                                        <a-image-preview :src=\"img.fileUrl\" v-model:visible=\"img.visible\" />\n                                    </div>\n                                    <!-- 删除 -->\n                                    <a-popconfirm content=\"确认要删除吗?\" @ok=\"deleteNoticePic(index)\">\n                                        <div class=\"icon-btn\">\n                                            <span><icon-delete /></span>\n                                        </div>\n                                    </a-popconfirm>\n                                </a-space>\n                            </div>\n                        </div>\n\n                        <!-- 上传按钮 -->\n                        <div class=\"upload-item\" @click=\"noticePicSelectVisible = true\" v-if=\"form.noticePicFile.length < 3\">\n                            <div class=\"upload-btn\">\n                                <div class=\"upload-content\">\n                                    <icon-plus class=\"upload-icon\" />\n                                    <span>上传</span>\n                                </div>\n                            </div>\n                        </div>\n                    </div>\n                    <!-- 封面图选择 -->\n                    <a-modal v-model:visible=\"noticePicSelectVisible\" width=\"60%\" title=\"选择封面图\" :esc-to-close=\"false\" :mask-closable=\"false\" draggable :footer=\"false\">\n                        <file-select @ok=\"noticePicSelectOk\" @cancel=\"noticePicSelectCancel\" :file-type=\"fileType.image.key\" :limit=\"10\" v-if=\"noticePicSelectVisible\" />\n                    </a-modal>\n                </a-form-item>\n                <a-form-item class=\"w-[100%]\" field=\"noticeContent\" label=\"公告内容\" required>\n                    <tinymce-editor :editorHeight=\"450\" :file-source=\"fileSource.notice_content_img.key\" ref=\"editorRef\" />\n                </a-form-item>\n            </div>\n        </a-form>\n\n        <!-- 分割线 -->\n        <a-divider class=\"mt-0\" />\n\n        <div class=\"flex justify-end\">\n            <a-space>\n                <a-button @click=\"cancelBtnClick()\">取消</a-button>\n                <a-button type=\"primary\" @click=\"okBtnClick()\">确定</a-button>\n            </a-space>\n        </div>\n    </a-spin>\n</template>\n\n<script setup>\nimport { ref, reactive, getCurrentInstance, watch } from 'vue'\nimport { addNoticeApi, updateNoticeByNoticeIdApi, getNoticeByNoticeIdApi } from \"~/api/notice.js\";\nimport { getDeptListApi } from \"~/api/dept.js\";\nimport TinymceEditor from '~/components/tinymceEditor/index.vue'\nimport {fileSource, fileType} from \"~/utils/sys.js\";\nimport FileSelect from \"~/pages/basic/file/FileSelect.vue\";\n\n//全局实例\nconst {proxy} = getCurrentInstance()\n//加载字典\nconst dicts = proxy.LoadDicts([proxy.DICT.commonNumberStatus, proxy.DICT.noticeType, proxy.DICT.yesNo, proxy.DICT.userList])\n//接收父组件参数\nconst props = defineProps({\n    params: {\n        type: Object,\n        default: () => {}\n    }\n})\n//父组件函数\nconst emits = defineEmits(['ok', 'cancel'])\n//加载中...\nconst spinLoading = ref(false)\n//表单ref\nconst formRef = ref(null)\n//表单\nconst form = reactive({\n    //系统公告ID\n    noticeId: null,\n    //系统公告标题\n    noticeTitle: null,\n    //系统公告类型\n    noticeType: null,\n    //系统公告内容\n    noticeContent: null,\n    //公告封面图文件\n    noticePicFile: [],\n    //是否置顶 -> 默认否\n    noticeTop: proxy.yesNo.no.key,\n    //延期发布时间\n    noticeTimeInterval: null,\n    //排序值\n    noticeSort: null,\n    //是否外链 -> 默认否\n    noticeOutChain: proxy.yesNo.no.key,\n    //外链URL\n    noticeLink: null,\n    //发布部门\n    publishDeptId: null,\n    //发布人\n    publishAuthorId: null,\n    //系统公告状态\n    status: null\n})\n//表单校验规则\nconst rules = {\n    noticeTitle: [{required: true, message: '系统公告标题不能为空', trigger: 'submit'}],\n    noticeType: [{required: true, message: '系统公告类型不能为空', trigger: 'submit'}],\n    noticeContent: [{required: true, message: '系统公告内容不能为空', trigger: 'submit'}],\n    publishAuthorId: [{required: true, message: '发布人不能为空', trigger: 'submit'}],\n    status: [{required: true, message: '系统公告状态不能为空', trigger: 'submit'}]\n}\n\n\n/************************ 封面图开始 ***************************/\n//封面图选择\nconst noticePicSelectVisible = ref(false)\n//封面图选择 - 确定\nconst noticePicSelectOk = (selectedFiles) => {\n    //未选择 - 跳过\n    if (!selectedFiles || selectedFiles.length <= 0) {\n        return;\n    }\n    //检查不能超过3张\n    if (form.noticePicFile.length + selectedFiles.length > 3) {\n        proxy.$msg.error('封面图最多选择3张，请检查')\n        return;\n    }\n\n    //满足条件，将选择的图片与之前选择的图片合并\n    const existingIds = new Set(form.noticePicFile.map(item => item.fileId))\n    //合并非重复项\n    selectedFiles.forEach(item => {\n        if (!existingIds.has(item.fileId)) {\n            form.noticePicFile.push(item)\n            //更新集合以避免后续重复\n            existingIds.add(item.fileId)\n        }\n    })\n\n    //隐藏模态框\n    noticePicSelectVisible.value = false\n}\n//封面图选择 - 取消\nconst noticePicSelectCancel = () => {\n    noticePicSelectVisible.value = false\n}\n//封面图选择 - 删除\nconst deleteNoticePic = (index) => {\n    //从已选列表中剔除\n    form.noticePicFile.splice(index, 1)\n}\n/************************ 封面图结束 ***************************/\n\n\n//编辑器ref\nconst editorRef = ref(null)\n//封面上传uploadRef\nconst uploadRef = ref(null)\n//确定 -> 点击\nconst okBtnClick = () => {\n    //获取富文本数据\n    form.noticeContent = editorRef.value.getEditorContent()\n    //表单验证\n    formRef.value.validate((valid) => {\n        if (valid) {return false}\n        //添加\n        if (props.params.operationType === proxy.operationType.add.type) {\n            spinLoading.value = true\n            addNoticeApi(form).then(() => {\n                proxy.$msg.success(proxy.operationType.add.success)\n                emits('ok')\n            }).finally(() => {\n                spinLoading.value = false\n            })\n        }\n        //修改\n        if (props.params.operationType === proxy.operationType.update.type) {\n            spinLoading.value = true\n            updateNoticeByNoticeIdApi(form).then(() => {\n                proxy.$msg.success(proxy.operationType.update.success)\n                emits('ok')\n            }).finally(() => {\n                spinLoading.value = false\n            })\n        }\n    })\n}\n//取消 -> 点击\nconst cancelBtnClick = () => {\n    emits('cancel')\n}\n//加载公告详细信息\nconst loadNoticeInfo = (noticeId) => {\n    spinLoading.value = true\n    getNoticeByNoticeIdApi(noticeId).then(res => {\n        console.info('res ', res)\n        //数据赋值\n        if (res) {\n            for (let key in res) {\n                if (form.hasOwnProperty(key)) {\n                    form[key] = res[key]\n                }\n            }\n            //富文本内容\n            editorRef.value.setEditorContent(res.noticeContent)\n        }\n    }).finally(() => {\n        spinLoading.value = false\n    })\n}\n//部门树数据\nconst deptTree = ref([])\n//获取部门数据列表\nconst getDeptTree = () => {\n    getDeptListApi({}).then(res => {\n        deptTree.value = res\n    })\n}\n//监听参数变化\nwatch(() => props.params, (newVal, oldVal) => {\n    //公告ID\n    if (props.params.noticeId) {\n        //加载公告信息\n        loadNoticeInfo(props.params.noticeId)\n    }\n    //加载部门树\n    getDeptTree()\n}, { deep: true, immediate: true })\n</script>\n<style scoped></style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/notice/NoticeMgt.vue",
    "content": "<template>\n    <a-card class=\"p-0\" :body-style=\"{height: 'calc(100vh - 125px)'}\">\n\n        <!-- 数据列表 -->\n        <a-row class=\"w-full h-full flex flex-col overflow-x-auto overflow-y-hidden\">\n            <!-- 查询条件 -->\n            <a-row class=\"w-full\" v-if=\"showSearchRow\">\n                <a-form :model=\"searchForm\" layout=\"inline\" label-align=\"left\" size=\"small\">\n                    <a-form-item field=\"noticeTitle\" label=\"公告标题\">\n                        <a-input v-model=\"searchForm.noticeTitle\" placeholder=\"公告标题\" />\n                    </a-form-item>\n                    <a-form-item field=\"noticeType\" label=\"公告类型\">\n                        <a-select v-model=\"searchForm.noticeType\" placeholder=\"公告类型\" allow-clear>\n                            <a-option v-for=\"(d, index) in dicts[proxy.DICT.noticeType]\" :key=\"index\" :value=\"d.dictKey\" :label=\"d.dictValue\" />\n                        </a-select>\n                    </a-form-item>\n                    <a-form-item field=\"status\" label=\"公告状态\">\n                        <a-select v-model=\"searchForm.status\" placeholder=\"公告状态\" allow-clear>\n                            <a-option v-for=\"(d, index) in dicts[proxy.DICT.commonNumberStatus]\" :key=\"index\" :value=\"d.dictKey\" :label=\"d.dictValue\" />\n                        </a-select>\n                    </a-form-item>\n                </a-form>\n                <a-row justify=\"center\" class=\"w-full mt-2\">\n                    <a-space>\n                        <a-button type=\"primary\" @click=\"getPageList(false)\">\n                            <template #icon><icon-search /></template>\n                            <template #default>查询</template>\n                        </a-button>\n                        <a-button @click=\"getPageList(true)\">\n                            <template #icon><icon-sync /></template>\n                            <template #default>重置</template>\n                        </a-button>\n                    </a-space>\n                </a-row>\n            </a-row>\n\n            <!-- 分割线 -->\n            <a-divider v-if=\"showSearchRow\" class=\"mt-2\" />\n\n            <!-- 数据操作区 -->\n            <a-row class=\"w-full flex justify-between\">\n                <a-space>\n                    <!-- 添加 -->\n                    <a-button v-perm=\"['basic:notice:add']\" type=\"primary\" size=\"small\" @click=\"addBtnClick()\">\n                        <template #icon><icon-plus /></template>\n                        <template #default>添加</template>\n                    </a-button>\n                </a-space>\n                <a-space>\n                    <!-- 刷新 -->\n                    <a-button shape=\"circle\" size=\"small\" @click=\"getPageList(false)\">\n                        <template #icon><icon-refresh /></template>\n                    </a-button>\n                    <!-- 收缩/展开 -->\n                    <a-button shape=\"circle\" size=\"small\" @click=\"showSearchRow = !showSearchRow\">\n                        <template #icon>\n                            <icon-caret-up v-if=\"showSearchRow\" />\n                            <icon-caret-down v-else />\n                        </template>\n                    </a-button>\n                </a-space>\n            </a-row>\n\n            <!-- 数据展示区 -->\n            <a-row class=\"w-full flex-1 mt-3 overflow-y-auto\">\n                <a-table class=\"w-[100%]\" :scroll=\"{ minWidth: 600, y: '100%' }\" :columns=\"datatable.columns\" :data=\"datatable.records\" :loading=\"datatable.loading\" :pagination=\"false\" table-layout-fixed>\n                    <!-- 公告标题 -->\n                    <template #noticeTitle=\"{ record }\">\n                        <a-link @click=\"detailBtnClick(record.noticeId)\" icon>{{ record.noticeTitle }}</a-link>\n                    </template>\n                    <!-- 公告类型 -->\n                    <template #noticeType=\"{ record }\">\n                        <dict-convert :dict-data=\"dicts[proxy.DICT.noticeType]\" :dict-key=\"record.noticeType\" />\n                    </template>\n                    <!-- 发布人 -->\n                    <template #publishAuthorId=\"{ record }\">\n                        <dict-convert :dict-data=\"dicts[proxy.DICT.userList]\" :dict-key=\"record.publishAuthorId\" />\n                    </template>\n                    <!-- 发布部门 -->\n                    <template #publishDeptId=\"{ record }\">\n                        <dict-convert :dict-data=\"dicts[proxy.DICT.deptList]\" :dict-key=\"record.publishDeptId\" />\n                    </template>\n                    <!-- 封面图 -->\n                    <template #noticePicFile=\"{ record }\">\n                        <template v-if=\"record.noticePicFile && record.noticePicFile.length > 0\">\n                            <!-- 多图轮播 -->\n                            <a-carousel :style=\"{width: '100%', height: '100px'}\" :indicator-type=\"'never'\">\n                                <a-carousel-item v-for=\"picFile in record.noticePicFile\">\n                                    <a-image class=\"cursor-pointer\"\n                                             style=\"transform: translateY(-20%)\"\n                                             :src=\"picFile.fileThUrl\"\n                                            :preview-props=\"{\n                                                     src: picFile.fileUrl\n                                            }\"\n                                    />\n                                </a-carousel-item>\n                            </a-carousel>\n                        </template>\n                    </template>\n                    <!-- 是否置顶 -->\n                    <template #noticeTop=\"{ record }\">\n                        <dict-convert :dict-data=\"dicts[proxy.DICT.yesNo]\" :dict-key=\"record.noticeTop\" />\n                    </template>\n                    <!-- 是否外链 -->\n                    <template #noticeOutChain=\"{ record }\">\n                        <dict-convert :dict-data=\"dicts[proxy.DICT.yesNo]\" :dict-key=\"record.noticeOutChain\" />\n                    </template>\n                    <!-- 公告状态 -->\n                    <template #status=\"{ record }\">\n                        <dict-convert :dict-data=\"dicts[proxy.DICT.commonNumberStatus]\" :dict-key=\"record.status\" />\n                    </template>\n                    <!-- 创建人 -->\n                    <template #createId=\"{ record }\">\n                        <dict-convert :dict-data=\"dicts[proxy.DICT.userList]\" :dict-key=\"record.createId\" />\n                    </template>\n                    <!-- 操作 -->\n                    <template #operation=\"{ record }\">\n                        <a-button v-perm=\"['basic:notice:update']\" type=\"text\" size=\"mini\" @click=\"updateBtnClick(record.noticeId)\">\n                            <template #icon>\n                                <icon-edit />\n                            </template>\n                            <template #default>修改</template>\n                        </a-button>\n                        <a-popconfirm content=\"确认要删除吗?\" @ok=\"deleteBtnOkClick(record.noticeId)\">\n                            <a-button v-perm=\"['basic:notice:delete']\" type=\"text\" status=\"danger\" size=\"mini\">\n                                <template #icon>\n                                    <icon-delete />\n                                </template>\n                                <template #default>删除</template>\n                            </a-button>\n                        </a-popconfirm>\n                    </template>\n                </a-table>\n            </a-row>\n\n            <!-- 分页 -->\n            <a-row class=\"w-full flex justify-end mt-2\">\n                <pagination v-if=\"datatable.total > 0\"\n                            v-model:page-num=\"searchForm.pageNum\"\n                            v-model:page-size=\"searchForm.pageSize\"\n                            :total=\"datatable.total\" @pagination=\"getPageList(false)\" />\n            </a-row>\n        </a-row>\n\n        <!-- 添加/修改 -->\n        <a-modal v-model:visible=\"modal.visible\" fullscreen :esc-to-close=\"false\" :mask-closable=\"false\" draggable :footer=\"false\">\n            <template #title>{{ modal.title }}</template>\n            <component :is=\"modal.component\" :params=\"modal.params\" @ok=\"onOk\" @cancel=\"onCancel\" v-if=\"modal.visible\" />\n        </a-modal>\n\n    </a-card>\n</template>\n\n<script setup>\nimport {ref, reactive, getCurrentInstance, shallowRef} from 'vue'\nimport { getPageNoticeListApi, deleteNoticeByNoticeIdApi } from \"~/api/notice.js\";\nimport NoticeEdit from \"~/pages/basic/notice/NoticeEdit.vue\";\nimport NoticeDetail from \"~/pages/basic/notice/NoticeDetail.vue\";\n//全局实例\nconst {proxy} = getCurrentInstance()\n//加载字典\nconst dicts = proxy.LoadDicts([proxy.DICT.commonNumberStatus, proxy.DICT.noticeType, proxy.DICT.userList, proxy.DICT.deptList, proxy.DICT.yesNo])\n//是否展示搜索区域\nconst showSearchRow = ref(true)\n//搜索参数表单\nconst searchForm = reactive({\n    //公告标题\n    noticeTitle: null,\n    //公告类型\n    noticeType: null,\n    //公告状态\n    status: null,\n    //页码\n    pageNum: 1,\n    //条数\n    pageSize: 10\n})\n//数据列表\nconst datatable = reactive({\n    //列配置\n    columns: [\n        {title: '公告标题', dataIndex: 'noticeTitle', slotName: 'noticeTitle', align: 'center', width: 220, ellipsis: true, tooltip: true},\n        {title: '公告类型', dataIndex: 'noticeType', slotName: 'noticeType', align: 'center', width: 100},\n        {title: '发布人', dataIndex: 'publishAuthorId', slotName: 'publishAuthorId', align: 'center', width: 100},\n        {title: '发布部门', dataIndex: 'publishDeptId', slotName: 'publishDeptId', align: 'center', width: 150, ellipsis: true, tooltip: true},\n        {title: '封面图', dataIndex: 'noticePicFile', slotName: 'noticePicFile', align: 'center', width: 200},\n        {title: '是否置顶', dataIndex: 'noticeTop', slotName: 'noticeTop', align: 'center', width: 90},\n        {title: '排序', dataIndex: 'noticeSort', align: 'center', width: 90},\n        {title: '是否外链', dataIndex: 'noticeOutChain', slotName: 'noticeOutChain', align: 'center', width: 90},\n        {title: '公告状态', dataIndex: 'status', slotName: 'status', align: 'center', width: 90},\n        {title: '创建人', dataIndex: 'createId', slotName: 'createId', align: 'center', width: 100},\n        {title: '创建时间', dataIndex: 'createTime', align: 'center', width: 170},\n        {title: '发布时间', dataIndex: 'publishTime', align: 'center', width: 170},\n        {title: '操作', slotName: 'operation', align: 'center', width: 160, fixed: 'right'}\n    ],\n    //加载\n    loading: false,\n    //数据列表\n    records: [],\n    //总条数\n    total: 0\n})\n//查询数据列表\nconst getPageList = (isReset = false) => {\n    if (isReset) {\n        searchForm.noticeTitle = null\n        searchForm.noticeType = null\n        searchForm.status = null\n        searchForm.pageNum = 1\n        searchForm.pageSize = 10\n    }\n    datatable.loading = true\n    getPageNoticeListApi(searchForm).then(res => {\n        datatable.records = res.records\n        datatable.total = res.total\n    }).finally(() => {\n        datatable.loading = false\n    })\n}\n//公共模态框\nconst modal = reactive({\n    //是否显示\n    visible: false,\n    //模态框标题\n    title: '系统公告',\n    //传递参数\n    params: {},\n    //组件名称\n    component: null\n});\n//添加按钮 -> 点击事件\nconst addBtnClick = () => {\n    modal.visible = true\n    modal.title = '添加系统公告'\n    modal.params = { operationType: proxy.operationType.add.type }\n    modal.component = shallowRef(NoticeEdit)\n}\n//表格行数据 \"修改\" -> 点击\nconst updateBtnClick = (noticeId) => {\n    modal.visible = true\n    modal.title = '修改系统公告'\n    modal.params = { operationType: proxy.operationType.update.type, noticeId: noticeId }\n    modal.component = shallowRef(NoticeEdit)\n}\n//表格行数据 \"查看\" -> 点击\nconst detailBtnClick = (noticeId) => {\n    modal.visible = true\n    modal.title = '系统公告详细信息'\n    modal.params = { operationType: proxy.operationType.detail.type, noticeId: noticeId }\n    modal.component = shallowRef(NoticeDetail)\n}\n//表格行数据 \"删除\" -> 确认\nconst deleteBtnOkClick = (noticeId) => {\n    deleteNoticeByNoticeIdApi(noticeId).then(() => {\n        proxy.$msg.success(proxy.operationType.delete.success)\n        //刷新列表\n        getPageList()\n    })\n}\n//模态框 -> 确认\nconst onOk = () => {\n    modal.visible = false\n    //查询数据列表\n    getPageList()\n}\n//模态框 -> 取消\nconst onCancel = () => {\n    modal.visible = false\n}\n\n//查询数据列表\ngetPageList()\n</script>\n<style scoped></style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/perm/PermDetail.vue",
    "content": "<template>\n    <a-spin class=\"w-[100%]\" :size=\"35\" :loading=\"spinLoading\" tip=\"正在处理, 请稍候...\">\n        <a-descriptions :column=\"2\" bordered>\n            <a-descriptions-item label=\"权限名称\">{{ form.permName }}</a-descriptions-item>\n            <a-descriptions-item label=\"权限编码\">{{ form.permCode }}</a-descriptions-item>\n            <a-descriptions-item label=\"权限类型\">\n                <dict-convert :dict-data=\"dicts[proxy.DICT.permType]\" :dict-key=\"form.permType\" />\n            </a-descriptions-item>\n            <a-descriptions-item label=\"权限图标\">\n                <functional-icons :icon=\"form.permIcon\" size=\"30\"></functional-icons>\n            </a-descriptions-item>\n            <a-descriptions-item label=\"路由地址\">{{ form.permPath }}</a-descriptions-item>\n            <a-descriptions-item label=\"权限状态\">\n                <dict-convert :dict-data=\"dicts[proxy.DICT.commonNumberStatus]\" :dict-key=\"form.status\" />\n            </a-descriptions-item>\n            <a-descriptions-item label=\"是否可见\">\n                <dict-convert :dict-data=\"dicts[proxy.DICT.yesNo]\" :dict-key=\"form.visible\" />\n            </a-descriptions-item>\n            <a-descriptions-item label=\"备注\">{{ form.remark }}</a-descriptions-item>\n        </a-descriptions>\n    </a-spin>\n</template>\n\n<script setup>\nimport { ref, reactive, getCurrentInstance, watch } from 'vue'\nimport {getPermByPermIdApi} from \"~/api/perm.js\";\nimport FunctionalIcons from \"~/components/iconSelect/FunctionalIcons.vue\";\n\n//全局实例\nconst { proxy } = getCurrentInstance()\n//加载字典\nconst dicts = proxy.LoadDicts([proxy.DICT.commonNumberStatus, proxy.DICT.yesNo, proxy.DICT.permType])\n//接收父组件参数\nconst props = defineProps({\n    params: {\n        type: Object,\n        default: () => {}\n    }\n})\n//加载中...\nconst spinLoading = ref(false)\n//表单\nconst form = reactive({\n    //权限ID\n    permId: null,\n    //权限编码\n    permCode: null,\n    //权限名称\n    permName: null,\n    //路由地址\n    permPath: null,\n    //权限图标\n    permIcon: null,\n    //权限类型\n    permType: null,\n    //组件路径\n    component: null,\n    //是否可见\n    visible: null,\n    //备注\n    remark: null,\n    //权限状态\n    status: null,\n})\n//加载权限数据\nconst loadPermInfo = (permId) => {\n    spinLoading.value = true\n    getPermByPermIdApi(permId).then(res => {\n        if (res) {\n            //数据赋值\n            for (let key in res) {\n                if (form.hasOwnProperty(key)) {\n                    form[key] = res[key]\n                }\n            }\n        }\n    }).finally(() => {\n        spinLoading.value = false\n    })\n}\n//监听参数变化\nwatch(() => props.params, (newVal, oldVal) => {\n    //权限ID\n    if (props.params.permId) {\n        //查询数据\n        loadPermInfo(props.params.permId)\n    }\n}, { deep: true, immediate: true })\n</script>\n<style scoped></style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/perm/PermEdit.vue",
    "content": "<template>\n    <a-spin class=\"w-[100%]\" :size=\"35\" :loading=\"spinLoading\" tip=\"正在处理, 请稍候...\">\n        <a-form :model=\"form\" ref=\"formRef\" layout=\"vertical\" :rules=\"rules\" auto-label-width>\n            <div class=\"flex justify-between\" style=\"flex-wrap: wrap;\">\n                <a-form-item class=\"w-[100%]\" field=\"permType\" label=\"权限类型\" required tooltip=\"菜单表示系统中左侧展示的菜单；按钮表示访问后端接口的权限。\">\n                    <a-radio-group v-model=\"form.permType\" type=\"button\">\n                        <a-radio v-for=\"(item, index) in dicts[proxy.DICT.permType]\" :key=\"index\" :value=\"item.dictKey\">{{ item.dictValue }}</a-radio>\n                    </a-radio-group>\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" field=\"parentPermId\" :label=\"'上级' + formName\" tooltip=\"可为空，为空表示顶级\">\n                    <a-tree-select v-model=\"form.parentPermId\" :data=\"permTree\" :placeholder=\"'上级' + formName\" allow-clear\n                                   :fieldNames=\"{key: 'permId', title: 'permName', children: 'children'}\" />\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" v-if=\"form.permType !== permType.B.key\" field=\"permIcon\" label=\"菜单图标\">\n                    <icon-select v-model=\"form.permIcon\" />\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" field=\"permName\" :label=\"formName + '名称'\" required>\n                    <a-input v-model=\"form.permName\" :placeholder=\"formName + '名称'\" />\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" field=\"permCode\" label=\"权限编码\" tooltip=\"Controller接口中定义的权限字符，如：`system:user:list`)\">\n                    <a-input v-model=\"form.permCode\" placeholder=\"权限编码\" />\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" field=\"permSort\" label=\"排序值\" required tooltip=\"展示顺序，按照数值升序排序\">\n                    <a-input-number :min=\"0\" v-model=\"form.permSort\" placeholder=\"排序值\" />\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" v-if=\"form.permType !== permType.B.key\" field=\"permPath\" label=\"路由地址\" tooltip=\"访问的url，会出现在浏览器的地址栏中，如：`/basic/user/mgt`；若访问外部链接需以`http或https`开头，如：`https://www.baidu.com`\">\n                    <a-input v-model=\"form.permPath\" placeholder=\"路由地址\" />\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" v-if=\"form.permType !== permType.B.key\" field=\"visible\" label=\"是否可见\" tooltip=\"选择否(隐藏)，将不会出现在侧边栏，但仍然可以访问\">\n                    <a-radio-group v-model=\"form.visible\" type=\"button\">\n                        <a-radio v-for=\"(item, index) in dicts[proxy.DICT.yesNo]\" :key=\"index\" :value=\"item.dictKey\">{{ item.dictValue }}</a-radio>\n                    </a-radio-group>\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" field=\"status\" :label=\"formName + '状态'\" required\n                    tooltip=\"选择禁用，将不会出现在侧边栏，也不能被访问\" v-if=\"props.params.operationType === proxy.operationType.update.type\">\n                    <a-select v-model=\"form.status\" :placeholder=\"formName + '状态'\" allow-clear>\n                        <a-option v-for=\"(d, index) in dicts[proxy.DICT.commonNumberStatus]\" :key=\"index\" :value=\"d.dictKey\" :label=\"d.dictValue\" />\n                    </a-select>\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" v-if=\"form.permType === permType.M.key\" field=\"component\" label=\"组件路径\" tooltip=\"vue文件的路径，文件需要放到pages目录下，如：`/basic/user/UserMgt.vue`\">\n                    <a-input v-model=\"form.component\" placeholder=\"组件路径\" />\n                </a-form-item>\n                <a-form-item class=\"w-[100%]\" field=\"remark\" label=\"备注\">\n                    <a-textarea v-model=\"form.remark\" placeholder=\"备注\" />\n                </a-form-item>\n            </div>\n        </a-form>\n\n        <!-- 分割线 -->\n        <a-divider class=\"mt-0\" />\n\n        <div class=\"flex justify-end\">\n            <a-space>\n                <a-button @click=\"cancelBtnClick\">取消</a-button>\n                <a-button type=\"primary\" @click=\"okBtnClick\">确定</a-button>\n            </a-space>\n        </div>\n    </a-spin>\n</template>\n\n<script setup>\nimport { ref, reactive, getCurrentInstance, watch } from 'vue'\nimport {addPermApi, permType, updatePermByPermIdApi, getPermListApi, getPermByPermIdApi} from '~/api/perm'\nimport {status} from \"~/utils/sys.js\";\nimport IconSelect from \"~/components/iconSelect/index.vue\";\n\n//全局实例\nconst {proxy} = getCurrentInstance()\n//加载字典\nconst dicts = proxy.LoadDicts([proxy.DICT.commonNumberStatus, proxy.DICT.permType, proxy.DICT.yesNo])\n//接收父组件参数\nconst props = defineProps({\n    params: {\n        type: Object,\n        default: () => {}\n    }\n})\n//加载中...\nconst spinLoading = ref(false)\n//表单ref\nconst formRef = ref(null)\n//表单\nconst form = reactive({\n    //权限ID\n    permId: null,\n    //权限编码\n    permCode: null,\n    //权限名称\n    permName: null,\n    //上级权限ID\n    parentPermId: \"0\",\n    //排序值\n    permSort: 0,\n    //路由地址\n    permPath: null,\n    //权限图标\n    permIcon: null,\n    //权限类型\n    permType: permType.M.key,\n    //组件路径\n    component: null,\n    //是否可见\n    visible: proxy.yesNo.yes.key,\n    //备注\n    remark: null,\n    //权限状态\n    status: status.status_1.key,\n})\n//表单验证规则\nconst rules = {\n    parentPermId: [{required: true, message: '上级权限不能为空', trigger: 'submit'}],\n    permType: [{required: true, message: '权限类型不能为空', trigger: 'submit'}],\n    permName: [{required: true, message: '权限名称不能为空', trigger: 'submit'}],\n    permSort: [{required: true, message: '排序值不能为空', trigger: 'submit'}],\n}\n//表单项名称\nconst formName = ref('')\n//权限树数据\nconst permTree = ref([])\n//获取权限数据列表\nconst getPermTree = () => {\n    getPermListApi({}).then(res => {\n        //权限树数据处理，将数据挂载到\"全部\"下边\n        permTree.value = [{permId: '0', permName: '全部', children: res}]\n    })\n}\n//加载权限详细信息\nconst loadPermInfo = (permId) => {\n    spinLoading.value = true\n    getPermByPermIdApi(permId).then(res => {\n        //数据赋值\n        if (res) {\n            for (let key in res) {\n                if (form.hasOwnProperty(key)) {\n                    form[key] = res[key]\n                }\n            }\n        }\n    }).finally(() => {\n        spinLoading.value = false\n    })\n}\n//父组件函数\nconst emits = defineEmits(['ok', 'cancel'])\n//确定 -> 点击\nconst okBtnClick = () => {\n    //表单验证\n    formRef.value.validate((valid) => {\n        if (valid) {return false}\n        //添加\n        if (props.params.operationType === proxy.operationType.add.type) {\n            spinLoading.value = true\n            addPermApi(form).then(() => {\n                proxy.$msg.success(proxy.operationType.add.success)\n                emits('ok')\n            }).finally(() => {\n                spinLoading.value = false\n            })\n        }\n        //修改\n        if (props.params.operationType === proxy.operationType.update.type) {\n            spinLoading.value = true\n            updatePermByPermIdApi(form).then(() => {\n                proxy.$msg.success(proxy.operationType.update.success)\n                emits('ok')\n            }).finally(() => {\n                spinLoading.value = false\n            })\n        }\n    })\n}\n//取消 -> 点击\nconst cancelBtnClick = () => {\n    emits('cancel')\n}\n//监听参数变化\nwatch(() => form.permType, (newVal, oldVal) => {\n    formName.value = form.permType === permType.M.key ? permType.M.value : '权限'\n}, { deep: true, immediate: true })\n//监听参数变化\nwatch(() => props.params, (newVal, oldVal) => {\n    //如果有permId参数，则是 \"修改\"\n    if (props.params.permId) {\n        loadPermInfo(props.params.permId)\n    }\n    //如果有parentPermId参数，则是 表格行 \"添加\"\n    if (props.params.parentPermId) {\n        //给上级权限ID赋值\n        form.parentPermId = props.params.parentPermId\n    }\n    //加载权限树\n    getPermTree()\n}, { deep: true, immediate: true })\n</script>\n<style scoped></style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/perm/PermMgt.vue",
    "content": "<template>\n    <a-card :body-style=\"{height: 'calc(100vh - 125px)'}\">\n\n        <!-- 数据列表 -->\n        <a-row class=\"w-full h-full flex flex-col overflow-x-auto overflow-y-hidden\">\n            <!-- 查询条件 -->\n            <a-row class=\"w-full\" v-if=\"showSearchRow\">\n                <a-form :model=\"searchForm\" layout=\"inline\" label-align=\"left\" size=\"small\">\n                    <a-form-item field=\"permName\" label=\"权限名称\">\n                        <a-input v-model=\"searchForm.permName\" placeholder=\"权限名称\" />\n                    </a-form-item>\n                    <a-form-item field=\"status\" label=\"权限状态\">\n                        <a-select v-model=\"searchForm.status\" placeholder=\"权限状态\" allow-clear>\n                            <a-option v-for=\"(d, index) in dicts[proxy.DICT.commonNumberStatus]\" :key=\"index\" :value=\"d.dictKey\" :label=\"d.dictValue\" />\n                        </a-select>\n                    </a-form-item>\n                </a-form>\n                <a-row justify=\"center\" class=\"w-full mt-2\">\n                    <a-space>\n                        <a-button type=\"primary\" @click=\"getList(false)\">\n                            <template #icon><icon-search /></template>\n                            <template #default>查询</template>\n                        </a-button>\n                        <a-button @click=\"getList(true)\">\n                            <template #icon><icon-sync /></template>\n                            <template #default>重置</template>\n                        </a-button>\n                    </a-space>\n                </a-row>\n            </a-row>\n\n            <!-- 分割线 -->\n            <a-divider v-if=\"showSearchRow\" class=\"mt-2\" />\n\n            <!-- 数据操作区 -->\n            <a-row class=\"w-full flex justify-between\">\n                <a-space>\n                    <!-- 添加 -->\n                    <a-button v-perm=\"['basic:perm:add']\" type=\"primary\" size=\"small\" @click=\"addBtnClick()\">\n                        <template #icon><icon-plus /></template>\n                        <template #default>添加</template>\n                    </a-button>\n                    <!-- 展开/折叠 -->\n                    <a-button size=\"small\" @click=\"treeExpand = !treeExpand\">\n                        <template #icon><icon-swap /></template>\n                        <template #default>展开/折叠</template>\n                    </a-button>\n                </a-space>\n                <a-space>\n                    <!-- 刷新 -->\n                    <a-button shape=\"circle\" size=\"small\" @click=\"getList(false)\">\n                        <template #icon><icon-refresh /></template>\n                    </a-button>\n                    <!-- 收缩/展开 -->\n                    <a-button shape=\"circle\" size=\"small\" @click=\"showSearchRow = !showSearchRow\">\n                        <template #icon>\n                            <icon-caret-up v-if=\"showSearchRow\" />\n                            <icon-caret-down v-else />\n                        </template>\n                    </a-button>\n                </a-space>\n            </a-row>\n\n            <!-- 数据展示区 -->\n            <a-row class=\"w-full flex-1 mt-3 overflow-y-auto\">\n                <a-table ref=\"tableRef\" class=\"w-[100%]\" :scroll=\"{ minWidth: 600, y: '100%' }\" :columns=\"datatable.columns\" :data=\"datatable.records\" :loading=\"datatable.loading\" row-key=\"permId\" :pagination=\"false\" table-layout-fixed>\n                    <!-- 权限名称 -->\n                    <template #permName=\"{ record }\">\n                        <a-link @click=\"detailBtnClick(record.permId)\" icon>{{ record.permName }}</a-link>\n                    </template>\n                    <!-- 图标展示 -->\n                    <template #permIcon=\"{ record }\">\n                        <functional-icons :icon=\"record.permIcon\" size=\"30\"></functional-icons>\n                    </template>\n                    <!-- 权限类型 -->\n                    <template #permType=\"{ record }\">\n                        <dict-convert :dict-data=\"dicts[proxy.DICT.permType]\" :dict-key=\"record.permType\" />\n                    </template>\n                    <!-- 状态数据转换 -->\n                    <template #status=\"{ record }\">\n                        <dict-convert :dict-data=\"dicts[proxy.DICT.commonNumberStatus]\" :dict-key=\"record.status\" />\n                    </template>\n                    <!-- 操作 -->\n                    <template #operation=\"{ record }\">\n                        <a-button v-perm=\"['basic:perm:add']\" type=\"text\" size=\"mini\" @click=\"addRowBtnClick(record)\" style=\"padding: 0 5px\">\n                            <template #icon>\n                                <icon-plus />\n                            </template>\n                            <template #default>添加</template>\n                        </a-button>\n                        <a-button v-perm=\"['basic:perm:update']\" type=\"text\" size=\"mini\" @click=\"updateBtnClick(record.permId)\" style=\"padding: 0 5px\">\n                            <template #icon>\n                                <icon-edit />\n                            </template>\n                            <template #default>修改</template>\n                        </a-button>\n                        <a-popconfirm content=\"确认要删除吗?\" @ok=\"deleteBtnOkClick(record)\">\n                            <a-button v-perm=\"['basic:perm:delete']\" type=\"text\" status=\"danger\" size=\"mini\" style=\"padding: 0 5px\">\n                                <template #icon>\n                                    <icon-delete />\n                                </template>\n                                <template #default>删除</template>\n                            </a-button>\n                        </a-popconfirm>\n                    </template>\n                </a-table>\n            </a-row>\n        </a-row>\n\n        <!-- 添加/修改 -->\n        <a-modal v-model:visible=\"modal.visible\" width=\"50%\" :esc-to-close=\"false\" :mask-closable=\"false\" draggable :footer=\"false\">\n            <template #title>{{ modal.title }}</template>\n            <component :is=\"modal.component\" :params=\"modal.params\" @ok=\"onOk\" @cancel=\"onCancel\" v-if=\"modal.visible\" />\n        </a-modal>\n\n    </a-card>\n</template>\n\n<script setup>\nimport FunctionalIcons from \"~/components/iconSelect/FunctionalIcons.vue\";\nimport {ref, reactive, getCurrentInstance, shallowRef, nextTick, watch, onMounted} from 'vue'\nimport PermEdit from \"~/pages/basic/perm/PermEdit.vue\";\nimport PermDetail from \"~/pages/basic/perm/PermDetail.vue\";\nimport {getPermListApi, deletePermByPermIdApi} from '~/api/perm'\n\n//全局实例\nconst {proxy} = getCurrentInstance()\n//加载字典\nconst dicts = proxy.LoadDicts([proxy.DICT.commonNumberStatus, proxy.DICT.permType])\n//是否展示搜索区域\nconst showSearchRow = ref(true)\n//搜索参数表单\nconst searchForm = reactive({\n    //权限名称\n    permName: null,\n    //权限状态\n    status: null\n})\n//数据列表\nconst datatable = reactive({\n    //列配置\n    columns: [\n        {title: '权限名称', dataIndex: 'permName', slotName: 'permName', align: 'left', width: 320, headerCellClass: 'w-[100%] flex justify-center'},\n        {title: '权限图标', dataIndex: 'permIcon', slotName: 'permIcon', align: 'center', width: 100},\n        {title: '权限类型', dataIndex: 'permType', slotName: 'permType', align: 'center', width: 100},\n        {title: '排序', dataIndex: 'permSort', align: 'center', width: 80},\n        {title: '权限编码', dataIndex: 'permCode', align: 'center', width: 200, ellipsis: true, tooltip: true},\n        {title: '路由地址', dataIndex: 'permPath', align: 'center', width: 200,ellipsis: true, tooltip: true},\n        {title: '组件路径', dataIndex: 'component', align: 'center', width: 200,ellipsis: true, tooltip: true},\n        {title: '权限状态', dataIndex: 'status', slotName: 'status', align: 'center', width: 100},\n        {title: '操作', slotName: 'operation', align: 'center', width: 200, fixed: 'right'}\n    ],\n    //加载\n    loading: false,\n    //数据列表\n    records: []\n})\n//公共模态框\nconst modal = reactive({\n    //是否显示\n    visible: false,\n    //模态框标题\n    title: '权限管理',\n    //传递参数\n    params: {},\n    //组件名称\n    component: null\n});\n//添加按钮 -> 点击事件\nconst addBtnClick = () => {\n    modal.visible = true\n    modal.title = '添加权限'\n    modal.params = { operationType: proxy.operationType.add.type }\n    modal.component = shallowRef(PermEdit)\n}\n//表格行数据 \"添加\" -> 点击\nconst addRowBtnClick = (record) => {\n    modal.visible = true\n    modal.title = '添加权限'\n    modal.params = { operationType: proxy.operationType.add.type, parentPermId: record.permId }\n    modal.component = shallowRef(PermEdit)\n}\n//表格行数据 \"修改\" -> 点击\nconst updateBtnClick = (permId) => {\n    modal.visible = true\n    modal.title = '修改权限'\n    modal.params = { operationType: proxy.operationType.update.type, permId: permId }\n    modal.component = shallowRef(PermEdit)\n}\n//表格行数据 \"查看\" -> 点击\nconst detailBtnClick = (permId) => {\n    modal.visible = true\n    modal.title = '权限详细信息'\n    modal.params = { operationType: proxy.operationType.detail.type, permId: permId }\n    modal.component = shallowRef(PermDetail)\n}\n//表格行数据 \"删除\" -> 确认\nconst deleteBtnOkClick = (record) => {\n    if (record.children && record.children.length > 0) {\n        proxy.$msg.error('该权限下包含下级权限，请先删除下级权限')\n        return\n    }\n    deletePermByPermIdApi(record.permId).then(() => {\n        proxy.$msg.success(proxy.operationType.delete.success)\n        //刷新列表\n        getList()\n    })\n}\n//展开/折叠 - 默认折叠\nconst treeExpand = ref(false)\n//表格ref\nconst tableRef = ref()\n//查询数据列表\nconst getList = (isReset = false) => {\n    //重置查询条件\n    if (isReset) {\n        searchForm.permName = null\n        searchForm.status = null\n    }\n    datatable.loading = true\n    getPermListApi(searchForm).then(res => {\n        //table数据赋值\n        datatable.records = res\n    }).finally(() => {\n        datatable.loading = false\n    })\n}\n//监听展开/折叠\nwatch(() => treeExpand.value, (newVal, oldVal) => {\n    if (tableRef.value) {\n        tableRef.value.expandAll(treeExpand.value)\n    }\n}, { deep: true, immediate: true })\n//模态框 -> 确认\nconst onOk = () => {\n    modal.visible = false\n    //查询数据列表\n    getList()\n}\n//模态框 -> 取消\nconst onCancel = () => {\n    modal.visible = false\n}\n//初始化\nonMounted(() => {\n    //查询数据列表\n    getList()\n})\n</script>\n\n<style scoped>\n\n</style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/post/PostDetail.vue",
    "content": "<template>\n    <a-spin class=\"w-[100%]\" :size=\"35\" :loading=\"spinLoading\" tip=\"正在处理, 请稍候...\">\n        <a-descriptions :column=\"2\" bordered>\n            <a-descriptions-item label=\"岗位名称\">{{ form.postName }}</a-descriptions-item>\n            <a-descriptions-item label=\"岗位编码\">{{ form.postCode }}</a-descriptions-item>\n            <a-descriptions-item label=\"排序\">{{ form.postSort }}</a-descriptions-item>\n            <a-descriptions-item label=\"岗位状态\">\n                <dict-convert :dict-data=\"dicts[proxy.DICT.commonNumberStatus]\" :dict-key=\"form.status\" />\n            </a-descriptions-item>\n            <a-descriptions-item label=\"备注\">{{ form.remark }}</a-descriptions-item>\n        </a-descriptions>\n    </a-spin>\n</template>\n<script setup>\nimport { ref, reactive, getCurrentInstance, watch } from 'vue'\nimport { getPostByPostIdApi } from \"~/api/post.js\";\n\n//全局实例\nconst { proxy } = getCurrentInstance()\n//加载字典\nconst dicts = proxy.LoadDicts([proxy.DICT.commonNumberStatus])\n//加载中...\nconst spinLoading = ref(false)\n//接收父组件参数\nconst props = defineProps({\n    params: {\n        type: Object,\n        default: () => {}\n    }\n})\n//表单\nconst form = reactive({\n    //岗位ID\n    postId: null,\n    //岗位名称\n    postName: null,\n    //岗位编码\n    postCode: null,\n    //排序值\n    postSort: null,\n    //岗位状态\n    status: null,\n    //备注\n    remark: null\n})\n//加载岗位详细信息\nconst loadPostInfo = (postId) => {\n    spinLoading.value = true\n    getPostByPostIdApi(postId).then(res => {\n        //数据赋值\n        if (res) {\n            for (let key in res) {\n                if (form.hasOwnProperty(key)) {\n                    form[key] = res[key]\n                }\n            }\n        }\n    }).finally(() => {\n        spinLoading.value = false\n    })\n}\n//监听参数变化\nwatch(() => props.params, (newVal, oldVal) => {\n    //岗位ID\n    if (props.params.postId) {\n        //查询数据\n        loadPostInfo(props.params.postId)\n    }\n}, { deep: true, immediate: true })\n</script>\n<style scoped></style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/post/PostEdit.vue",
    "content": "<template>\n    <a-spin class=\"w-[100%]\" :size=\"35\" :loading=\"spinLoading\" tip=\"正在处理, 请稍候...\">\n        <a-form :model=\"form\" ref=\"formRef\" layout=\"vertical\" :rules=\"rules\" auto-label-width>\n            <a-form-item field=\"postName\" label=\"岗位名称\" required>\n                <a-input v-model=\"form.postName\" placeholder=\"岗位名称\" />\n            </a-form-item>\n            <a-form-item field=\"postCode\" label=\"岗位编码\" required tooltip=\"岗位的唯一英文标识\">\n                <a-input v-model=\"form.postCode\" placeholder=\"岗位编码\" />\n            </a-form-item>\n            <a-form-item field=\"postSort\" label=\"排序值\" required>\n                <a-input-number :min=\"0\" v-model=\"form.postSort\" placeholder=\"排序值\" />\n            </a-form-item>\n            <a-form-item field=\"status\" label=\"岗位状态\" required v-if=\"props.params.operationType === proxy.operationType.update.type\">\n                <a-select v-model=\"form.status\" placeholder=\"岗位状态\" allow-clear>\n                    <a-option v-for=\"(d, index) in dicts[proxy.DICT.commonNumberStatus]\" :key=\"index\" :value=\"d.dictKey\" :label=\"d.dictValue\" />\n                </a-select>\n            </a-form-item>\n            <a-form-item field=\"remark\" label=\"备注\">\n                <a-textarea v-model=\"form.remark\" placeholder=\"备注\" />\n            </a-form-item>\n        </a-form>\n\n        <!-- 分割线 -->\n        <a-divider class=\"mt-0\" />\n\n        <div class=\"flex justify-end\">\n            <a-space>\n                <a-button @click=\"cancelBtnClick()\">取消</a-button>\n                <a-button type=\"primary\" @click=\"okBtnClick()\">确定</a-button>\n            </a-space>\n        </div>\n    </a-spin>\n</template>\n\n<script setup>\nimport { ref, reactive, getCurrentInstance, watch } from 'vue'\nimport { addPostApi, updatePostByPostIdApi, getPostByPostIdApi } from \"~/api/post.js\";\n\n//全局实例\nconst {proxy} = getCurrentInstance()\n//加载字典\nconst dicts = proxy.LoadDicts([proxy.DICT.commonNumberStatus])\n//接收父组件参数\nconst props = defineProps({\n    params: {\n        type: Object,\n        default: () => {}\n    }\n})\n//父组件函数\nconst emits = defineEmits(['ok', 'cancel'])\n//加载中...\nconst spinLoading = ref(false)\n//表单ref\nconst formRef = ref(null)\n//表单\nconst form = reactive({\n    //岗位ID\n    postId: null,\n    //岗位名称\n    postName: null,\n    //岗位编码\n    postCode: null,\n    //排序值\n    postSort: null,\n    //岗位状态\n    status: null,\n    //备注\n    remark: null\n})\n//表单校验规则\nconst rules = {\n    postName: [{required: true, message: '岗位名称不能为空', trigger: 'submit'}],\n    postCode: [{required: true, message: '岗位编码不能为空', trigger: 'submit'}],\n    postSort: [{required: true, message: '排序值不能为空', trigger: 'submit'}],\n}\n//确定 -> 点击\nconst okBtnClick = () => {\n    //表单验证\n    formRef.value.validate((valid) => {\n        if (valid) {return false}\n        //添加\n        if (props.params.operationType === proxy.operationType.add.type) {\n            spinLoading.value = true\n            addPostApi(form).then(() => {\n                proxy.$msg.success(proxy.operationType.add.success)\n                emits('ok')\n            }).finally(() => {\n                spinLoading.value = false\n            })\n        }\n        //修改\n        if (props.params.operationType === proxy.operationType.update.type) {\n            spinLoading.value = true\n            updatePostByPostIdApi(form).then(() => {\n                proxy.$msg.success(proxy.operationType.update.success)\n                emits('ok')\n            }).finally(() => {\n                spinLoading.value = false\n            })\n        }\n    })\n}\n//取消 -> 点击\nconst cancelBtnClick = () => {\n    emits('cancel')\n}\n//加载岗位详细信息\nconst loadPostInfo = (postId) => {\n    spinLoading.value = true\n    getPostByPostIdApi(postId).then(res => {\n        //数据赋值\n        if (res) {\n            for (let key in res) {\n                if (form.hasOwnProperty(key)) {\n                    form[key] = res[key]\n                }\n            }\n        }\n    }).finally(() => {\n        spinLoading.value = false\n    })\n}\n//监听参数变化\nwatch(() => props.params, (newVal, oldVal) => {\n    //岗位ID\n    if (props.params.postId) {\n        //加载岗位信息\n        loadPostInfo(props.params.postId)\n    }\n}, { deep: true, immediate: true })\n</script>\n<style scoped></style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/post/PostMgt.vue",
    "content": "<template>\n    <a-card :body-style=\"{height: 'calc(100vh - 125px)'}\">\n\n        <!-- 数据列表 -->\n        <a-row class=\"w-full h-full flex flex-col overflow-x-auto overflow-y-hidden\">\n            <!-- 查询条件 -->\n            <a-row class=\"w-full\" v-if=\"showSearchRow\">\n                <a-form :model=\"searchForm\" layout=\"inline\" label-align=\"left\" size=\"small\">\n                    <a-form-item field=\"postName\" label=\"岗位名称\">\n                        <a-input v-model=\"searchForm.postName\" placeholder=\"岗位名称\" />\n                    </a-form-item>\n                    <a-form-item field=\"postCode\" label=\"岗位编码\">\n                        <a-input v-model=\"searchForm.postCode\" placeholder=\"岗位编码\" />\n                    </a-form-item>\n                    <a-form-item field=\"status\" label=\"岗位状态\">\n                        <a-select v-model=\"searchForm.status\" placeholder=\"岗位状态\" allow-clear>\n                            <a-option v-for=\"(d, index) in dicts[proxy.DICT.commonNumberStatus]\" :key=\"index\" :value=\"d.dictKey\" :label=\"d.dictValue\" />\n                        </a-select>\n                    </a-form-item>\n                </a-form>\n                <a-row justify=\"center\" class=\"w-full mt-2\">\n                    <a-space>\n                        <a-button type=\"primary\" @click=\"getPageList(false)\">\n                            <template #icon><icon-search /></template>\n                            <template #default>查询</template>\n                        </a-button>\n                        <a-button @click=\"getPageList(true)\">\n                            <template #icon><icon-sync /></template>\n                            <template #default>重置</template>\n                        </a-button>\n                    </a-space>\n                </a-row>\n            </a-row>\n\n            <!-- 分割线 -->\n            <a-divider v-if=\"showSearchRow\" class=\"mt-2\" />\n\n            <!-- 数据操作区 -->\n            <a-row class=\"w-full flex justify-between\">\n                <a-space>\n                    <!-- 添加 -->\n                    <a-button v-perm=\"['basic:post:add']\" type=\"primary\" size=\"small\" @click=\"addBtnClick()\">\n                        <template #icon><icon-plus /></template>\n                        <template #default>添加</template>\n                    </a-button>\n                </a-space>\n                <a-space>\n                    <!-- 刷新 -->\n                    <a-button shape=\"circle\" size=\"small\" @click=\"getPageList(false)\">\n                        <template #icon><icon-refresh /></template>\n                    </a-button>\n                    <!-- 收缩/展开 -->\n                    <a-button shape=\"circle\" size=\"small\" @click=\"showSearchRow = !showSearchRow\">\n                        <template #icon>\n                            <icon-caret-up v-if=\"showSearchRow\" />\n                            <icon-caret-down v-else />\n                        </template>\n                    </a-button>\n                </a-space>\n            </a-row>\n\n            <!-- 数据展示区 -->\n            <a-row class=\"w-full flex-1 mt-3 overflow-y-auto\">\n                <a-table class=\"w-[100%]\" :scroll=\"{ y: '100%' }\" :columns=\"datatable.columns\" :data=\"datatable.records\" :loading=\"datatable.loading\" :pagination=\"false\" table-layout-fixed>\n                    <!-- 岗位名称 -->\n                    <template #postName=\"{ record }\">\n                        <a-link @click=\"detailBtnClick(record.postId)\" icon>{{ record.postName }}</a-link>\n                    </template>\n                    <!-- 岗位状态 -->\n                    <template #status=\"{ record }\">\n                        <dict-convert :dict-data=\"dicts[proxy.DICT.commonNumberStatus]\" :dict-key=\"record.status\" />\n                    </template>\n                    <!-- 操作 -->\n                    <template #operation=\"{ record }\">\n                        <a-button v-perm=\"['basic:post:update']\" type=\"text\" size=\"mini\" @click=\"updateBtnClick(record.postId)\">\n                            <template #icon>\n                                <icon-edit />\n                            </template>\n                            <template #default>修改</template>\n                        </a-button>\n                        <a-popconfirm content=\"确认要删除吗?\" @ok=\"deleteBtnOkClick(record.postId)\">\n                            <a-button v-perm=\"['basic:post:delete']\" type=\"text\" status=\"danger\" size=\"mini\">\n                                <template #icon>\n                                    <icon-delete />\n                                </template>\n                                <template #default>删除</template>\n                            </a-button>\n                        </a-popconfirm>\n                    </template>\n                </a-table>\n            </a-row>\n\n            <!-- 分页 -->\n            <a-row class=\"w-full flex justify-end mt-2\">\n                <pagination v-if=\"datatable.total > 0\"\n                            v-model:page-num=\"searchForm.pageNum\"\n                            v-model:page-size=\"searchForm.pageSize\"\n                            :total=\"datatable.total\" @pagination=\"getPageList(false)\" />\n            </a-row>\n        </a-row>\n\n        <!-- 添加/修改 -->\n        <a-modal v-model:visible=\"modal.visible\" width=\"400px\" :esc-to-close=\"false\" :mask-closable=\"false\" draggable :footer=\"false\">\n            <template #title>{{ modal.title }}</template>\n            <component :is=\"modal.component\" :params=\"modal.params\" @ok=\"onOk\" @cancel=\"onCancel\" v-if=\"modal.visible\" />\n        </a-modal>\n\n    </a-card>\n</template>\n\n<script setup>\nimport {ref, reactive, getCurrentInstance, shallowRef} from 'vue'\nimport { getPagePostListApi, deletePostByPostIdApi } from '~/api/post.js'\nimport PostEdit from \"~/pages/basic/post/PostEdit.vue\";\nimport PostDetail from \"~/pages/basic/post/PostDetail.vue\";\n\n//全局实例\nconst {proxy} = getCurrentInstance()\n//加载字典\nconst dicts = proxy.LoadDicts([proxy.DICT.commonNumberStatus])\n//是否展示搜索区域\nconst showSearchRow = ref(true)\n//搜索参数表单\nconst searchForm = reactive({\n    //岗位名称\n    postName: null,\n    //岗位编码\n    postCode: null,\n    //岗位状态\n    status: null,\n    //页码\n    pageNum: 1,\n    //条数\n    pageSize: 10\n})\n//数据列表\nconst datatable = reactive({\n    //列配置\n    columns: [\n        {title: '岗位名称', dataIndex: 'postName', slotName: 'postName', align: 'center'},\n        {title: '岗位编码', dataIndex: 'postCode', align: 'center'},\n        {title: '排序', dataIndex: 'postSort', align: 'center'},\n        {title: '岗位状态', dataIndex: 'status', slotName: 'status', align: 'center'},\n        {title: '操作', slotName: 'operation', align: 'center', width: 160, fixed: 'right'}\n    ],\n    //加载\n    loading: false,\n    //数据列表\n    records: [],\n    //总条数\n    total: 0\n})\n//查询数据列表\nconst getPageList = (isReset = false) => {\n    if (isReset) {\n        searchForm.postName = null\n        searchForm.postCode = null\n        searchForm.status = null\n        searchForm.pageNum = 1\n        searchForm.pageSize = 10\n    }\n    datatable.loading = true\n    getPagePostListApi(searchForm).then(res => {\n        datatable.records = res.records\n        datatable.total = res.total\n    }).finally(() => {\n        datatable.loading = false\n    })\n}\n//公共模态框\nconst modal = reactive({\n    //是否显示\n    visible: false,\n    //模态框标题\n    title: '岗位管理',\n    //传递参数\n    params: {},\n    //组件名称\n    component: null\n});\n//添加按钮 -> 点击事件\nconst addBtnClick = () => {\n    modal.visible = true\n    modal.title = '添加岗位'\n    modal.params = { operationType: proxy.operationType.add.type }\n    modal.component = shallowRef(PostEdit)\n}\n//表格行数据 \"修改\" -> 点击\nconst updateBtnClick = (postId) => {\n    modal.visible = true\n    modal.title = '修改岗位'\n    modal.params = { operationType: proxy.operationType.update.type, postId: postId }\n    modal.component = shallowRef(PostEdit)\n}\n//表格行数据 \"查看\" -> 点击\nconst detailBtnClick = (postId) => {\n    modal.visible = true\n    modal.title = '岗位详细信息'\n    modal.params = { operationType: proxy.operationType.detail.type, postId: postId }\n    modal.component = shallowRef(PostDetail)\n}\n//表格行数据 \"删除\" -> 确认\nconst deleteBtnOkClick = (postId) => {\n    deletePostByPostIdApi(postId).then(() => {\n        proxy.$msg.success(proxy.operationType.delete.success)\n        //刷新列表\n        getPageList()\n    })\n}\n//模态框 -> 确认\nconst onOk = () => {\n    modal.visible = false\n    //查询数据列表\n    getPageList()\n}\n//模态框 -> 取消\nconst onCancel = () => {\n    modal.visible = false\n}\n\n//查询数据列表\ngetPageList()\n</script>\n<style scoped></style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/role/RoleDetail.vue",
    "content": "<template>\n    <a-spin class=\"w-[100%]\" :size=\"35\" :loading=\"spinLoading\" tip=\"正在处理, 请稍候...\">\n        <a-descriptions :column=\"2\" bordered>\n            <a-descriptions-item label=\"角色名称\">{{ form.roleName }}</a-descriptions-item>\n            <a-descriptions-item label=\"角色编码\">{{ form.roleCode }}</a-descriptions-item>\n            <a-descriptions-item label=\"排序\">{{ form.roleSort }}</a-descriptions-item>\n            <a-descriptions-item label=\"角色状态\">\n                <dict-convert :dict-data=\"dicts[proxy.DICT.commonNumberStatus]\" :dict-key=\"form.status\" />\n            </a-descriptions-item>\n            <a-descriptions-item label=\"备注\">{{ form.remark }}</a-descriptions-item>\n        </a-descriptions>\n\n        <a-descriptions :column=\"2\" bordered class=\"mt-3\">\n            <a-descriptions-item :span=\"2\" label=\"角色权限\">\n                <a-spin class=\"w-[100%]\" :size=\"35\" :loading=\"permTreeSpinLoading\" tip=\"正在处理, 请稍候...\">\n                    <a-scrollbar class=\"w-[100%] max-h-[250px] overflow-auto\" :outer-style=\"{width: '100%'}\" type=\"track\">\n                        <a-tree v-model:checked-keys=\"form.checkedPermIds\" :data=\"permTreeData\" v-if=\"permTreeData.length > 0\"\n                                show-line checkable multiple blockNode :selectable=\"false\"\n                                :fieldNames=\"{\n                                    key: 'permId',\n                                    title: 'permName',\n                                    children: 'children'\n                                }\" />\n                    </a-scrollbar>\n                </a-spin>\n            </a-descriptions-item>\n        </a-descriptions>\n    </a-spin>\n</template>\n<script setup>\nimport {ref, reactive, getCurrentInstance, watch, nextTick} from 'vue'\nimport { getRoleByRoleIdApi } from \"~/api/role.js\";\nimport { getTenantEnablePermListApi } from \"~/api/perm.js\";\nimport {getAllTreeParentId} from \"~/utils/sys.js\";\n\n//全局实例\nconst { proxy } = getCurrentInstance()\n//加载字典\nconst dicts = proxy.LoadDicts([proxy.DICT.commonNumberStatus])\n//加载中...\nconst spinLoading = ref(false)\n//接收父组件参数\nconst props = defineProps({\n    params: {\n        type: Object,\n        default: () => {}\n    }\n})\n//表单\nconst form = reactive({\n    //角色ID\n    roleId: null,\n    //角色名称\n    roleName: null,\n    //角色编码\n    roleCode: null,\n    //排序值\n    roleSort: 0,\n    //角色状态\n    status: null,\n    //备注\n    remark: null,\n    //角色权限 -> 全勾选\n    checkedPermIds: []\n})\n//加载角色详细信息\nconst loadRoleInfo = (roleId) => {\n    spinLoading.value = true\n    getRoleByRoleIdApi(roleId).then(res => {\n        //数据赋值\n        if (res) {\n            for (let key in res) {\n                if (form.hasOwnProperty(key)) {\n                    if (key === 'checkedPermIds') {\n                        let checkedPermIds = []\n                        //处理权限回显\n                        res[key].forEach(permId => {\n                            //如果不是父节点，则回显为勾选\n                            if (!allParentPermId.value.includes(permId)) {\n                                checkedPermIds.push(permId)\n                            }\n                        })\n                        form.checkedPermIds = checkedPermIds\n                    } else {\n                        form[key] = res[key]\n                    }\n                }\n            }\n        }\n    }).finally(() => {\n        spinLoading.value = false\n    })\n}\n//权限树数据\nconst permTreeData = ref([])\n//权限树加载\nconst permTreeSpinLoading = ref(false)\n//权限树所有父节点ID(只要有子集，就视为是父节点)\nconst allParentPermId = ref([])\n//获取权限数据列表\nconst getPermTree = (loadRole = false) => {\n    permTreeSpinLoading.value = true\n    getTenantEnablePermListApi().then(res => {\n        //权限树数据赋值\n        permTreeData.value = res\n        if (loadRole) {\n            //获取所有父permId\n            allParentPermId.value = getAllTreeParentId(res, 'permId')\n            //查询数据\n            loadRoleInfo(props.params.roleId)\n        }\n    }).finally(() => {\n        permTreeSpinLoading.value = false\n    })\n}\n//监听参数变化\nwatch(() => props.params, (newVal, oldVal) => {\n    //角色ID\n    if (props.params.roleId) {\n        //加载权限树，同时加载角色详情\n        getPermTree(true)\n    } else {\n        //加载权限树\n        getPermTree()\n    }\n}, { deep: true, immediate: true })\n</script>\n<style scoped>\n/* 树形组件点击会高亮，容易引起歧义，此处取消高亮样式 */\n:deep(.arco-tree-node-selected .arco-tree-node-title, .arco-tree-node-selected .arco-tree-node-title:hover) {\n    color: rgb(var(--color-text-1));\n}\n</style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/role/RoleEdit.vue",
    "content": "<template>\n    <a-spin class=\"w-[100%]\" :size=\"35\" :loading=\"spinLoading\" tip=\"正在处理, 请稍候...\">\n        <a-form :model=\"form\" ref=\"formRef\" layout=\"vertical\" :rules=\"rules\" auto-label-width>\n            <div class=\"flex justify-between\" style=\"flex-wrap: wrap;\">\n                <a-form-item class=\"w-[49%]\" field=\"roleName\" label=\"角色名称\" required>\n                    <a-input v-model=\"form.roleName\" placeholder=\"角色名称\" />\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" field=\"roleCode\" label=\"角色编码\" required tooltip=\"控制器中定义的角色编码\">\n                    <a-input v-model=\"form.roleCode\" placeholder=\"角色编码\" />\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" field=\"roleSort\" label=\"排序值\" required>\n                    <a-input-number :min=\"0\" v-model=\"form.roleSort\" placeholder=\"排序值\" />\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" field=\"status\" label=\"角色状态\" required v-if=\"props.params.operationType === proxy.operationType.update.type\">\n                    <a-select v-model=\"form.status\" placeholder=\"角色状态\" allow-clear>\n                        <a-option v-for=\"(d, index) in dicts[proxy.DICT.commonNumberStatus]\" :key=\"index\" :value=\"d.dictKey\" :label=\"d.dictValue\" />\n                    </a-select>\n                </a-form-item>\n                <a-form-item class=\"w-[100%]\" field=\"remark\" label=\"备注\">\n                    <a-textarea v-model=\"form.remark\" placeholder=\"备注\" />\n                </a-form-item>\n                <a-form-item class=\"w-[100%]\" field=\"checkedPermIds\" label=\"角色权限\" required>\n                    <a-spin class=\"w-[100%]\" :size=\"35\" :loading=\"permTreeSpinLoading\" tip=\"正在处理, 请稍候...\">\n                        <a-scrollbar class=\"w-[100%] max-h-[250px] overflow-auto border\" :outer-style=\"{width: '100%'}\" type=\"track\">\n                            <a-tree v-model:checked-keys=\"form.checkedPermIds\" :data=\"permTreeData\" ref=\"treeRef\" v-if=\"permTreeData.length > 0\"\n                                    show-line multiple checkable blockNode action-on-node-click=\"expand\"\n                                    :fieldNames=\"{\n                                        key: 'permId',\n                                        title: 'permName',\n                                        children: 'children'\n                                    }\" />\n                        </a-scrollbar>\n                    </a-spin>\n                </a-form-item>\n            </div>\n        </a-form>\n\n        <!-- 分割线 -->\n        <a-divider class=\"mt-0\" />\n\n        <div class=\"flex justify-end\">\n            <a-space>\n                <a-button @click=\"cancelBtnClick()\">取消</a-button>\n                <a-button type=\"primary\" @click=\"okBtnClick()\">确定</a-button>\n            </a-space>\n        </div>\n    </a-spin>\n</template>\n\n<script setup>\nimport {ref, reactive, getCurrentInstance, watch, nextTick} from 'vue'\nimport { addRoleApi, updateRoleByRoleIdApi, getRoleByRoleIdApi } from \"~/api/role.js\";\nimport {getTenantEnablePermListApi} from \"~/api/perm.js\";\nimport { getAllTreeParentId } from \"~/utils/sys.js\";\n\n//全局实例\nconst {proxy} = getCurrentInstance()\n//加载字典\nconst dicts = proxy.LoadDicts([proxy.DICT.commonNumberStatus])\n//接收父组件参数\nconst props = defineProps({\n    params: {\n        type: Object,\n        default: () => {}\n    }\n})\n//父组件函数\nconst emits = defineEmits(['ok', 'cancel'])\n//加载中...\nconst spinLoading = ref(false)\n//表单ref\nconst formRef = ref(null)\n//表单\nconst form = reactive({\n    //角色ID\n    roleId: null,\n    //角色名称\n    roleName: null,\n    //角色编码\n    roleCode: null,\n    //排序值\n    roleSort: null,\n    //角色状态\n    status: null,\n    //备注\n    remark: null,\n    //角色权限 -> 全勾选+半勾选\n    permissionsIds: [],\n    //角色权限 -> 全勾选\n    checkedPermIds: []\n})\n//表单校验规则\nconst rules = {\n    roleName: [{required: true, message: '角色名称不能为空', trigger: 'submit'}],\n    roleCode: [{required: true, message: '角色编码不能为空', trigger: 'submit'}],\n    roleSort: [{required: true, message: '排序值不能为空', trigger: 'submit'}],\n    permissionsIds: [{required: true, message: '角色权限不能为空', trigger: 'submit'}],\n    checkedPermIds: [{required: true, message: '角色权限不能为空', trigger: 'submit'}]\n}\n//确定 -> 点击\nconst okBtnClick = () => {\n    //获取权限 -> 全勾选+半勾选\n    form.permissionsIds = getPermTreeSelectData(true)\n    //表单验证\n    formRef.value.validate((valid) => {\n        if (valid) {return false}\n        //添加\n        if (props.params.operationType === proxy.operationType.add.type) {\n            spinLoading.value = true\n            addRoleApi(form).then(() => {\n                proxy.$msg.success(proxy.operationType.add.success)\n                emits('ok')\n            }).finally(() => {\n                spinLoading.value = false\n            })\n        }\n        //修改\n        if (props.params.operationType === proxy.operationType.update.type) {\n            spinLoading.value = true\n            updateRoleByRoleIdApi(form).then(() => {\n                proxy.$msg.success(proxy.operationType.update.success)\n                emits('ok')\n            }).finally(() => {\n                spinLoading.value = false\n            })\n        }\n    })\n}\n//取消 -> 点击\nconst cancelBtnClick = () => {\n    emits('cancel')\n}\n//获取权限树的节点 isGetHalf：是否获取半勾选节点ID\nconst getPermTreeSelectData = (isGetHalf) => {\n    //选中的节点\n    let checkedKeys = treeRef.value.getCheckedNodes()\n    if (checkedKeys && checkedKeys.length > 0) {\n        checkedKeys = [...new Set(checkedKeys.map(node => node.permId))]\n    }\n    //半选中的节点\n    let halfCheckedKeys = []\n    if (isGetHalf) {\n        halfCheckedKeys = treeRef.value.getHalfCheckedNodes()\n        if (halfCheckedKeys && halfCheckedKeys.length > 0) {\n            halfCheckedKeys = [...new Set(halfCheckedKeys.map(node => node.permId))]\n        }\n    }\n    //合并两个数组\n    return [...halfCheckedKeys, ...checkedKeys]\n}\n//权限树数据\nconst permTreeData = ref([])\n//权限树加载\nconst permTreeSpinLoading = ref(false)\n//权限树ref\nconst treeRef = ref(null)\n//权限树所有父节点ID(只要有子集，就视为是父节点)\nconst allParentPermId = ref([])\n//获取权限数据列表\nconst getPermTree = (loadRole = false) => {\n    permTreeSpinLoading.value = true\n    getTenantEnablePermListApi().then(res => {\n        //权限树数据赋值\n        permTreeData.value = res\n        if (loadRole) {\n            //获取所有父permId\n            allParentPermId.value = getAllTreeParentId(res, 'permId')\n            //加载角色信息\n            loadRoleInfo(props.params.roleId)\n        }\n    }).finally(() => {\n        permTreeSpinLoading.value = false\n    })\n}\n//加载角色详细信息\nconst loadRoleInfo = (roleId) => {\n    spinLoading.value = true\n    getRoleByRoleIdApi(roleId).then(res => {\n        //数据赋值\n        if (res) {\n            for (let key in res) {\n                if (form.hasOwnProperty(key)) {\n                    if (key === 'checkedPermIds') {\n                        let checkedPermIds = []\n                        //处理权限回显\n                        res[key].forEach(permId => {\n                            //如果不是父节点，则回显为勾选\n                            if (!allParentPermId.value.includes(permId)) {\n                                checkedPermIds.push(permId)\n                            }\n                        })\n                        form.checkedPermIds = checkedPermIds\n                    } else {\n                        form[key] = res[key]\n                    }\n                }\n            }\n        }\n    }).finally(() => {\n        spinLoading.value = false\n    })\n}\n//监听参数变化\nwatch(() => props.params, (newVal, oldVal) => {\n    //角色ID\n    if (props.params.roleId) {\n        //加载权限树，同时加载角色详情\n        getPermTree(true)\n    } else {\n        //加载权限树\n        getPermTree()\n    }\n}, { deep: true, immediate: true })\n</script>\n<style scoped>\n/* 树形组件点击会高亮，容易引起歧义，此处取消高亮样式 */\n:deep(.arco-tree-node-selected .arco-tree-node-title, .arco-tree-node-selected .arco-tree-node-title:hover) {\n    color: rgb(var(--color-text-1));\n}\n</style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/role/RoleMgt.vue",
    "content": "<template>\n    <a-card :body-style=\"{height: 'calc(100vh - 125px)'}\">\n\n        <!-- 数据列表 -->\n        <a-row class=\"w-full h-full flex flex-col overflow-x-auto overflow-y-hidden\">\n            <!-- 查询条件 -->\n            <a-row class=\"w-full\" v-if=\"showSearchRow\">\n                <a-form :model=\"searchForm\" layout=\"inline\" label-align=\"left\" size=\"small\">\n                    <a-form-item field=\"roleName\" label=\"角色名称\">\n                        <a-input v-model=\"searchForm.roleName\" placeholder=\"角色名称\" />\n                    </a-form-item>\n                    <a-form-item field=\"roleCode\" label=\"角色编码\">\n                        <a-input v-model=\"searchForm.roleCode\" placeholder=\"角色编码\" />\n                    </a-form-item>\n                    <a-form-item field=\"status\" label=\"角色状态\">\n                        <a-select v-model=\"searchForm.status\" placeholder=\"角色状态\" allow-clear>\n                            <a-option v-for=\"(d, index) in dicts[proxy.DICT.commonNumberStatus]\" :key=\"index\" :value=\"d.dictKey\" :label=\"d.dictValue\" />\n                        </a-select>\n                    </a-form-item>\n                </a-form>\n                <a-row justify=\"center\" class=\"w-full mt-2\">\n                    <a-space>\n                        <a-button type=\"primary\" @click=\"getPageList(false)\">\n                            <template #icon><icon-search /></template>\n                            <template #default>查询</template>\n                        </a-button>\n                        <a-button @click=\"getPageList(true)\">\n                            <template #icon><icon-sync /></template>\n                            <template #default>重置</template>\n                        </a-button>\n                    </a-space>\n                </a-row>\n            </a-row>\n\n            <!-- 分割线 -->\n            <a-divider v-if=\"showSearchRow\" class=\"mt-2\" />\n\n            <!-- 数据操作区 -->\n            <a-row class=\"w-full flex justify-between\">\n                <a-space>\n                    <!-- 添加 -->\n                    <a-button v-perm=\"['basic:role:add']\" type=\"primary\" size=\"small\" @click=\"addBtnClick()\">\n                        <template #icon><icon-plus /></template>\n                        <template #default>添加</template>\n                    </a-button>\n                </a-space>\n                <a-space>\n                    <!-- 刷新 -->\n                    <a-button shape=\"circle\" size=\"small\" @click=\"getPageList(false)\">\n                        <template #icon><icon-refresh /></template>\n                    </a-button>\n                    <!-- 收缩/展开 -->\n                    <a-button shape=\"circle\" size=\"small\" @click=\"showSearchRow = !showSearchRow\">\n                        <template #icon>\n                            <icon-caret-up v-if=\"showSearchRow\" />\n                            <icon-caret-down v-else />\n                        </template>\n                    </a-button>\n                </a-space>\n            </a-row>\n\n            <!-- 数据展示区 -->\n            <a-row class=\"w-full flex-1 mt-3 overflow-y-auto\">\n                <a-table class=\"w-[100%]\" :scroll=\"{ minWidth: 600, y: '100%' }\" :columns=\"datatable.columns\" :data=\"datatable.records\" :loading=\"datatable.loading\" :pagination=\"false\" table-layout-fixed>\n                    <!-- 角色名称 -->\n                    <template #roleName=\"{ record }\">\n                        <a-link @click=\"detailBtnClick(record.roleId)\" icon>{{ record.roleName }}</a-link>\n                    </template>\n                    <!-- 角色状态 -->\n                    <template #status=\"{ record }\">\n                        <dict-convert :dict-data=\"dicts[proxy.DICT.commonNumberStatus]\" :dict-key=\"record.status\" />\n                    </template>\n                    <!-- 操作 -->\n                    <template #operation=\"{ record }\">\n                        <a-button v-perm=\"['basic:role:update']\" type=\"text\" size=\"mini\" @click=\"updateBtnClick(record.roleId)\">\n                            <template #icon>\n                                <icon-edit />\n                            </template>\n                            <template #default>修改</template>\n                        </a-button>\n                        <a-popconfirm content=\"确认要删除吗?\" @ok=\"deleteBtnOkClick(record.roleId)\">\n                            <a-button v-perm=\"['basic:role:delete']\" type=\"text\" status=\"danger\" size=\"mini\">\n                                <template #icon>\n                                    <icon-delete />\n                                </template>\n                                <template #default>删除</template>\n                            </a-button>\n                        </a-popconfirm>\n                    </template>\n                </a-table>\n            </a-row>\n\n            <!-- 分页 -->\n            <a-row class=\"w-full flex justify-end mt-2\">\n                <pagination v-if=\"datatable.total > 0\"\n                            v-model:page-num=\"searchForm.pageNum\"\n                            v-model:page-size=\"searchForm.pageSize\"\n                            :total=\"datatable.total\" @pagination=\"getPageList(false)\" />\n            </a-row>\n        </a-row>\n\n        <!-- 添加/修改 -->\n        <a-modal v-model:visible=\"modal.visible\" width=\"50%\" :esc-to-close=\"false\" :mask-closable=\"false\" draggable :footer=\"false\">\n            <template #title>{{ modal.title }}</template>\n            <component :is=\"modal.component\" :params=\"modal.params\" @ok=\"onOk\" @cancel=\"onCancel\" v-if=\"modal.visible\" />\n        </a-modal>\n\n    </a-card>\n</template>\n\n<script setup>\nimport {ref, reactive, getCurrentInstance, shallowRef} from 'vue'\nimport { getPageRoleListApi, deleteRoleByRoleIdApi } from '~/api/role.js'\nimport RoleEdit from \"~/pages/basic/role/RoleEdit.vue\";\nimport RoleDetail from \"~/pages/basic/role/RoleDetail.vue\";\n\n//全局实例\nconst {proxy} = getCurrentInstance()\n//加载字典\nconst dicts = proxy.LoadDicts([proxy.DICT.commonNumberStatus])\n//是否展示搜索区域\nconst showSearchRow = ref(true)\n//搜索参数表单\nconst searchForm = reactive({\n    //角色名称\n    roleName: null,\n    //角色编码\n    roleCode: null,\n    //角色状态\n    status: null,\n    //页码\n    pageNum: 1,\n    //条数\n    pageSize: 10\n})\n//数据列表\nconst datatable = reactive({\n    //列配置\n    columns: [\n        {title: '角色名称', dataIndex: 'roleName', slotName: 'roleName', align: 'center'},\n        {title: '角色编码', dataIndex: 'roleCode', align: 'center'},\n        {title: '排序', dataIndex: 'roleSort', align: 'center'},\n        {title: '角色状态', dataIndex: 'status', slotName: 'status', align: 'center'},\n        {title: '操作', slotName: 'operation', align: 'center', width: 160, fixed: 'right'}\n    ],\n    //加载\n    loading: false,\n    //数据列表\n    records: [],\n    //总条数\n    total: 0\n})\n//查询数据列表\nconst getPageList = (isReset = false) => {\n    if (isReset) {\n        searchForm.roleName = null\n        searchForm.roleCode = null\n        searchForm.status = null\n        searchForm.pageNum = 1\n        searchForm.pageSize = 10\n    }\n    datatable.loading = true\n    getPageRoleListApi(searchForm).then(res => {\n        datatable.records = res.records\n        datatable.total = res.total\n    }).finally(() => {\n        datatable.loading = false\n    })\n}\n//公共模态框\nconst modal = reactive({\n    //是否显示\n    visible: false,\n    //模态框标题\n    title: '角色管理',\n    //传递参数\n    params: {},\n    //组件名称\n    component: null\n});\n//添加按钮 -> 点击事件\nconst addBtnClick = () => {\n    modal.visible = true\n    modal.title = '添加角色'\n    modal.params = { operationType: proxy.operationType.add.type }\n    modal.component = shallowRef(RoleEdit)\n}\n//表格行数据 \"修改\" -> 点击\nconst updateBtnClick = (roleId) => {\n    modal.visible = true\n    modal.title = '修改角色'\n    modal.params = { operationType: proxy.operationType.update.type, roleId: roleId }\n    modal.component = shallowRef(RoleEdit)\n}\n//表格行数据 \"查看\" -> 点击\nconst detailBtnClick = (roleId) => {\n    modal.visible = true\n    modal.title = '角色详细信息'\n    modal.params = { operationType: proxy.operationType.detail.type, roleId: roleId }\n    modal.component = shallowRef(RoleDetail)\n}\n//表格行数据 \"删除\" -> 确认\nconst deleteBtnOkClick = (roleId) => {\n    deleteRoleByRoleIdApi(roleId).then(() => {\n        proxy.$msg.success(proxy.operationType.delete.success)\n        //刷新列表\n        getPageList()\n    })\n}\n//模态框 -> 确认\nconst onOk = () => {\n    modal.visible = false\n    //查询数据列表\n    getPageList()\n}\n//模态框 -> 取消\nconst onCancel = () => {\n    modal.visible = false\n}\n\n//查询数据列表\ngetPageList()\n</script>\n<style scoped></style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/storage/Local.vue",
    "content": "<template>\n    <div>\n        <!-- 修改时展示 -->\n        <a-form-item field=\"storagePath\" label=\"本地存储路径\" v-if=\"proxy.operationType.update.type === optType\"\n            tooltip=\"指的是要将文件存放在什么位置。Windows系统，如`E:\\temp`。Linux系统，如`/opt/temp`\">\n            <a-input v-model=\"form.storagePath\" placeholder=\"本地存储路径\" />\n        </a-form-item>\n        <div v-if=\"proxy.operationType.detail.type !== optType\" class=\"font-bold mb-3\">注意：本地存储路径新增或修改后，需重启项目后生效</div>\n\n        <!-- 查看时展示 -->\n        <a-descriptions :column=\"1\" bordered v-if=\"proxy.operationType.detail.type === optType\">\n            <a-descriptions-item label=\"本地存储路径\">{{ form.storagePath }}</a-descriptions-item>\n        </a-descriptions>\n\n    </div>\n</template>\n<script setup>\nimport {getCurrentInstance, reactive, watch} from \"vue\";\n//全局实例\nconst {proxy} = getCurrentInstance()\n//接收父组件参数\nconst props = defineProps({\n    params: {\n        type: String,\n        default: () => ''\n    },\n    //操作类型，区分修改(可编辑)和查看(不可编辑)\n    optType: {\n        type: String,\n        default: () => ''\n    }\n})\n//表单\nconst form = reactive({\n    //本地存储路径\n    storagePath: null\n})\n//获取存储配置\nconst getStorageConfig = () => {\n    return JSON.stringify(form)\n}\n//暴露组件方法\ndefineExpose({getStorageConfig})\n//监听参数变化\nwatch(() => props.params, (newVal, oldVal) => {\n    //有值则回显\n    if (props.params) {\n        let data = JSON.parse(props.params)\n        form.storagePath = data.storagePath\n    }\n}, { deep: true, immediate: true })\n</script>\n<style scoped>\n</style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/storage/Minio.vue",
    "content": "<template>\n    <div>\n\n        <!-- 修改时展示 -->\n        <a-row justify=\"space-between\" v-if=\"proxy.operationType.update.type === optType\">\n            <a-form-item class=\"w-[49%]\" field=\"accessKey\" label=\"访问密钥\">\n                <a-input v-model=\"form.accessKey\" placeholder=\"accessKey\" />\n            </a-form-item>\n            <a-form-item class=\"w-[49%]\" field=\"secretKey\" label=\"私有密钥\">\n                <a-input v-model=\"form.secretKey\" placeholder=\"secretKey\" />\n            </a-form-item>\n        </a-row>\n\n        <a-row justify=\"space-between\" v-if=\"proxy.operationType.update.type === optType\">\n            <a-form-item class=\"w-[49%]\" field=\"endPoint\" label=\"域名\" tooltip=\"MinIO服务器的URL\">\n                <a-input v-model=\"form.endPoint\" placeholder=\"endPoint\" />\n            </a-form-item>\n            <a-form-item class=\"w-[49%]\" field=\"bucketName\" label=\"桶名称\">\n                <a-input v-model=\"form.bucketName\" placeholder=\"bucketName\" />\n            </a-form-item>\n        </a-row>\n\n        <!-- 查看时展示 -->\n        <a-descriptions :column=\"2\" bordered  v-if=\"proxy.operationType.detail.type === optType\">\n            <a-descriptions-item label=\"访问密钥\">{{ form.accessKey }}</a-descriptions-item>\n            <a-descriptions-item label=\"私有密钥\">{{ form.secretKey }}</a-descriptions-item>\n            <a-descriptions-item label=\"域名\">{{ form.endPoint }}</a-descriptions-item>\n            <a-descriptions-item label=\"桶名称\">{{ form.bucketName }}</a-descriptions-item>\n        </a-descriptions>\n\n    </div>\n</template>\n<script setup>\nimport {getCurrentInstance, reactive, watch} from \"vue\";\n//全局实例\nconst {proxy} = getCurrentInstance()\n//接收父组件参数\nconst props = defineProps({\n    params: {\n        type: String,\n        default: () => ''\n    },\n    //操作类型，区分修改(可编辑)和查看(不可编辑)\n    optType: {\n        type: String,\n        default: () => ''\n    }\n})\n//表单\nconst form = reactive({\n    accessKey: null,\n    secretKey: null,\n    endPoint: null,\n    bucketName: null\n})\n//获取存储配置\nconst getStorageConfig = () => {\n    return JSON.stringify(form)\n}\n//暴露组件方法\ndefineExpose({getStorageConfig})\n//监听参数变化\nwatch(() => props.params, (newVal, oldVal) => {\n    //有值则回显\n    if (props.params) {\n        let data = JSON.parse(props.params)\n        form.accessKey = data.accessKey\n        form.secretKey = data.secretKey\n        form.endPoint = data.endPoint\n        form.bucketName = data.bucketName\n    }\n}, { deep: true, immediate: true })\n</script>\n<style scoped>\n</style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/storage/QiNiu.vue",
    "content": "<template>\n    <div>\n\n        <!-- 修改时展示 -->\n        <a-row justify=\"space-between\" v-if=\"proxy.operationType.update.type === optType\">\n            <a-form-item class=\"w-[49%]\" field=\"accessKey\" label=\"访问密钥\">\n                <a-input v-model=\"form.accessKey\" placeholder=\"accessKey\" />\n            </a-form-item>\n            <a-form-item class=\"w-[49%]\" field=\"secretKey\" label=\"私有密钥\">\n                <a-input v-model=\"form.secretKey\" placeholder=\"secretKey\" />\n            </a-form-item>\n        </a-row>\n        <a-row justify=\"space-between\" v-if=\"proxy.operationType.update.type === optType\">\n            <a-form-item class=\"w-[49%]\" field=\"endPoint\" label=\"域名\" tooltip=\"访问文件的域名，末尾不要带斜杠。比如：http://file.baidu.com\">\n                <a-input v-model=\"form.endPoint\" placeholder=\"endPoint\" />\n            </a-form-item>\n            <a-form-item class=\"w-[49%]\" field=\"bucketName\" label=\"桶名称\">\n                <a-input v-model=\"form.bucketName\" placeholder=\"bucketName\" />\n            </a-form-item>\n        </a-row>\n        <a-row justify=\"space-between\" v-if=\"proxy.operationType.update.type === optType\">\n            <a-form-item class=\"w-[49%]\" field=\"regionId\" label=\"区域ID\" tooltip=\"请查阅：https://developer.qiniu.com/kodo/1671/region-endpoint-fq\">\n                <a-input v-model=\"form.regionId\" placeholder=\"regionId\" />\n            </a-form-item>\n        </a-row>\n\n        <!-- 查看时展示 -->\n        <a-descriptions :column=\"2\" bordered  v-if=\"proxy.operationType.detail.type === optType\">\n            <a-descriptions-item label=\"访问密钥\">{{ form.accessKey }}</a-descriptions-item>\n            <a-descriptions-item label=\"私有密钥\">{{ form.secretKey }}</a-descriptions-item>\n            <a-descriptions-item label=\"域名\">{{ form.endPoint }}</a-descriptions-item>\n            <a-descriptions-item label=\"桶名称\">{{ form.bucketName }}</a-descriptions-item>\n            <a-descriptions-item label=\"存储区域ID\">{{ form.regionId }}</a-descriptions-item>\n        </a-descriptions>\n\n    </div>\n</template>\n<script setup>\nimport {getCurrentInstance, reactive, watch} from \"vue\";\n//全局实例\nconst {proxy} = getCurrentInstance()\n//接收父组件参数\nconst props = defineProps({\n    params: {\n        type: String,\n        default: () => ''\n    },\n    //操作类型，区分修改(可编辑)和查看(不可编辑)\n    optType: {\n        type: String,\n        default: () => ''\n    }\n})\n//表单\nconst form = reactive({\n    accessKey: null,\n    secretKey: null,\n    endPoint: null,\n    bucketName: null,\n    regionId: null\n})\n//获取存储配置\nconst getStorageConfig = () => {\n    return JSON.stringify(form)\n}\n//暴露组件方法\ndefineExpose({getStorageConfig})\n//监听参数变化\nwatch(() => props.params, (newVal, oldVal) => {\n    //有值则回显\n    if (props.params) {\n        let data = JSON.parse(props.params)\n        form.accessKey = data.accessKey\n        form.secretKey = data.secretKey\n        form.endPoint = data.endPoint\n        form.bucketName = data.bucketName\n        form.regionId = data.regionId\n    }\n}, { deep: true, immediate: true })\n</script>\n<style scoped>\n</style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/storage/StorageDetail.vue",
    "content": "<template>\n    <a-spin class=\"w-[100%]\" :size=\"35\" :loading=\"spinLoading\" tip=\"正在处理, 请稍候...\">\n        <a-descriptions :column=\"2\" bordered>\n            <a-descriptions-item label=\"存储名称\">{{ form.storageName }}</a-descriptions-item>\n            <a-descriptions-item label=\"存储状态\">\n                <dict-convert :dict-data=\"dicts[proxy.DICT.commonNumberStatus]\" :dict-key=\"form.status\" />\n            </a-descriptions-item>\n            <a-descriptions-item label=\"存储类型\">\n                <dict-convert :dict-data=\"dicts[proxy.DICT.storageType]\" :dict-key=\"form.storageType\" />\n            </a-descriptions-item>\n            <a-descriptions-item label=\"存储说明\">{{ form.description }}</a-descriptions-item>\n        </a-descriptions>\n\n        <a-divider orientation=\"left\" style=\"margin-top: 2.5em;\">存储配置</a-divider>\n        <!-- 本地存储 -->\n        <local ref=\"storageConfigRef\" :params=\"form.storageConfig\" :optType=\"proxy.operationType.detail.type\" v-if=\"form.storageType === 'local'\" />\n\n        <!-- minio -->\n        <minio ref=\"storageConfigRef\" :params=\"form.storageConfig\" :optType=\"proxy.operationType.detail.type\" v-if=\"form.storageType === 'minio'\" />\n\n        <!-- 七牛云 -->\n        <minio ref=\"storageConfigRef\" :params=\"form.storageConfig\" :optType=\"proxy.operationType.detail.type\" v-if=\"form.storageType === 'qiniu'\" />\n    </a-spin>\n</template>\n<script setup>\nimport { ref, reactive, getCurrentInstance, watch } from 'vue'\nimport {getStorageByStorageIdApi} from \"~/api/storage.js\";\nimport Local from \"~/pages/basic/storage/Local.vue\";\nimport Minio from \"~/pages/basic/storage/Minio.vue\";\n\n//全局实例\nconst { proxy } = getCurrentInstance()\n//加载字典\nconst dicts = proxy.LoadDicts([proxy.DICT.commonNumberStatus, proxy.DICT.storageType, proxy.DICT.yesNo])\n//加载中...\nconst spinLoading = ref(false)\n//接收父组件参数\nconst props = defineProps({\n    params: {\n        type: Object,\n        default: () => {}\n    }\n})\n//表单\nconst form = reactive({\n    //存储ID\n    storageId: null,\n    //存储名称\n    storageName: null,\n    //存储类型\n    storageType: null,\n    //是否默认使用\n    storageDefault: null,\n    //说明\n    description: null,\n    //存储配置\n    storageConfig: null,\n    //状态\n    status: null\n})\n//加载岗位详细信息\nconst loadStorageInfo = (storageId) => {\n    spinLoading.value = true\n    getStorageByStorageIdApi(storageId).then(res => {\n        //数据赋值\n        if (res) {\n            for (let key in res) {\n                if (form.hasOwnProperty(key)) {\n                    form[key] = res[key]\n                }\n            }\n        }\n    }).finally(() => {\n        spinLoading.value = false\n    })\n}\n//监听参数变化\nwatch(() => props.params, (newVal, oldVal) => {\n    //存储ID\n    if (props.params.storageId) {\n        //加载存储信息\n        loadStorageInfo(props.params.storageId)\n    }\n}, { deep: true, immediate: true })\n</script>\n<style scoped></style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/storage/StorageEdit.vue",
    "content": "<template>\n    <a-spin class=\"w-[100%]\" :size=\"35\" :loading=\"spinLoading\" tip=\"正在处理, 请稍候...\">\n        <a-form :model=\"form\" ref=\"formRef\" layout=\"vertical\" :rules=\"rules\" auto-label-width>\n            <a-row justify=\"space-between\">\n                <a-form-item class=\"w-[49%]\" field=\"storageName\" label=\"存储名称\" required>\n                    <a-input v-model=\"form.storageName\" placeholder=\"存储名称\" />\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" field=\"status\" label=\"存储状态\" required v-if=\"props.params.operationType === proxy.operationType.update.type\">\n                    <a-select v-model=\"form.status\" placeholder=\"存储状态\" allow-clear>\n                        <a-option v-for=\"(d, index) in dicts[proxy.DICT.commonNumberStatus]\" :key=\"index\" :value=\"d.dictKey\" :label=\"d.dictValue\" />\n                    </a-select>\n                </a-form-item>\n            </a-row>\n\n            <a-form-item field=\"description\" label=\"说明\">\n                <a-textarea v-model=\"form.description\" placeholder=\"说明\" />\n            </a-form-item>\n\n            <a-divider orientation=\"left\">存储配置</a-divider>\n            <a-row justify=\"space-between\">\n                <a-form-item class=\"w-[49%]\" field=\"storageType\" label=\"存储类型\" required>\n                    <a-select v-model=\"form.storageType\" placeholder=\"存储类型\" allow-clear>\n                        <a-option v-for=\"(d, index) in dicts[proxy.DICT.storageType]\" :key=\"index\" :value=\"d.dictKey\" :label=\"d.dictValue\" />\n                    </a-select>\n                </a-form-item>\n            </a-row>\n\n            <!-- 本地存储 -->\n            <local ref=\"storageConfigRef\" :params=\"form.storageConfig\" :optType=\"proxy.operationType.update.type\" v-if=\"form.storageType === 'local'\" />\n\n            <!-- minio -->\n            <minio ref=\"storageConfigRef\" :params=\"form.storageConfig\" :optType=\"proxy.operationType.update.type\" v-if=\"form.storageType === 'minio'\" />\n\n            <!-- 七牛云 -->\n            <qi-niu ref=\"storageConfigRef\" :params=\"form.storageConfig\" :optType=\"proxy.operationType.update.type\" v-if=\"form.storageType === 'qiniu'\" />\n\n        </a-form>\n\n        <!-- 分割线 -->\n        <a-divider class=\"mt-0\" />\n\n        <div class=\"flex justify-end\">\n            <a-space>\n                <a-button @click=\"cancelBtnClick()\">取消</a-button>\n                <a-button type=\"primary\" @click=\"okBtnClick()\">确定</a-button>\n            </a-space>\n        </div>\n    </a-spin>\n</template>\n\n<script setup>\nimport { ref, reactive, getCurrentInstance, watch } from 'vue'\nimport {addStorageApi, updateStorageByStorageIdApi, getStorageByStorageIdApi} from \"~/api/storage.js\";\nimport Local from \"~/pages/basic/storage/Local.vue\";\nimport Minio from \"~/pages/basic/storage/Minio.vue\";\nimport QiNiu from \"~/pages/basic/storage/QiNiu.vue\";\n\n//全局实例\nconst {proxy} = getCurrentInstance()\n//加载字典\nconst dicts = proxy.LoadDicts([proxy.DICT.commonNumberStatus, proxy.DICT.storageType, proxy.DICT.yesNo])\n//接收父组件参数\nconst props = defineProps({\n    params: {\n        type: Object,\n        default: () => {}\n    }\n})\n//父组件函数\nconst emits = defineEmits(['ok', 'cancel'])\n//加载中...\nconst spinLoading = ref(false)\n//表单ref\nconst formRef = ref(null)\n//表单\nconst form = reactive({\n    //存储ID\n    storageId: null,\n    //存储名称\n    storageName: null,\n    //存储类型\n    storageType: null,\n    //说明\n    description: null,\n    //存储配置\n    storageConfig: null,\n    //状态\n    status: null\n})\n//表单校验规则\nconst rules = {\n    storageName: [{required: true, message: '存储名称不能为空', trigger: 'submit'}],\n    storageType: [{required: true, message: '存储类型不能为空', trigger: 'submit'}],\n}\n//存储配置\nconst storageConfigRef = ref()\n//确定 -> 点击\nconst okBtnClick = () => {\n    //表单验证\n    formRef.value.validate((valid) => {\n        if (valid) {return false}\n        if (!storageConfigRef.value) {\n            return false\n        }\n        form.storageConfig = storageConfigRef.value.getStorageConfig()\n        //添加\n        if (props.params.operationType === proxy.operationType.add.type) {\n            spinLoading.value = true\n            addStorageApi(form).then(() => {\n                proxy.$msg.success(proxy.operationType.add.success)\n                emits('ok')\n            }).finally(() => {\n                spinLoading.value = false\n            })\n        }\n        //修改\n        if (props.params.operationType === proxy.operationType.update.type) {\n            spinLoading.value = true\n            updateStorageByStorageIdApi(form).then(() => {\n                proxy.$msg.success(proxy.operationType.update.success)\n                emits('ok')\n            }).finally(() => {\n                spinLoading.value = false\n            })\n        }\n    })\n}\n//取消 -> 点击\nconst cancelBtnClick = () => {\n    emits('cancel')\n}\n//加载存储详细信息\nconst loadStorageInfo = (storageId) => {\n    spinLoading.value = true\n    getStorageByStorageIdApi(storageId).then(res => {\n        //数据赋值\n        if (res) {\n            for (let key in res) {\n                if (form.hasOwnProperty(key)) {\n                    form[key] = res[key]\n                }\n            }\n        }\n    }).finally(() => {\n        spinLoading.value = false\n    })\n}\n//监听参数变化\nwatch(() => props.params, (newVal, oldVal) => {\n    //存储ID\n    if (props.params.storageId) {\n        //加载存储信息\n        loadStorageInfo(props.params.storageId)\n    }\n}, { deep: true, immediate: true })\n</script>\n<style scoped></style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/storage/StorageMgt.vue",
    "content": "<template>\n    <a-card :body-style=\"{height: 'calc(100vh - 125px)'}\">\n\n        <!-- 数据列表 -->\n        <a-row class=\"w-full h-full flex flex-col overflow-x-auto overflow-y-hidden\">\n            <!-- 查询条件 -->\n            <a-row class=\"w-full\" v-if=\"showSearchRow\">\n                <a-form :model=\"searchForm\" layout=\"inline\" label-align=\"left\" size=\"small\">\n                    <a-form-item field=\"storageName\" label=\"存储名称\">\n                        <a-input v-model=\"searchForm.storageName\" placeholder=\"存储名称\" />\n                    </a-form-item>\n                    <a-form-item field=\"storageType\" label=\"存储类型\">\n                        <a-select v-model=\"searchForm.storageType\" placeholder=\"存储类型\" allow-clear>\n                            <a-option v-for=\"(d, index) in dicts[proxy.DICT.storageType]\" :key=\"index\" :value=\"d.dictKey\" :label=\"d.dictValue\" />\n                        </a-select>\n                    </a-form-item>\n                    <a-form-item field=\"status\" label=\"存储状态\">\n                        <a-select v-model=\"searchForm.status\" placeholder=\"岗位状态\" allow-clear>\n                            <a-option v-for=\"(d, index) in dicts[proxy.DICT.commonNumberStatus]\" :key=\"index\" :value=\"d.dictKey\" :label=\"d.dictValue\" />\n                        </a-select>\n                    </a-form-item>\n                </a-form>\n                <a-row justify=\"center\" class=\"w-full mt-2\">\n                    <a-space>\n                        <a-button @click=\"getPageList(true)\">\n                            <template #icon><icon-sync /></template>\n                            <template #default>重置</template>\n                        </a-button>\n                        <a-button type=\"primary\" @click=\"getPageList(false)\">\n                            <template #icon><icon-search /></template>\n                            <template #default>查询</template>\n                        </a-button>\n                    </a-space>\n                </a-row>\n            </a-row>\n\n            <!-- 分割线 -->\n            <a-divider v-if=\"showSearchRow\" class=\"mt-2\" />\n\n            <!-- 数据操作区 -->\n            <a-row class=\"w-full flex justify-between\">\n                <a-space>\n                    <!-- 添加 -->\n                    <a-button v-perm=\"['basic:storage:add']\" type=\"primary\" size=\"small\" @click=\"addBtnClick()\">\n                        <template #icon><icon-plus /></template>\n                        <template #default>添加</template>\n                    </a-button>\n                </a-space>\n                <a-space>\n                    <!-- 刷新 -->\n                    <a-button shape=\"circle\" size=\"small\" @click=\"getPageList(false)\">\n                        <template #icon><icon-refresh /></template>\n                    </a-button>\n                    <!-- 收缩/展开 -->\n                    <a-button shape=\"circle\" size=\"small\" @click=\"showSearchRow = !showSearchRow\">\n                        <template #icon>\n                            <icon-caret-up v-if=\"showSearchRow\" />\n                            <icon-caret-down v-else />\n                        </template>\n                    </a-button>\n                </a-space>\n            </a-row>\n\n            <!-- 数据展示区 -->\n            <a-row class=\"w-full flex-1 mt-3 overflow-y-auto\">\n                <a-table class=\"w-[100%]\" :scroll=\"{ minWidth: 600, y: '100%' }\" :columns=\"datatable.columns\" :data=\"datatable.records\" :loading=\"datatable.loading\" :pagination=\"false\" table-layout-fixed>\n                    <!-- 存储名称 -->\n                    <template #storageName=\"{ record }\">\n                        <a-link @click=\"detailBtnClick(record.storageId)\" icon>{{ record.storageName }}</a-link>\n                    </template>\n                    <!-- 存储类型 -->\n                    <template #storageType=\"{ record }\">\n                        <dict-convert :dict-data=\"dicts[proxy.DICT.storageType]\" :dict-key=\"record.storageType\" />\n                    </template>\n                    <!-- 存储状态 -->\n                    <template #status=\"{ record }\">\n                        <dict-convert :dict-data=\"dicts[proxy.DICT.commonNumberStatus]\" :dict-key=\"record.status\" />\n                    </template>\n                    <!-- 操作 -->\n                    <template #operation=\"{ record }\">\n                        <a-button v-perm=\"['basic:storage:update']\" type=\"text\" size=\"mini\" @click=\"updateBtnClick(record.storageId)\">\n                            <template #icon>\n                                <icon-edit />\n                            </template>\n                            <template #default>修改</template>\n                        </a-button>\n                        <a-popconfirm content=\"确认要删除吗?\" @ok=\"deleteBtnOkClick(record.storageId)\">\n                            <a-button v-perm=\"['basic:storage:delete']\" type=\"text\" status=\"danger\" size=\"mini\">\n                                <template #icon>\n                                    <icon-delete />\n                                </template>\n                                <template #default>删除</template>\n                            </a-button>\n                        </a-popconfirm>\n                    </template>\n                </a-table>\n            </a-row>\n\n            <!-- 分页 -->\n            <a-row class=\"w-full flex justify-end mt-2\">\n                <pagination v-if=\"datatable.total > 0\"\n                            v-model:page-num=\"searchForm.pageNum\"\n                            v-model:page-size=\"searchForm.pageSize\"\n                            :total=\"datatable.total\" @pagination=\"getPageList(false)\" />\n            </a-row>\n        </a-row>\n\n        <!-- 添加/修改 -->\n        <a-modal v-model:visible=\"modal.visible\" width=\"700px\" :esc-to-close=\"false\" :mask-closable=\"false\" draggable :footer=\"false\">\n            <template #title>{{ modal.title }}</template>\n            <component :is=\"modal.component\" :params=\"modal.params\" @ok=\"onOk\" @cancel=\"onCancel\" v-if=\"modal.visible\" />\n        </a-modal>\n\n    </a-card>\n</template>\n\n<script setup>\nimport {ref, reactive, getCurrentInstance, shallowRef} from 'vue'\nimport StorageEdit from \"~/pages/basic/storage/StorageEdit.vue\";\nimport StorageDetail from \"~/pages/basic/storage/StorageDetail.vue\";\nimport {deleteStorageByStorageIdApi, getPageStorageListApi} from \"~/api/storage.js\";\n\n//全局实例\nconst {proxy} = getCurrentInstance()\n//加载字典\nconst dicts = proxy.LoadDicts([proxy.DICT.commonNumberStatus, proxy.DICT.storageType])\n//是否展示搜索区域\nconst showSearchRow = ref(true)\n//搜索参数表单\nconst searchForm = reactive({\n    //存储名称\n    storageName: null,\n    //存储类型\n    storageType: null,\n    //存储状态\n    status: null,\n    //页码\n    pageNum: 1,\n    //条数\n    pageSize: 10\n})\n//数据列表\nconst datatable = reactive({\n    //列配置\n    columns: [\n        {title: '存储名称', dataIndex: 'storageName', slotName: 'storageName', align: 'center'},\n        {title: '存储类型', dataIndex: 'storageType', slotName: 'storageType', align: 'center'},\n        {title: '说明', dataIndex: 'description', width: 200, align: 'center', ellipsis: true, tooltip: true},\n        {title: '状态', dataIndex: 'status', slotName: 'status', align: 'center'},\n        {title: '操作', slotName: 'operation', align: 'center', width: 160, fixed: 'right'}\n    ],\n    //加载\n    loading: false,\n    //数据列表\n    records: [],\n    //总条数\n    total: 0\n})\n//查询数据列表\nconst getPageList = (isReset = false) => {\n    if (isReset) {\n        searchForm.postName = null\n        searchForm.postCode = null\n        searchForm.status = null\n        searchForm.pageNum = 1\n        searchForm.pageSize = 10\n    }\n    datatable.loading = true\n    getPageStorageListApi(searchForm).then(res => {\n        datatable.records = res.records\n        datatable.total = res.total\n    }).finally(() => {\n        datatable.loading = false\n    })\n}\n//公共模态框\nconst modal = reactive({\n    //是否显示\n    visible: false,\n    //模态框标题\n    title: '存储管理',\n    //传递参数\n    params: {},\n    //组件名称\n    component: null\n});\n//添加按钮 -> 点击事件\nconst addBtnClick = () => {\n    modal.visible = true\n    modal.title = '添加存储'\n    modal.params = { operationType: proxy.operationType.add.type }\n    modal.component = shallowRef(StorageEdit)\n}\n//表格行数据 \"修改\" -> 点击\nconst updateBtnClick = (storageId) => {\n    modal.visible = true\n    modal.title = '修改存储'\n    modal.params = { operationType: proxy.operationType.update.type, storageId: storageId }\n    modal.component = shallowRef(StorageEdit)\n}\n//表格行数据 \"查看\" -> 点击\nconst detailBtnClick = (storageId) => {\n    modal.visible = true\n    modal.title = '存储详细信息'\n    modal.params = { operationType: proxy.operationType.detail.type, storageId: storageId }\n    modal.component = shallowRef(StorageDetail)\n}\n//表格行数据 \"删除\" -> 确认\nconst deleteBtnOkClick = (postId) => {\n    deleteStorageByStorageIdApi(postId).then(() => {\n        proxy.$msg.success(proxy.operationType.delete.success)\n        //刷新列表\n        getPageList()\n    })\n}\n//模态框 -> 确认\nconst onOk = () => {\n    modal.visible = false\n    //查询数据列表\n    getPageList()\n}\n//模态框 -> 取消\nconst onCancel = () => {\n    modal.visible = false\n}\n//查询数据列表\ngetPageList()\n</script>\n<style scoped></style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/system/Swagger.vue",
    "content": "<template>\n    <div class=\"w-[100%]\" style=\"height: calc(100% - 50px)\" v-if=\"hasPerm(['system:swagger'])\">\n        <iframe class=\"w-[100%] h-[100%]\" :src=\"swaggerUrl\"></iframe>\n    </div>\n    <div class=\"w-[100%] flex items-center\" style=\"height: calc(100% - 50px)\" v-else>\n        <a-empty>\n            <template #image>\n                <icon-close-circle-fill />\n            </template>\n            <span>暂无访问权限，请联系管理员分配权限</span>\n        </a-empty>\n    </div>\n</template>\n<script setup>\nimport {ref} from \"vue\";\nimport {hasPerm} from \"~/utils/sys.js\";\n\nconst swaggerUrl = ref(import.meta.env.VITE_API_BASE_PREFIX + '/doc.html')\n</script>\n<style scoped></style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/tenant/TenantDetail.vue",
    "content": "<template>\n    <a-spin class=\"w-[100%]\" :size=\"35\" :loading=\"spinLoading\" tip=\"正在处理, 请稍候...\">\n        <a-descriptions :column=\"2\" bordered>\n            <a-descriptions-item label=\"租户名称\">{{ form.tenantName }}</a-descriptions-item>\n            <a-descriptions-item label=\"租户套餐\">\n                <dict-convert :dict-data=\"dicts[proxy.DICT.tenantPackageList]\" :dict-key=\"form.packageId\" />\n            </a-descriptions-item>\n            <a-descriptions-item label=\"联系人\">{{ form.contactName }}</a-descriptions-item>\n            <a-descriptions-item label=\"联系人手机\">{{ form.phone }}</a-descriptions-item>\n            <a-descriptions-item label=\"联系人邮箱\">{{ form.email }}</a-descriptions-item>\n            <a-descriptions-item label=\"账号额度\">{{ form.accountCount }}</a-descriptions-item>\n            <a-descriptions-item label=\"过期时间\">{{ form.expireTime }}</a-descriptions-item>\n            <a-descriptions-item label=\"数据隔离方式\">\n                <dict-convert :dict-data=\"dicts[proxy.DICT.tenantDataIsolation]\" :dict-key=\"form.dataIsolation\" />\n            </a-descriptions-item>\n            <a-descriptions-item label=\"数据源名称\">{{ form.datasource }}</a-descriptions-item>\n            <a-descriptions-item label=\"文件存储方式\">\n                <dict-convert :dict-data=\"dicts[proxy.DICT.storageList]\" :dict-key=\"form.storageId\" />\n            </a-descriptions-item>\n            <a-descriptions-item label=\"租户状态\">\n                <dict-convert :dict-data=\"dicts[proxy.DICT.commonNumberStatus]\" :dict-key=\"form.status\" />\n            </a-descriptions-item>\n            <a-descriptions-item label=\"备注\">{{ form.remark }}</a-descriptions-item>\n        </a-descriptions>\n    </a-spin>\n</template>\n<script setup>\nimport { ref, reactive, getCurrentInstance, watch } from 'vue'\nimport { getTenantByTenantIdApi } from \"~/api/tenant.js\";\n\n//全局实例\nconst { proxy } = getCurrentInstance()\n//加载字典\nconst dicts = proxy.LoadDicts([proxy.DICT.commonNumberStatus, proxy.DICT.tenantPackageList, proxy.DICT.tenantDataIsolation, proxy.DICT.storageList])\n//加载中...\nconst spinLoading = ref(false)\n//接收父组件参数\nconst props = defineProps({\n    params: {\n        type: Object,\n        default: () => {}\n    }\n})\n//表单\nconst form = reactive({\n    //租户ID\n    tenantId: null,\n    //租户名称\n    tenantName: null,\n    //租户套餐\n    packageId: null,\n    //联系人\n    contactName: null,\n    //联系人手机\n    phone: null,\n    //联系人邮箱\n    email: null,\n    //账号额度\n    accountCount: null,\n    //过期时间\n    expireTime: null,\n    // 数据隔离方式\n    dataIsolation: null,\n    //数据源名称\n    datasource: null,\n    //存储ID\n    storageId: null,\n    //租户状态\n    status: null,\n    //备注\n    remark: null\n})\n//加载租户详细信息\nconst loadTenantInfo = (tenantId) => {\n    spinLoading.value = true\n    getTenantByTenantIdApi(tenantId).then(res => {\n        //数据赋值\n        if (res) {\n            for (let key in res) {\n                if (form.hasOwnProperty(key)) {\n                    form[key] = res[key]\n                }\n            }\n        }\n    }).finally(() => {\n        spinLoading.value = false\n    })\n}\n//监听参数变化\nwatch(() => props.params, (newVal, oldVal) => {\n    //租户ID\n    if (props.params.tenantId) {\n        //查询数据\n        loadTenantInfo(props.params.tenantId)\n    }\n}, { deep: true, immediate: true })\n</script>\n<style scoped></style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/tenant/TenantEdit.vue",
    "content": "<template>\n    <a-spin class=\"w-[100%]\" :size=\"35\" :loading=\"spinLoading\" tip=\"正在处理, 请稍候...\">\n        <a-form :model=\"form\" ref=\"formRef\" layout=\"vertical\" :rules=\"rules\" auto-label-width>\n            <div class=\"flex justify-between\" style=\"flex-wrap: wrap;\">\n                <a-form-item class=\"w-[49%]\" field=\"tenantName\" label=\"租户名称\" required>\n                    <a-input v-model=\"form.tenantName\" placeholder=\"租户名称\" />\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" field=\"packageId\" label=\"租户套餐\" required>\n                    <a-select v-model=\"form.packageId\" placeholder=\"租户套餐\" allow-clear>\n                        <a-option v-for=\"(d, index) in dicts[proxy.DICT.tenantPackageList]\" :key=\"index\" :value=\"d.dictKey\" :label=\"d.dictValue\" />\n                    </a-select>\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" field=\"expireTime\" label=\"过期时间\" required>\n                    <a-date-picker class=\"w-[100%]\" v-model=\"form.expireTime\" show-time format=\"YYYY-MM-DD HH:mm:ss\" disabled-input placeholder=\"过期时间\" />\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" field=\"accountCount\" label=\"账号额度\" required tooltip=\"表示该租户下可以创建多少个用户账号\">\n                    <a-input-number v-model=\"form.accountCount\" :min=\"0\" placeholder=\"账号额度\" />\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" field=\"dataIsolation\" label=\"数据隔离方式\" required tooltip=\"多租户数据隔离方式\">\n                    <a-select v-model=\"form.dataIsolation\" placeholder=\"数据隔离方式\" allow-clear>\n                        <a-option v-for=\"(d, index) in dicts[proxy.DICT.tenantDataIsolation]\" :key=\"index\" :value=\"d.dictKey\" :label=\"d.dictValue\" />\n                    </a-select>\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" field=\"storageId\" label=\"文件存储方式\" required>\n                    <a-select v-model=\"form.storageId\" placeholder=\"文件存储方式\" allow-clear>\n                        <a-option v-for=\"(d, index) in dicts[proxy.DICT.storageList]\" :key=\"index\" :value=\"d.dictKey\" :label=\"d.dictValue\" />\n                    </a-select>\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" field=\"status\" label=\"租户状态\" v-if=\"props.params.operationType === proxy.operationType.update.type\">\n                    <a-select v-model=\"form.status\" placeholder=\"租户状态\" allow-clear>\n                        <a-option v-for=\"(d, index) in dicts[proxy.DICT.commonNumberStatus]\" :key=\"index\" :value=\"d.dictKey\" :label=\"d.dictValue\" />\n                    </a-select>\n                </a-form-item>\n                <a-form-item class=\"w-[100%]\" field=\"remark\" label=\"备注\">\n                    <a-textarea v-model=\"form.remark\" placeholder=\"备注\" />\n                </a-form-item>\n\n\n                <!-- 数据源名称：选择隔离方式为db时，需要配置数据源信息 -->\n                <template v-if=\"form.dataIsolation === 'db'\">\n                    <a-divider orientation=\"left\">\n                        租户的数据源信息\n                    </a-divider>\n                    <a-form-item class=\"w-[49%]\" field=\"datasourceName\" label=\"数据源名称\">\n                        <a-input v-model=\"form.tenantDatasource.datasourceName\" placeholder=\"数据源名称\" />\n                    </a-form-item>\n                    <a-form-item class=\"w-[49%]\" field=\"datasourceUrl\" label=\"数据源连接\">\n                        <a-textarea v-model=\"form.tenantDatasource.datasourceUrl\" placeholder=\"数据源连接\" />\n                    </a-form-item>\n                    <a-form-item class=\"w-[49%]\" field=\"username\" label=\"数据源用户名\">\n                        <a-input v-model=\"form.tenantDatasource.username\" placeholder=\"数据源用户名\" />\n                    </a-form-item>\n                    <a-form-item class=\"w-[49%]\" field=\"password\" label=\"数据源密码\">\n                        <a-input-password v-model=\"form.tenantDatasource.password\" placeholder=\"数据源密码\" />\n                    </a-form-item>\n                </template>\n\n                <!-- 租户与用户绑定 -->\n                <a-divider orientation=\"left\" v-if=\"props.params.operationType === proxy.operationType.add.type\">\n                    租户的用户信息\n                </a-divider>\n\n                <!-- 新增时，添加用户信息 -->\n                <a-form-item class=\"w-[49%]\" field=\"username\" label=\"用户账号\" tooltip=\"用户登录系统使用的账号\" v-if=\"props.params.operationType === proxy.operationType.add.type\">\n                    <a-input v-model=\"form.user.username\" placeholder=\"用户账号\" />\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" field=\"password\" label=\"用户密码\" v-if=\"props.params.operationType === proxy.operationType.add.type\">\n                    <a-input-password v-model=\"form.user.password\" placeholder=\"用户密码\" />\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" field=\"nickname\" label=\"用户昵称\" tooltip=\"用户的网名\" v-if=\"props.params.operationType === proxy.operationType.add.type\">\n                    <a-input v-model=\"form.user.nickname\" placeholder=\"用户昵称\" />\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" field=\"userRealName\" label=\"用户真实姓名\" v-if=\"props.params.operationType === proxy.operationType.add.type\">\n                    <a-input v-model=\"form.user.userRealName\" placeholder=\"用户真实姓名\" />\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" field=\"phone\" label=\"手机号\" v-if=\"props.params.operationType === proxy.operationType.add.type\">\n                    <a-input v-model=\"form.user.phone\" placeholder=\"手机号\" />\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" field=\"email\" label=\"邮箱\" v-if=\"props.params.operationType === proxy.operationType.add.type\">\n                    <a-input v-model=\"form.user.email\" placeholder=\"邮箱\" />\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" field=\"userSex\" label=\"用户性别\" v-if=\"props.params.operationType === proxy.operationType.add.type\">\n                    <a-select v-model=\"form.user.userSex\" placeholder=\"用户性别\" allow-clear allow-search>\n                        <a-option v-for=\"(d, index) in dicts[proxy.DICT.userSex]\" :key=\"index\" :value=\"d.dictKey\" :label=\"d.dictValue\" />\n                    </a-select>\n                </a-form-item>\n            </div>\n        </a-form>\n\n        <!-- 分割线 -->\n        <a-divider class=\"mt-0\" />\n\n        <div class=\"flex justify-end\">\n            <a-space>\n                <a-button @click=\"cancelBtnClick()\">取消</a-button>\n                <a-button type=\"primary\" @click=\"okBtnClick()\">确定</a-button>\n            </a-space>\n        </div>\n    </a-spin>\n</template>\n\n<script setup>\nimport {ref, reactive, getCurrentInstance, watch} from 'vue'\nimport { addTenantApi, updateTenantByTenantIdApi, getTenantByTenantIdApi } from \"~/api/tenant.js\";\n\n//全局实例\nconst {proxy} = getCurrentInstance()\n//加载字典\nconst dicts = proxy.LoadDicts([proxy.DICT.commonNumberStatus, proxy.DICT.tenantPackageList, proxy.DICT.userSex, proxy.DICT.tenantDataIsolation, proxy.DICT.storageList])\n//接收父组件参数\nconst props = defineProps({\n    params: {\n        type: Object,\n        default: () => {}\n    }\n})\n//父组件函数\nconst emits = defineEmits(['ok', 'cancel'])\n//加载中...\nconst spinLoading = ref(false)\n//表单ref\nconst formRef = ref(null)\n//默认数据源信息\nconst defaultTenantDatasource = {\n    datasourceName: null,\n    datasourceUrl: 'jdbc:mysql://localhost:3306/minimalist?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowMultiQueries=true&useAffectedRows=true&rewriteBatchedStatements=true',\n    username: null,\n    password: null\n}\n//表单\nconst form = reactive({\n    //租户ID\n    tenantId: null,\n    //租户名称\n    tenantName: null,\n    //租户套餐\n    packageId: null,\n    //联系人\n    userId: null,\n    //账号额度\n    accountCount: null,\n    //过期时间\n    expireTime: null,\n    //数据隔离方式\n    dataIsolation: null,\n    //数据源名称\n    datasource: null,\n    //存储ID\n    storageId: null,\n    //租户状态\n    status: null,\n    //备注\n    remark: null,\n    //用户信息，新增租户时填充数据\n    user: {\n        //用户账号\n        username: null,\n        //用户密码\n        password: null,\n        //用户昵称\n        nickname: null,\n        //用户真实姓名\n        userRealName: null,\n        //手机号\n        phone: null,\n        //邮箱\n        email: null,\n        //用户性别\n        userSex: null\n    },\n    //数据源信息，隔离方式为db时填充数据\n    tenantDatasource: defaultTenantDatasource\n})\n//表单校验规则\nconst rules = {\n    tenantName: [{required: true, message: '租户名称不能为空', trigger: 'submit'}],\n    packageId: [{required: true, message: '租户套餐不能为空', trigger: 'submit'}],\n    accountCount: [{required: true, message: '账号额度不能为空', trigger: 'submit'}],\n    expireTime: [{required: true, message: '过期时间不能为空', trigger: 'submit'}],\n    dataIsolation: [{required: true, message: '数据库隔离方式不能为空', trigger: 'submit'}],\n    storageId: [{required: true, message: '租户文件存储方式不能为空', trigger: 'submit'}]\n}\n//确定 -> 点击\nconst okBtnClick = () => {\n    //表单验证\n    formRef.value.validate((valid) => {\n        if (valid) {return false}\n        //添加\n        if (props.params.operationType === proxy.operationType.add.type) {\n            spinLoading.value = true\n            addTenantApi(form).then(() => {\n                proxy.$msg.success(proxy.operationType.add.success)\n                emits('ok')\n            }).finally(() => {\n                spinLoading.value = false\n            })\n        }\n        //修改\n        if (props.params.operationType === proxy.operationType.update.type) {\n            spinLoading.value = true\n            updateTenantByTenantIdApi(form).then(() => {\n                proxy.$msg.success(proxy.operationType.update.success)\n                emits('ok')\n            }).finally(() => {\n                spinLoading.value = false\n            })\n        }\n    })\n}\n//取消 -> 点击\nconst cancelBtnClick = () => {\n    emits('cancel')\n}\n//加载租户详细信息\nconst loadTenantInfo = (tenantId) => {\n    spinLoading.value = true\n    getTenantByTenantIdApi(tenantId).then(res => {\n        //数据赋值\n        if (res) {\n            for (let key in res) {\n                if (form.hasOwnProperty(key)) {\n                    //回显数据源信息\n                    if (key === 'tenantDatasource') {\n                        if (res[key]) {\n                            form[key] = res[key]\n                        } else {\n                            form[key] = defaultTenantDatasource\n                        }\n                    } else {\n                        form[key] = res[key]\n                    }\n                }\n            }\n            form.packageId = form.packageId === '0' ? Number(form.packageId) : String(form.packageId)\n        }\n    }).finally(() => {\n        spinLoading.value = false\n    })\n}\n//监听参数变化\nwatch(() => props.params, (newVal, oldVal) => {\n    //租户ID\n    if (props.params.tenantId) {\n        //加载租户信息\n        loadTenantInfo(props.params.tenantId)\n    }\n}, { deep: true, immediate: true })\n</script>\n<style scoped></style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/tenant/TenantMgt.vue",
    "content": "<template>\n    <a-card :body-style=\"{height: 'calc(100vh - 125px)'}\">\n\n        <!-- 数据列表 -->\n        <a-row class=\"w-full h-full flex flex-col overflow-x-auto overflow-y-hidden\">\n            <!-- 查询条件 -->\n            <a-row class=\"w-full\" v-if=\"showSearchRow\">\n                <a-form :model=\"searchForm\" layout=\"inline\" label-align=\"left\" size=\"small\">\n                    <a-form-item field=\"tenantName\" label=\"租户名称\">\n                        <a-input v-model=\"searchForm.tenantName\" placeholder=\"租户名称\" />\n                    </a-form-item>\n                    <a-form-item field=\"status\" label=\"租户状态\">\n                        <a-select v-model=\"searchForm.status\" placeholder=\"租户状态\" allow-clear>\n                            <a-option v-for=\"(d, index) in dicts[proxy.DICT.commonNumberStatus]\" :key=\"index\" :value=\"d.dictKey\" :label=\"d.dictValue\" />\n                        </a-select>\n                    </a-form-item>\n                </a-form>\n                <a-row justify=\"center\" class=\"w-full mt-2\">\n                    <a-space>\n                        <a-button type=\"primary\" @click=\"getPageList(false)\">\n                            <template #icon><icon-search /></template>\n                            <template #default>查询</template>\n                        </a-button>\n                        <a-button @click=\"getPageList(true)\">\n                            <template #icon><icon-sync /></template>\n                            <template #default>重置</template>\n                        </a-button>\n                    </a-space>\n                </a-row>\n            </a-row>\n\n            <!-- 分割线 -->\n            <a-divider v-if=\"showSearchRow\" class=\"mt-2\" />\n\n            <!-- 数据操作区 -->\n            <a-row class=\"w-full flex justify-between\">\n                <a-space>\n                    <!-- 添加 -->\n                    <a-button v-perm=\"['basic:tenant:add']\" type=\"primary\" size=\"small\" @click=\"addBtnClick()\">\n                        <template #icon><icon-plus /></template>\n                        <template #default>添加</template>\n                    </a-button>\n                </a-space>\n                <a-space>\n                    <!-- 刷新 -->\n                    <a-button shape=\"circle\" size=\"small\" @click=\"getPageList(false)\">\n                        <template #icon><icon-refresh /></template>\n                    </a-button>\n                    <!-- 收缩/展开 -->\n                    <a-button shape=\"circle\" size=\"small\" @click=\"showSearchRow = !showSearchRow\">\n                        <template #icon>\n                            <icon-caret-up v-if=\"showSearchRow\" />\n                            <icon-caret-down v-else />\n                        </template>\n                    </a-button>\n                </a-space>\n            </a-row>\n\n            <!-- 数据展示区 -->\n            <a-row class=\"w-full flex-1 mt-3 overflow-y-auto\">\n                <a-table class=\"w-[100%]\" :scroll=\"{ minWidth: 600, y: '100%' }\" :columns=\"datatable.columns\" :data=\"datatable.records\" :loading=\"datatable.loading\" :pagination=\"false\" table-layout-fixed>\n                    <!-- 租户名 -->\n                    <template #tenantName=\"{ record }\">\n                        <a-link @click=\"detailBtnClick(record.tenantId)\" icon>{{ record.tenantName }}</a-link>\n                    </template>\n                    <!-- 租户套餐 -->\n                    <template #packageId=\"{ record }\">\n                        <dict-convert :dict-data=\"dicts[proxy.DICT.tenantPackageList]\" :dict-key=\"record.packageId === '0' ? Number(record.packageId) : String(record.packageId)\" />\n                    </template>\n                    <!-- 数据隔离方式 -->\n                    <template #dataIsolation=\"{ record }\">\n                        <dict-convert :dict-data=\"dicts[proxy.DICT.tenantDataIsolation]\" :dict-key=\"record.dataIsolation\" />\n                    </template>\n                    <!-- 文件存储方式 -->\n                    <template #storageId=\"{ record }\">\n                        <dict-convert :dict-data=\"dicts[proxy.DICT.storageList]\" :dict-key=\"record.storageId\" />\n                    </template>\n                    <!-- 租户状态 -->\n                    <template #status=\"{ record }\">\n                        <dict-convert :dict-data=\"dicts[proxy.DICT.commonNumberStatus]\" :dict-key=\"record.status\" />\n                    </template>\n                    <!-- 操作 -->\n                    <template #operation=\"{ record }\">\n                        <a-button v-perm=\"['basic:tenant:update']\" type=\"text\" size=\"mini\" @click=\"updateBtnClick(record.tenantId)\">\n                            <template #icon>\n                                <icon-edit />\n                            </template>\n                            <template #default>修改</template>\n                        </a-button>\n                        <a-popconfirm content=\"删除租户会导致租户下的所有用户无法使用，确认要删除吗?\" @ok=\"deleteBtnOkClick(record.tenantId)\">\n                            <a-button v-perm=\"['basic:tenant:delete']\" type=\"text\" status=\"danger\" size=\"mini\">\n                                <template #icon>\n                                    <icon-delete />\n                                </template>\n                                <template #default>删除</template>\n                            </a-button>\n                        </a-popconfirm>\n                    </template>\n                </a-table>\n            </a-row>\n\n            <!-- 分页 -->\n            <a-row class=\"w-full flex justify-end mt-2\">\n                <pagination v-if=\"datatable.total > 0\"\n                            v-model:page-num=\"searchForm.pageNum\"\n                            v-model:page-size=\"searchForm.pageSize\"\n                            :total=\"datatable.total\" @pagination=\"getPageList(false)\" />\n            </a-row>\n        </a-row>\n\n        <!-- 添加/修改 -->\n        <a-modal v-model:visible=\"modal.visible\" width=\"50%\" :esc-to-close=\"false\" :mask-closable=\"false\" draggable :footer=\"false\">\n            <template #title>{{ modal.title }}</template>\n            <component :is=\"modal.component\" :params=\"modal.params\" @ok=\"onOk\" @cancel=\"onCancel\" v-if=\"modal.visible\" />\n        </a-modal>\n\n    </a-card>\n</template>\n\n<script setup>\nimport {ref, reactive, getCurrentInstance, shallowRef} from 'vue'\n\nimport { getPageTenantListApi, deleteTenantByTenantIdApi } from \"~/api/tenant.js\";\nimport TenantDetail from \"~/pages/basic/tenant/TenantDetail.vue\";\nimport TenantEdit from \"~/pages/basic/tenant/TenantEdit.vue\";\n\n//全局实例\nconst {proxy} = getCurrentInstance()\n//加载字典\nconst dicts = proxy.LoadDicts([proxy.DICT.commonNumberStatus, proxy.DICT.tenantPackageList, proxy.DICT.tenantDataIsolation, proxy.DICT.storageList])\n//是否展示搜索区域\nconst showSearchRow = ref(true)\n//搜索参数表单\nconst searchForm = reactive({\n    //租户名称\n    tenantName: null,\n    //租户状态\n    status: null,\n    //页码\n    pageNum: 1,\n    //条数\n    pageSize: 10\n})\n//数据列表\nconst datatable = reactive({\n    //列配置\n    columns: [\n        {title: '租户名', dataIndex: 'tenantName', slotName: 'tenantName', align: 'center', width: 200, ellipsis: true, tooltip: true},\n        {title: '租户套餐', dataIndex: 'packageId', slotName: 'packageId', align: 'center', width: 150, ellipsis: true, tooltip: true},\n        {title: '数据隔离方式', dataIndex: 'dataIsolation', slotName: 'dataIsolation', align: 'center', width: 150},\n        {title: '数据源名称', dataIndex: 'datasource', slotName: 'datasource', align: 'center', width: 150},\n        {title: '文件存储方式', dataIndex: 'storageId', slotName: 'storageId', align: 'center', width: 150},\n        {title: '联系人', dataIndex: 'contactName', align: 'center', width: 150, ellipsis: true, tooltip: true},\n        {title: '联系人手机', dataIndex: 'phone', align: 'center', width: 125, ellipsis: true, tooltip: true},\n        {title: '联系人邮箱', dataIndex: 'email', align: 'center', width: 125, ellipsis: true, tooltip: true},\n        {title: '账号额度', dataIndex: 'accountCount', align: 'center', width: 90},\n        {title: '过期时间', dataIndex: 'expireTime', align: 'center', width: 180},\n        {title: '租户状态', dataIndex: 'status', slotName: 'status', align: 'center', width: 100},\n        {title: '操作', slotName: 'operation', align: 'center', width: 160, fixed: 'right'}\n    ],\n    //加载\n    loading: false,\n    //数据列表\n    records: [],\n    //总条数\n    total: 0\n})\n//查询数据列表\nconst getPageList = (isReset = false) => {\n    if (isReset) {\n        searchForm.tenantName = null\n        searchForm.status = null\n        searchForm.pageNum = 1\n        searchForm.pageSize = 10\n    }\n    datatable.loading = true\n    getPageTenantListApi(searchForm).then(res => {\n        datatable.records = res.records\n        datatable.total = res.total\n    }).finally(() => {\n        datatable.loading = false\n    })\n}\n//公共模态框\nconst modal = reactive({\n    //是否显示\n    visible: false,\n    //模态框标题\n    title: '租户管理',\n    //传递参数\n    params: {},\n    //组件名称\n    component: null\n});\n//添加按钮 -> 点击事件\nconst addBtnClick = () => {\n    modal.visible = true\n    modal.title = '添加租户'\n    modal.params = { operationType: proxy.operationType.add.type }\n    modal.component = shallowRef(TenantEdit)\n}\n//表格行数据 \"修改\" -> 点击\nconst updateBtnClick = (tenantId) => {\n    modal.visible = true\n    modal.title = '修改租户'\n    modal.params = { operationType: proxy.operationType.update.type, tenantId: tenantId }\n    modal.component = shallowRef(TenantEdit)\n}\n//表格行数据 \"查看\" -> 点击\nconst detailBtnClick = (tenantId) => {\n    modal.visible = true\n    modal.title = '租户详细信息'\n    modal.params = { operationType: proxy.operationType.detail.type, tenantId: tenantId }\n    modal.component = shallowRef(TenantDetail)\n}\n//表格行数据 \"删除\" -> 确认\nconst deleteBtnOkClick = (tenantId) => {\n    deleteTenantByTenantIdApi(tenantId).then(() => {\n        proxy.$msg.success(proxy.operationType.delete.success)\n        //刷新列表\n        getPageList()\n    })\n}\n//模态框 -> 确认\nconst onOk = () => {\n    modal.visible = false\n    //查询数据列表\n    getPageList()\n}\n//模态框 -> 取消\nconst onCancel = () => {\n    modal.visible = false\n}\n\n//查询数据列表\ngetPageList()\n</script>\n<style scoped></style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/tenantPackage/TenantPackageDetail.vue",
    "content": "<template>\n    <a-spin class=\"w-[100%]\" :size=\"35\" :loading=\"spinLoading\" tip=\"正在处理, 请稍候...\">\n        <a-descriptions :column=\"2\" bordered>\n            <a-descriptions-item label=\"套餐名称\">{{ form.packageName }}</a-descriptions-item>\n            <a-descriptions-item label=\"套餐状态\">\n                <dict-convert :dict-data=\"dicts[proxy.DICT.commonNumberStatus]\" :dict-key=\"form.status\" />\n            </a-descriptions-item>\n            <a-descriptions-item label=\"备注\">{{ form.remark }}</a-descriptions-item>\n        </a-descriptions>\n\n        <a-descriptions :column=\"2\" bordered class=\"mt-3\">\n            <a-descriptions-item :span=\"2\" label=\"套餐权限\">\n                <a-spin class=\"w-[100%]\" :size=\"35\" :loading=\"permTreeSpinLoading\" tip=\"正在处理, 请稍候...\">\n                    <a-scrollbar class=\"w-[100%] max-h-[250px] overflow-auto\" :outer-style=\"{width: '100%'}\" type=\"track\">\n                        <a-tree v-model:checked-keys=\"form.checkedPermIds\" :data=\"permTreeData\" v-if=\"permTreeData.length > 0\"\n                                show-line checkable multiple blockNode :selectable=\"false\"\n                                :fieldNames=\"{\n                                    key: 'permId',\n                                    title: 'permName',\n                                    children: 'children'\n                                }\" />\n                    </a-scrollbar>\n                </a-spin>\n            </a-descriptions-item>\n        </a-descriptions>\n    </a-spin>\n</template>\n<script setup>\nimport {ref, reactive, getCurrentInstance, watch, nextTick} from 'vue'\nimport { getTenantPackageByTenantPackageIdApi } from \"~/api/tenantPackage.js\";\nimport { getEnablePermListApi } from \"~/api/perm.js\";\nimport {getAllTreeParentId} from \"~/utils/sys.js\";\n\n//全局实例\nconst { proxy } = getCurrentInstance()\n//加载字典\nconst dicts = proxy.LoadDicts([proxy.DICT.commonNumberStatus])\n//加载中...\nconst spinLoading = ref(false)\n//接收父组件参数\nconst props = defineProps({\n    params: {\n        type: Object,\n        default: () => {}\n    }\n})\n//表单\nconst form = reactive({\n    //套餐ID\n    packageId: null,\n    //套餐名称\n    packageName: null,\n    //套餐状态\n    status: null,\n    //备注\n    remark: null,\n    //套餐权限 -> 全勾选+半勾选\n    permissionsIds: [],\n    //套餐权限 -> 全勾选\n    checkedPermIds: []\n})\n//加载套餐详细信息\nconst loadTenantPackageInfo = (packageId) => {\n    spinLoading.value = true\n    getTenantPackageByTenantPackageIdApi(packageId).then(res => {\n        //数据赋值\n        if (res) {\n            for (let key in res) {\n                if (form.hasOwnProperty(key)) {\n                    if (key === 'checkedPermIds') {\n                        let checkedPermIds = []\n                        //处理权限回显\n                        res[key].forEach(permId => {\n                            //如果不是父节点，则回显为勾选\n                            if (!allParentPermId.value.includes(permId)) {\n                                checkedPermIds.push(permId)\n                            }\n                        })\n                        form.checkedPermIds = checkedPermIds\n                    } else {\n                        form[key] = res[key]\n                    }\n                }\n            }\n        }\n    }).finally(() => {\n        spinLoading.value = false\n    })\n}\n//权限树数据\nconst permTreeData = ref([])\n//权限树加载\nconst permTreeSpinLoading = ref(false)\n//权限树所有父节点ID(只要有子集，就视为是父节点)\nconst allParentPermId = ref([])\n//获取权限数据列表\nconst getPermTree = (loadTenantPackage = false) => {\n    permTreeSpinLoading.value = true\n    getEnablePermListApi().then(res => {\n        //权限树数据赋值\n        permTreeData.value = res\n        if (loadTenantPackage) {\n            //获取所有父permId\n            allParentPermId.value = getAllTreeParentId(res, 'permId')\n            //加载套餐信息\n            loadTenantPackageInfo(props.params.packageId)\n        }\n    }).finally(() => {\n        permTreeSpinLoading.value = false\n    })\n}\n//监听参数变化\nwatch(() => props.params, (newVal, oldVal) => {\n    //租户套餐ID\n    if (props.params.packageId) {\n        //加载权限树\n        getPermTree(true)\n    } else {\n        //加载权限树\n        getPermTree()\n    }\n}, { deep: true, immediate: true })\n</script>\n<style scoped>\n/* 树形组件点击会高亮，容易引起歧义，此处取消高亮样式 */\n:deep(.arco-tree-node-selected .arco-tree-node-title, .arco-tree-node-selected .arco-tree-node-title:hover) {\n    color: rgb(var(--color-text-1));\n}\n</style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/tenantPackage/TenantPackageEdit.vue",
    "content": "<template>\n    <a-spin class=\"w-[100%]\" :size=\"35\" :loading=\"spinLoading\" tip=\"正在处理, 请稍候...\">\n        <a-form :model=\"form\" ref=\"formRef\" layout=\"vertical\" :rules=\"rules\" auto-label-width>\n            <a-form-item field=\"packageName\" label=\"套餐名称\" required>\n                <a-input v-model=\"form.packageName\" placeholder=\"套餐名称\" />\n            </a-form-item>\n            <a-form-item field=\"status\" label=\"套餐状态\" required v-if=\"props.params.operationType === proxy.operationType.update.type\">\n                <a-select v-model=\"form.status\" placeholder=\"套餐状态\" allow-clear>\n                    <a-option v-for=\"(d, index) in dicts[proxy.DICT.commonNumberStatus]\" :key=\"index\" :value=\"d.dictKey\" :label=\"d.dictValue\" />\n                </a-select>\n            </a-form-item>\n            <a-form-item field=\"remark\" label=\"备注\">\n                <a-textarea v-model=\"form.remark\" placeholder=\"备注\" />\n            </a-form-item>\n            <a-form-item field=\"checkedPermIds\" label=\"套餐权限\" required>\n                <a-spin class=\"w-[100%]\" :size=\"35\" :loading=\"permTreeSpinLoading\" tip=\"正在处理, 请稍候...\">\n                    <a-scrollbar class=\"w-[100%] max-h-[250px] overflow-auto border\" :outer-style=\"{width: '100%'}\" type=\"track\">\n                        <a-tree v-model:checked-keys=\"form.checkedPermIds\" :data=\"permTreeData\" ref=\"treeRef\" v-if=\"permTreeData.length > 0\"\n                                show-line multiple checkable blockNode action-on-node-click=\"expand\"\n                                :fieldNames=\"{\n                                        key: 'permId',\n                                        title: 'permName',\n                                        children: 'children'\n                                    }\" />\n                    </a-scrollbar>\n                </a-spin>\n            </a-form-item>\n        </a-form>\n\n        <!-- 分割线 -->\n        <a-divider class=\"mt-0\" />\n\n        <div class=\"flex justify-end\">\n            <a-space>\n                <a-button @click=\"cancelBtnClick()\">取消</a-button>\n                <a-button type=\"primary\" @click=\"okBtnClick()\">确定</a-button>\n            </a-space>\n        </div>\n    </a-spin>\n</template>\n\n<script setup>\nimport {ref, reactive, getCurrentInstance, watch, nextTick} from 'vue'\nimport { addTenantPackageApi, updateTenantPackageByTenantPackageIdApi, getTenantPackageByTenantPackageIdApi } from \"~/api/tenantPackage.js\";\nimport { getEnablePermListApi } from \"~/api/perm.js\";\nimport { getAllTreeParentId } from \"~/utils/sys.js\";\n\n//全局实例\nconst {proxy} = getCurrentInstance()\n//加载字典\nconst dicts = proxy.LoadDicts([proxy.DICT.commonNumberStatus])\n//接收父组件参数\nconst props = defineProps({\n    params: {\n        type: Object,\n        default: () => {}\n    }\n})\n//父组件函数\nconst emits = defineEmits(['ok', 'cancel'])\n//加载中...\nconst spinLoading = ref(false)\n//表单ref\nconst formRef = ref(null)\n//表单\nconst form = reactive({\n    //套餐ID\n    packageId: null,\n    //套餐名称\n    packageName: null,\n    //套餐状态\n    status: null,\n    //备注\n    remark: null,\n    //套餐权限 -> 全勾选+半勾选\n    permissionsIds: [],\n    //套餐权限 -> 全勾选\n    checkedPermIds: []\n})\n//表单校验规则\nconst rules = {\n    packageName: [{required: true, message: '套餐名称不能为空', trigger: 'submit'}],\n    permissionsIds: [{required: true, message: '套餐权限不能为空', trigger: 'submit'}],\n    checkedPermIds: [{required: true, message: '套餐权限不能为空', trigger: 'submit'}]\n}\n//确定 -> 点击\nconst okBtnClick = () => {\n    //获取权限 -> 全勾选+半勾选\n    form.permissionsIds = getPermTreeSelectData(true)\n    //表单验证\n    formRef.value.validate((valid) => {\n        if (valid) {return false}\n        //添加\n        if (props.params.operationType === proxy.operationType.add.type) {\n            spinLoading.value = true\n            addTenantPackageApi(form).then(() => {\n                proxy.$msg.success(proxy.operationType.add.success)\n                emits('ok')\n            }).finally(() => {\n                spinLoading.value = false\n            })\n        }\n        //修改\n        if (props.params.operationType === proxy.operationType.update.type) {\n            spinLoading.value = true\n            updateTenantPackageByTenantPackageIdApi(form).then(() => {\n                proxy.$msg.success(proxy.operationType.update.success)\n                emits('ok')\n            }).finally(() => {\n                spinLoading.value = false\n            })\n        }\n    })\n}\n//取消 -> 点击\nconst cancelBtnClick = () => {\n    emits('cancel')\n}\n//获取权限树的节点 isGetHalf：是否获取半勾选节点ID\nconst getPermTreeSelectData = (isGetHalf) => {\n    //选中的节点\n    let checkedKeys = treeRef.value.getCheckedNodes()\n    if (checkedKeys && checkedKeys.length > 0) {\n        checkedKeys = [...new Set(checkedKeys.map(node => node.permId))]\n    }\n    //半选中的节点\n    let halfCheckedKeys = []\n    if (isGetHalf) {\n        halfCheckedKeys = treeRef.value.getHalfCheckedNodes()\n        if (halfCheckedKeys && halfCheckedKeys.length > 0) {\n            halfCheckedKeys = [...new Set(halfCheckedKeys.map(node => node.permId))]\n        }\n    }\n    //合并两个数组\n    return [...halfCheckedKeys, ...checkedKeys]\n}\n//权限树数据\nconst permTreeData = ref([])\n//权限树加载\nconst permTreeSpinLoading = ref(false)\n//权限树ref\nconst treeRef = ref(null)\n//权限树所有父节点ID(只要有子集，就视为是父节点)\nconst allParentPermId = ref([])\n//获取权限数据列表\nconst getPermTree = (loadTenantPackage = false) => {\n    permTreeSpinLoading.value = true\n    getEnablePermListApi().then(res => {\n        //权限树数据赋值\n        permTreeData.value = res\n        if (loadTenantPackage) {\n            //获取所有父permId\n            allParentPermId.value = getAllTreeParentId(res, 'permId')\n            //加载套餐信息\n            loadTenantPackageInfo(props.params.packageId)\n        }\n    }).finally(() => {\n        permTreeSpinLoading.value = false\n    })\n}\n//加载套餐详细信息\nconst loadTenantPackageInfo = (packageId) => {\n    spinLoading.value = true\n    getTenantPackageByTenantPackageIdApi(packageId).then(res => {\n        //数据赋值\n        if (res) {\n            for (let key in res) {\n                if (form.hasOwnProperty(key)) {\n                    if (key === 'checkedPermIds') {\n                        let checkedPermIds = []\n                        //处理权限回显\n                        res[key].forEach(permId => {\n                            //如果不是父节点，则回显为勾选\n                            if (!allParentPermId.value.includes(permId)) {\n                                checkedPermIds.push(permId)\n                            }\n                        })\n                        form.checkedPermIds = checkedPermIds\n                    } else {\n                        form[key] = res[key]\n                    }\n                }\n            }\n        }\n    }).finally(() => {\n        spinLoading.value = false\n    })\n}\n//监听参数变化\nwatch(() => props.params, (newVal, oldVal) => {\n    //租户套餐ID\n    if (props.params.packageId) {\n        //加载权限树\n        getPermTree(true)\n    } else {\n        //加载权限树\n        getPermTree()\n    }\n}, { deep: true, immediate: true })\n</script>\n<style scoped>\n/* 树形组件点击会高亮，容易引起歧义，此处取消高亮样式 */\n:deep(.arco-tree-node-selected .arco-tree-node-title, .arco-tree-node-selected .arco-tree-node-title:hover) {\n    color: rgb(var(--color-text-1));\n}\n</style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/tenantPackage/TenantPackageMgt.vue",
    "content": "<template>\n    <a-card :body-style=\"{height: 'calc(100vh - 125px)'}\">\n\n        <!-- 数据列表 -->\n        <a-row class=\"w-full h-full flex flex-col overflow-x-auto overflow-y-hidden\">\n            <!-- 查询条件 -->\n            <a-row class=\"w-full\" v-if=\"showSearchRow\">\n                <a-form :model=\"searchForm\" layout=\"inline\" label-align=\"left\" size=\"small\">\n                    <a-form-item field=\"packageName\" label=\"套餐名称\">\n                        <a-input v-model=\"searchForm.packageName\" placeholder=\"套餐名称\" />\n                    </a-form-item>\n                    <a-form-item field=\"status\" label=\"套餐状态\">\n                        <a-select v-model=\"searchForm.status\" placeholder=\"套餐状态\" allow-clear>\n                            <a-option v-for=\"(d, index) in dicts[proxy.DICT.commonNumberStatus]\" :key=\"index\" :value=\"d.dictKey\" :label=\"d.dictValue\" />\n                        </a-select>\n                    </a-form-item>\n                </a-form>\n                <a-row justify=\"center\" class=\"w-full mt-2\">\n                    <a-space>\n                        <a-button type=\"primary\" @click=\"getPageList(false)\">\n                            <template #icon><icon-search /></template>\n                            <template #default>查询</template>\n                        </a-button>\n                        <a-button @click=\"getPageList(true)\">\n                            <template #icon><icon-sync /></template>\n                            <template #default>重置</template>\n                        </a-button>\n                    </a-space>\n                </a-row>\n            </a-row>\n\n            <!-- 分割线 -->\n            <a-divider v-if=\"showSearchRow\" class=\"mt-2\" />\n\n            <!-- 数据操作区 -->\n            <a-row class=\"w-full flex justify-between\">\n                <a-space>\n                    <!-- 添加 -->\n                    <a-button v-perm=\"['basic:tenantPackage:add']\" type=\"primary\" size=\"small\" @click=\"addBtnClick()\">\n                        <template #icon><icon-plus /></template>\n                        <template #default>添加</template>\n                    </a-button>\n                </a-space>\n                <a-space>\n                    <!-- 刷新 -->\n                    <a-button shape=\"circle\" size=\"small\" @click=\"getPageList(false)\">\n                        <template #icon><icon-refresh /></template>\n                    </a-button>\n                    <!-- 收缩/展开 -->\n                    <a-button shape=\"circle\" size=\"small\" @click=\"showSearchRow = !showSearchRow\">\n                        <template #icon>\n                            <icon-caret-up v-if=\"showSearchRow\" />\n                            <icon-caret-down v-else />\n                        </template>\n                    </a-button>\n                </a-space>\n            </a-row>\n\n            <!-- 数据展示区 -->\n            <a-row class=\"w-full flex-1 mt-3 overflow-y-auto\">\n                <a-table class=\"w-[100%]\" :scroll=\"{ minWidth: 600, y: '100%' }\" :columns=\"datatable.columns\" :data=\"datatable.records\" :loading=\"datatable.loading\" :pagination=\"false\" table-layout-fixed>\n                    <!-- 套餐名称 -->\n                    <template #packageName=\"{ record }\">\n                        <a-link @click=\"detailBtnClick(record.packageId)\" icon>{{ record.packageName }}</a-link>\n                    </template>\n                    <!-- 套餐状态 -->\n                    <template #status=\"{ record }\">\n                        <dict-convert :dict-data=\"dicts[proxy.DICT.commonNumberStatus]\" :dict-key=\"record.status\" />\n                    </template>\n                    <!-- 操作 -->\n                    <template #operation=\"{ record }\">\n                        <a-button v-perm=\"['basic:tenantPackage:update']\" type=\"text\" size=\"mini\" @click=\"updateBtnClick(record.packageId)\">\n                            <template #icon>\n                                <icon-edit />\n                            </template>\n                            <template #default>修改</template>\n                        </a-button>\n                        <a-popconfirm content=\"确认要删除吗?\" @ok=\"deleteBtnOkClick(record.packageId)\">\n                            <a-button v-perm=\"['basic:tenantPackage:delete']\" type=\"text\" status=\"danger\" size=\"mini\">\n                                <template #icon>\n                                    <icon-delete />\n                                </template>\n                                <template #default>删除</template>\n                            </a-button>\n                        </a-popconfirm>\n                    </template>\n                </a-table>\n            </a-row>\n\n            <!-- 分页 -->\n            <a-row class=\"w-full flex justify-end mt-2\">\n                <pagination v-if=\"datatable.total > 0\"\n                            v-model:page-num=\"searchForm.pageNum\"\n                            v-model:page-size=\"searchForm.pageSize\"\n                            :total=\"datatable.total\" @pagination=\"getPageList(false)\" />\n            </a-row>\n        </a-row>\n\n        <!-- 添加/修改 -->\n        <a-modal v-model:visible=\"modal.visible\" width=\"500px\" :esc-to-close=\"false\" :mask-closable=\"false\" draggable :footer=\"false\">\n            <template #title>{{ modal.title }}</template>\n            <component :is=\"modal.component\" :params=\"modal.params\" @ok=\"onOk\" @cancel=\"onCancel\" v-if=\"modal.visible\" />\n        </a-modal>\n\n    </a-card>\n</template>\n\n<script setup>\nimport {ref, reactive, getCurrentInstance, shallowRef} from 'vue'\nimport {deleteTenantPackageByTenantPackageIdApi, getPageTenantPackageListApi} from \"~/api/tenantPackage.js\";\nimport TenantPackageDetail from \"~/pages/basic/tenantPackage/TenantPackageDetail.vue\";\nimport TenantPackageEdit from \"~/pages/basic/tenantPackage/TenantPackageEdit.vue\";\n\n//全局实例\nconst {proxy} = getCurrentInstance()\n//加载字典\nconst dicts = proxy.LoadDicts([proxy.DICT.commonNumberStatus])\n//是否展示搜索区域\nconst showSearchRow = ref(true)\n//搜索参数表单\nconst searchForm = reactive({\n    //套餐名称\n    packageName: null,\n    //套餐状态\n    status: null,\n    //页码\n    pageNum: 1,\n    //条数\n    pageSize: 10\n})\n//数据列表\nconst datatable = reactive({\n    //列配置\n    columns: [\n        {title: '套餐名称', dataIndex: 'packageName', slotName: 'packageName', align: 'center', width: 200, ellipsis: true, tooltip: true},\n        {title: '套餐状态', dataIndex: 'status', slotName: 'status', align: 'center', width: 100},\n        {title: '备注', dataIndex: 'remark', align: 'center', ellipsis: true, tooltip: true},\n        {title: '操作', slotName: 'operation', align: 'center', width: 160, fixed: 'right'}\n    ],\n    //加载\n    loading: false,\n    //数据列表\n    records: [],\n    //总条数\n    total: 0\n})\n//查询数据列表\nconst getPageList = (isReset = false) => {\n    if (isReset) {\n        searchForm.packageName = null\n        searchForm.status = null\n        searchForm.pageNum = 1\n        searchForm.pageSize = 10\n    }\n    datatable.loading = true\n    getPageTenantPackageListApi(searchForm).then(res => {\n        datatable.records = res.records\n        datatable.total = res.total\n    }).finally(() => {\n        datatable.loading = false\n    })\n}\n//公共模态框\nconst modal = reactive({\n    //是否显示\n    visible: false,\n    //模态框标题\n    title: '租户套餐管理',\n    //传递参数\n    params: {},\n    //组件名称\n    component: null\n});\n//添加按钮 -> 点击事件\nconst addBtnClick = () => {\n    modal.visible = true\n    modal.title = '添加租户套餐'\n    modal.params = { operationType: proxy.operationType.add.type }\n    modal.component = shallowRef(TenantPackageEdit)\n}\n//表格行数据 \"修改\" -> 点击\nconst updateBtnClick = (packageId) => {\n    modal.visible = true\n    modal.title = '修改租户套餐'\n    modal.params = { operationType: proxy.operationType.update.type, packageId: packageId }\n    modal.component = shallowRef(TenantPackageEdit)\n}\n//表格行数据 \"查看\" -> 点击\nconst detailBtnClick = (packageId) => {\n    modal.visible = true\n    modal.title = '租户套餐详细信息'\n    modal.params = { operationType: proxy.operationType.detail.type, packageId: packageId }\n    modal.component = shallowRef(TenantPackageDetail)\n}\n//表格行数据 \"删除\" -> 确认\nconst deleteBtnOkClick = (packageId) => {\n    deleteTenantPackageByTenantPackageIdApi(packageId).then(() => {\n        proxy.$msg.success(proxy.operationType.delete.success)\n        //刷新列表\n        getPageList()\n    })\n}\n//模态框 -> 确认\nconst onOk = () => {\n    modal.visible = false\n    //查询数据列表\n    getPageList()\n}\n//模态框 -> 取消\nconst onCancel = () => {\n    modal.visible = false\n}\n\n//查询数据列表\ngetPageList()\n</script>\n<style scoped></style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/user/UserDetail.vue",
    "content": "<template>\n    <a-spin class=\"w-[100%]\" :size=\"35\" :loading=\"spinLoading\" tip=\"正在处理, 请稍候...\">\n        <a-descriptions :column=\"2\" bordered>\n            <a-descriptions-item label=\"用户账号\">{{ form.username }}</a-descriptions-item>\n            <a-descriptions-item label=\"用户昵称\">{{ form.nickname }}</a-descriptions-item>\n            <a-descriptions-item label=\"用户姓名\">{{ form.userRealName }}</a-descriptions-item>\n            <a-descriptions-item label=\"用户手机\">{{ form.phone }}</a-descriptions-item>\n            <a-descriptions-item label=\"用户邮箱\">{{ form.email }}</a-descriptions-item>\n            <a-descriptions-item label=\"用户性别\">\n                <dict-convert :dict-data=\"dicts[proxy.DICT.userSex]\" :dict-key=\"form.userSex\" />\n            </a-descriptions-item>\n            <a-descriptions-item label=\"用户状态\">\n                <dict-convert :dict-data=\"dicts[proxy.DICT.commonNumberStatus]\" :dict-key=\"form.status\" />\n            </a-descriptions-item>\n            <a-descriptions-item label=\"用户角色\">\n                <template v-for=\"(roleId, index) in form.roleIds\" :key=\"index\">\n                    <dict-convert :dict-data=\"dicts[proxy.DICT.roleList]\" :dict-key=\"roleId\" />\n                    {{ index < form.roleIds.length - 1 ? ', ' : '' }}\n                </template>\n            </a-descriptions-item>\n            <a-descriptions-item label=\"用户岗位\">\n                <template v-for=\"(postId, index) in form.postIds\" :key=\"index\">\n                    <dict-convert :dict-data=\"dicts[proxy.DICT.postList]\" :dict-key=\"postId\" />\n                    {{index < form.postIds.length - 1 ? '   ' : ''}}\n                </template>\n            </a-descriptions-item>\n        </a-descriptions>\n\n        <a-descriptions :column=\"2\" bordered class=\"mt-3\">\n            <a-descriptions-item :span=\"2\" label=\"所属部门\">\n                <a-spin class=\"w-[100%]\" :size=\"35\" :loading=\"deptTreeSpinLoading\" tip=\"正在处理, 请稍候...\">\n                    <a-scrollbar class=\"w-[100%] max-h-[250px] overflow-auto\" :outer-style=\"{width: '100%'}\" type=\"track\">\n                        <a-tree v-model:checked-keys=\"form.checkedDeptIds\" :data=\"deptTreeData\" v-if=\"deptTreeData.length > 0\"\n                                show-line checkable multiple blockNode :selectable=\"false\"\n                                :fieldNames=\"{\n                                    key: 'deptId',\n                                    title: 'deptName',\n                                    children: 'children'\n                                }\" />\n                    </a-scrollbar>\n                </a-spin>\n            </a-descriptions-item>\n        </a-descriptions>\n    </a-spin>\n</template>\n<script setup>\nimport {ref, reactive, getCurrentInstance, watch, nextTick} from 'vue'\nimport { getUserByUserIdApi } from \"~/api/user.js\";\nimport { getEnableDeptListApi } from \"~/api/dept.js\";\nimport {getAllTreeParentId} from \"~/utils/sys.js\";\n\n//全局实例\nconst { proxy } = getCurrentInstance()\n//加载字典\nconst dicts = proxy.LoadDicts([proxy.DICT.commonNumberStatus, proxy.DICT.userSex, proxy.DICT.roleList, proxy.DICT.postList])\n//加载中...\nconst spinLoading = ref(false)\n//接收父组件参数\nconst props = defineProps({\n    params: {\n        type: Object,\n        default: () => {}\n    }\n})\n//表单\nconst form = reactive({\n    //用户ID\n    userId: null,\n    //用户账号\n    username: null,\n    //用户昵称\n    nickname: null,\n    //用户真实姓名\n    userRealName: null,\n    //手机号\n    phone: null,\n    //邮箱\n    email: null,\n    //用户性别\n    userSex: null,\n    //用户状态\n    status: null,\n    //备注\n    remark: null,\n    //角色ID集合\n    roleIds: [],\n    //岗位ID集合\n    postIds: [],\n    //部门 -> 全勾选\n    checkedDeptIds: []\n})\n//加载用户详细信息\nconst loadUserInfo = (userId) => {\n    spinLoading.value = true\n    getUserByUserIdApi(userId).then(res => {\n        //数据赋值\n        if (res) {\n            for (let key in res) {\n                if (form.hasOwnProperty(key)) {\n                    if (key === 'checkedDeptIds') {\n                        let checkedDeptIds = []\n                        //处理部门回显\n                        res[key].forEach(deptId => {\n                            //如果不是父节点，则回显为勾选\n                            if (!allParentDeptId.value.includes(deptId)) {\n                                checkedDeptIds.push(deptId)\n                            }\n                        })\n                        form.checkedDeptIds = checkedDeptIds\n                    } else {\n                        form[key] = res[key]\n                    }\n                }\n            }\n        }\n    }).finally(() => {\n        spinLoading.value = false\n    })\n}\n//部门树加载\nconst deptTreeSpinLoading = ref(false)\n//部门树数据\nconst deptTreeData = ref([])\n//部门树所有父节点ID(只要有子集，就视为是父节点)\nconst allParentDeptId = ref([])\n//获取部门数据列表\nconst getDeptTree = (loadUser = false) => {\n    deptTreeSpinLoading.value = true\n    getEnableDeptListApi().then(res => {\n        //权限树数据赋值\n        deptTreeData.value = res\n        if (loadUser) {\n            //获取所有父deptId\n            allParentDeptId.value = getAllTreeParentId(res, 'deptId')\n            //加载用户信息\n            loadUserInfo(props.params.userId)\n        }\n    }).finally(() => {\n        deptTreeSpinLoading.value = false\n    })\n}\n//监听参数变化\nwatch(() => props.params, (newVal, oldVal) => {\n    //角色ID\n    if (props.params.userId) {\n        //加载部门树\n        getDeptTree(true)\n    } else {\n        //加载部门树\n        getDeptTree()\n    }\n}, { deep: true, immediate: true })\n</script>\n<style scoped>\n/* 树形组件点击会高亮，容易引起歧义，此处取消高亮样式 */\n:deep(.arco-tree-node-selected .arco-tree-node-title, .arco-tree-node-selected .arco-tree-node-title:hover) {\n    color: rgb(var(--color-text-1));\n}\n</style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/user/UserEdit.vue",
    "content": "<template>\n    <a-spin class=\"w-[100%]\" :size=\"35\" :loading=\"spinLoading\" tip=\"正在处理, 请稍候...\">\n        <a-form :model=\"form\" ref=\"formRef\" layout=\"vertical\" :rules=\"rules\" auto-label-width>\n            <div class=\"flex justify-between\" style=\"flex-wrap: wrap;\">\n                <a-form-item class=\"w-[49%]\" field=\"username\" label=\"用户账号\" required tooltip=\"用户登录系统使用的账号\">\n                    <a-input v-model=\"form.username\" placeholder=\"用户账号\" />\n                </a-form-item>\n                <!-- 新增时，可以手动输入密码 -->\n                <a-form-item class=\"w-[49%]\" field=\"password\" label=\"用户密码\" v-if=\"props.params.operationType === proxy.operationType.add.type\">\n                    <a-input-password v-model=\"form.password\" placeholder=\"用户密码\" />\n                </a-form-item>\n                <!-- 修改时，只询问是否重置密码，选择重置密码后，才能手动输入密码 -->\n                <a-form-item class=\"w-[49%]\" row-class=\"form-label\" field=\"password\" v-else-if=\"props.params.operationType === proxy.operationType.update.type\">\n                    <template #label>\n                        <template class=\"flex items-center justify-between\">\n                            <span>\n                                用户密码\n                                <a-tooltip content=\"勾选重置密码后可输入密码进行重置，不勾选表示不修改密码\">\n                                    <icon-question-circle style=\"color: var(--color-text-4)\" />\n                                </a-tooltip>\n                            </span>\n                            <a-checkbox v-model=\"isResetPassword\" :value=\"true\" @change=\"isResetPasswordChange\">重置密码</a-checkbox>\n                        </template>\n                    </template>\n                    <!-- 密码框 -->\n                    <a-input-password v-model=\"form.password\" placeholder=\"用户密码\" :disabled=\"!isResetPassword\" />\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" field=\"nickname\" label=\"用户昵称\" required tooltip=\"用户的网名\">\n                    <a-input v-model=\"form.nickname\" placeholder=\"用户昵称\" />\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" field=\"userRealName\" label=\"用户真实姓名\" required>\n                    <a-input v-model=\"form.userRealName\" placeholder=\"用户真实姓名\" />\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" field=\"phone\" label=\"手机号\" required>\n                    <a-input v-model=\"form.phone\" placeholder=\"手机号\" />\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" field=\"email\" label=\"邮箱\">\n                    <a-input v-model=\"form.email\" placeholder=\"邮箱\" />\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" field=\"userSex\" label=\"用户性别\" required>\n                    <a-select v-model=\"form.userSex\" placeholder=\"用户性别\" allow-clear allow-search>\n                        <a-option v-for=\"(d, index) in dicts[proxy.DICT.userSex]\" :key=\"index\" :value=\"d.dictKey\" :label=\"d.dictValue\" />\n                    </a-select>\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" field=\"status\" label=\"用户状态\" v-if=\"props.params.operationType === proxy.operationType.update.type\">\n                    <a-select v-model=\"form.status\" placeholder=\"用户状态\" allow-clear allow-search>\n                        <a-option v-for=\"(d, index) in dicts[proxy.DICT.commonNumberStatus]\" :key=\"index\" :value=\"d.dictKey\" :label=\"d.dictValue\" />\n                    </a-select>\n                </a-form-item>\n                <a-form-item class=\"w-[100%]\" field=\"remark\" label=\"备注\">\n                    <a-textarea v-model=\"form.remark\" placeholder=\"备注\" />\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" field=\"roleIds\" label=\"用户角色\" required tooltip=\"用户在系统中的角色，用于关联用户的菜单和权限，一个用户可以有多个角色，支持多选\">\n                    <a-select v-model=\"form.roleIds\" placeholder=\"用户角色\" multiple :max-tag-count=\"2\" allow-clear allow-search>\n                        <a-option v-for=\"(d, index) in dicts[proxy.DICT.roleList]\" :key=\"index\" :value=\"d.dictKey\" :label=\"d.dictValue\" />\n                    </a-select>\n                </a-form-item>\n                <a-form-item class=\"w-[49%]\" field=\"postIds\" label=\"用户岗位\">\n                    <a-select v-model=\"form.postIds\" placeholder=\"用户岗位\" multiple :max-tag-count=\"2\" allow-clear allow-search>\n                        <a-option v-for=\"(d, index) in dicts[proxy.DICT.postList]\" :key=\"index\" :value=\"d.dictKey\" :label=\"d.dictValue\" />\n                    </a-select>\n                </a-form-item>\n                <a-form-item class=\"w-[100%]\" field=\"checkedDeptIds\" label=\"所在部门\">\n                    <a-spin class=\"w-[100%]\" :size=\"35\" :loading=\"deptTreeSpinLoading\" tip=\"正在处理, 请稍候...\">\n                        <a-scrollbar class=\"w-[100%] max-h-[250px] overflow-auto border\" :outer-style=\"{width: '100%'}\" type=\"track\">\n                            <a-tree v-model:checked-keys=\"form.checkedDeptIds\" :data=\"deptTreeData\" ref=\"treeRef\" v-if=\"deptTreeData.length > 0\"\n                                    show-line multiple checkable blockNode action-on-node-click=\"expand\" allow-search\n                                    :fieldNames=\"{\n                                        key: 'deptId',\n                                        title: 'deptName',\n                                        children: 'children'\n                                    }\" />\n                            <a-empty v-else />\n                        </a-scrollbar>\n                    </a-spin>\n                </a-form-item>\n            </div>\n        </a-form>\n\n        <!-- 分割线 -->\n        <a-divider class=\"mt-0\" />\n\n        <div class=\"flex justify-end\">\n            <a-space>\n                <a-button @click=\"cancelBtnClick()\">取消</a-button>\n                <a-button type=\"primary\" @click=\"okBtnClick()\">确定</a-button>\n            </a-space>\n        </div>\n    </a-spin>\n</template>\n\n<script setup>\nimport {ref, reactive, getCurrentInstance, watch, nextTick} from 'vue'\nimport { getUserByUserIdApi, addUserApi, updateUserByUserIdApi } from \"~/api/user.js\";\nimport { getEnableDeptListApi } from \"~/api/dept.js\";\nimport { getAllTreeParentId } from \"~/utils/sys.js\";\n\n//全局实例\nconst {proxy} = getCurrentInstance()\n//加载字典\nconst dicts = proxy.LoadDicts([proxy.DICT.commonNumberStatus, proxy.DICT.userSex, proxy.DICT.roleList, proxy.DICT.postList])\n//接收父组件参数\nconst props = defineProps({\n    params: {\n        type: Object,\n        default: () => {}\n    }\n})\n//父组件函数\nconst emits = defineEmits(['ok', 'cancel'])\n//加载中...\nconst spinLoading = ref(false)\n//表单ref\nconst formRef = ref(null)\n//是否重置密码\nconst isResetPassword = ref(false)\n//是否重置密码 change事件\nconst isResetPasswordChange = (val) => {\n    isResetPassword.value = val\n}\n//表单\nconst form = reactive({\n    //用户ID\n    userId: null,\n    //用户账号\n    username: null,\n    //用户密码\n    password: null,\n    //用户昵称\n    nickname: null,\n    //用户真实姓名\n    userRealName: null,\n    //手机号\n    phone: null,\n    //邮箱\n    email: null,\n    //用户性别\n    userSex: null,\n    //用户状态\n    status: null,\n    //备注\n    remark: null,\n    //角色ID集合\n    roleIds: [],\n    //岗位ID集合\n    postIds: [],\n    //部门ID集合 -> 全勾选+半勾选\n    deptIds: [],\n    //部门 -> 全勾选\n    checkedDeptIds: []\n})\n//表单校验规则\nconst rules = {\n    username: [{required: true, message: '用户账号不能为空', trigger: 'submit'}],\n    nickname: [{required: true, message: '用户昵称不能为空', trigger: 'submit'}],\n    userRealName: [{required: true, message: '用户真实姓名不能为空', trigger: 'submit'}],\n    phone: [{required: true, message: '用户手机号不能为空', trigger: 'submit'}],\n    userSex: [{required: true, message: '用户性别不能为空', trigger: 'submit'}],\n    roleIds: [{required: true, message: '用户角色不能为空', trigger: 'submit'}]\n}\n//确定 -> 点击\nconst okBtnClick = () => {\n    //获取部门 -> 全勾选+半勾选\n    form.deptIds = getDeptTreeSelectData(true)\n    //表单验证\n    formRef.value.validate((valid) => {\n        if (valid) {return false}\n        //添加\n        if (props.params.operationType === proxy.operationType.add.type) {\n            spinLoading.value = true\n            addUserApi(form).then(() => {\n                proxy.$msg.success(proxy.operationType.add.success)\n                emits('ok')\n            }).finally(() => {\n                spinLoading.value = false\n            })\n        }\n        //修改\n        if (props.params.operationType === proxy.operationType.update.type) {\n            spinLoading.value = true\n            updateUserByUserIdApi(form).then(() => {\n                proxy.$msg.success(proxy.operationType.update.success)\n                emits('ok')\n            }).finally(() => {\n                spinLoading.value = false\n            })\n        }\n    })\n}\n//取消 -> 点击\nconst cancelBtnClick = () => {\n    emits('cancel')\n}\n//获取部门树的节点 isGetHalf：是否获取半勾选节点ID\nconst getDeptTreeSelectData = (isGetHalf) => {\n    //选中的节点\n    let checkedKeys = []\n    if (treeRef.value) {\n        checkedKeys = treeRef.value.getCheckedNodes()\n        if (checkedKeys && checkedKeys.length > 0) {\n            checkedKeys = [...new Set(checkedKeys.map(node => node.deptId))]\n        }\n    }\n    //半选中的节点\n    let halfCheckedKeys = []\n    if (isGetHalf && treeRef.value) {\n        halfCheckedKeys = treeRef.value.getHalfCheckedNodes()\n        if (halfCheckedKeys && halfCheckedKeys.length > 0) {\n            halfCheckedKeys = [...new Set(halfCheckedKeys.map(node => node.deptId))]\n        }\n    }\n    //合并两个数组\n    return [...halfCheckedKeys, ...checkedKeys]\n}\n//部门树数据\nconst deptTreeData = ref([])\n//部门树加载\nconst deptTreeSpinLoading = ref(false)\n//部门树所有父节点ID(只要有子集，就视为是父节点)\nconst allParentDeptId = ref([])\n//部门树ref\nconst treeRef = ref(null)\n//获取部门数据列表\nconst getDeptTree = (loadUser = false) => {\n    deptTreeSpinLoading.value = true\n    getEnableDeptListApi().then(res => {\n        //部门树数据赋值\n        deptTreeData.value = res\n        if (loadUser) {\n            //获取所有父deptId\n            allParentDeptId.value = getAllTreeParentId(res, 'deptId')\n            //加载用户信息\n            loadUserInfo(props.params.userId)\n        }\n    }).finally(() => {\n        deptTreeSpinLoading.value = false\n    })\n}\n//加载用户详细信息\nconst loadUserInfo = (userId) => {\n    spinLoading.value = true\n    getUserByUserIdApi(userId).then(res => {\n        //数据赋值\n        if (res) {\n            for (let key in res) {\n                if (form.hasOwnProperty(key)) {\n                    if (key === 'checkedDeptIds') {\n                        let checkedDeptIds = []\n                        //处理部门回显\n                        res[key].forEach(deptId => {\n                            //如果不是父节点，则回显为勾选\n                            if (!allParentDeptId.value.includes(deptId)) {\n                                checkedDeptIds.push(deptId)\n                            }\n                        })\n                        form.checkedDeptIds = checkedDeptIds\n                    } else {\n                        form[key] = res[key]\n                    }\n                }\n            }\n        }\n    }).finally(() => {\n        spinLoading.value = false\n    })\n}\n//监听参数变化\nwatch(() => props.params, (newVal, oldVal) => {\n    //部门ID\n    if (props.params.userId) {\n        //加载部门树\n        getDeptTree(true)\n    } else {\n        //加载部门树\n        getDeptTree()\n    }\n}, { deep: true, immediate: true })\n</script>\n<style scoped>\n/* 树形组件点击会高亮，容易引起歧义，此处取消高亮样式 */\n:deep(.arco-tree-node-selected .arco-tree-node-title, .arco-tree-node-selected .arco-tree-node-title:hover) {\n    color: rgb(var(--color-text-1));\n}\n.form-label :deep(.arco-form-item-label) {\n    @apply w-[100%];\n}\n</style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/user/UserMgt.vue",
    "content": "<template>\n    <a-card :body-style=\"{height: 'calc(100vh - 125px)'}\">\n\n        <div class=\"w-full h-full flex justify-between\">\n            <!-- 部门数据 -->\n            <a-row class=\"h-full border-r pr-3\" v-perm=\"['basic:dept:get']\">\n                <a-spin class=\"w-[240px] h-full\" :size=\"35\" :loading=\"loadDeptListLoading\" tip=\"正在处理, 请稍候...\">\n                    <a-tree class=\"w-full h-full overflow-y-auto\" :data=\"deptTree\" v-if=\"deptTree.length > 0\" show-line  blockNode\n                            v-model:selected-keys=\"selectDept\" @select=\"selectDeptChange\" ref=\"treeRef\"\n                            :fieldNames=\"{\n                            key: 'deptId',\n                            title: 'deptName',\n                            children: 'children'\n                        }\" />\n                </a-spin>\n            </a-row>\n\n            <!-- 用户数据 -->\n            <a-row class=\"flex flex-1 flex-col pl-3 overflow-x-auto overflow-y-hidden\">\n                <!-- 查询条件 -->\n                <a-row class=\"w-full\" v-if=\"showSearchRow\">\n                    <a-form :model=\"searchForm\" layout=\"inline\" label-align=\"left\" size=\"small\">\n                        <a-form-item field=\"userRealName\" label=\"用户姓名\">\n                            <a-input v-model=\"searchForm.userRealName\" placeholder=\"用户姓名\" />\n                        </a-form-item>\n                        <a-form-item field=\"phone\" label=\"用户手机\">\n                            <a-input v-model=\"searchForm.phone\" placeholder=\"用户手机\" />\n                        </a-form-item>\n                        <a-form-item field=\"status\" label=\"用户状态\">\n                            <a-select v-model=\"searchForm.status\" placeholder=\"用户状态\" allow-clear allow-search>\n                                <a-option v-for=\"(d, index) in dicts[proxy.DICT.commonNumberStatus]\" :key=\"index\" :value=\"d.dictKey\" :label=\"d.dictValue\" />\n                            </a-select>\n                        </a-form-item>\n                    </a-form>\n                    <a-row justify=\"center\" class=\"w-full mt-2\">\n                        <a-space>\n                            <a-button type=\"primary\" @click=\"getPageList(false)\">\n                                <template #icon><icon-search /></template>\n                                <template #default>查询</template>\n                            </a-button>\n                            <a-button @click=\"getPageList(true)\">\n                                <template #icon><icon-sync /></template>\n                                <template #default>重置</template>\n                            </a-button>\n                        </a-space>\n                    </a-row>\n                </a-row>\n\n                <!-- 分割线 -->\n                <a-divider v-if=\"showSearchRow\" class=\"mt-2\" />\n\n                <!-- 数据操作区 -->\n                <a-row class=\"w-full flex justify-between\">\n                    <a-space>\n                        <!-- 添加 -->\n                        <a-button v-perm=\"['basic:user:add']\" type=\"primary\" size=\"small\" @click=\"addBtnClick()\">\n                            <template #icon><icon-plus /></template>\n                            <template #default>添加</template>\n                        </a-button>\n                    </a-space>\n                    <a-space>\n                        <!-- 刷新 -->\n                        <a-button shape=\"circle\" size=\"small\" @click=\"getPageList(false)\">\n                            <template #icon><icon-refresh /></template>\n                        </a-button>\n                        <!-- 收缩/展开 -->\n                        <a-button shape=\"circle\" size=\"small\" @click=\"showSearchRow = !showSearchRow\">\n                            <template #icon>\n                                <icon-caret-up v-if=\"showSearchRow\" />\n                                <icon-caret-down v-else />\n                            </template>\n                        </a-button>\n                    </a-space>\n                </a-row>\n\n                <!-- 数据展示区 -->\n                <a-row class=\"w-full flex-1 mt-3 overflow-y-auto\">\n                    <a-table class=\"w-[100%]\" :scroll=\"{ minWidth: 600, y: '100%' }\" :columns=\"datatable.columns\" :data=\"datatable.records\" :loading=\"datatable.loading\" :pagination=\"false\" table-layout-fixed>\n                        <!-- 用户名称 -->\n                        <template #username=\"{ record }\">\n                            <a-link @click=\"detailBtnClick(record.userId)\" icon>{{ record.username }}</a-link>\n                        </template>\n                        <!-- 用户头像 -->\n                        <template #userAvatar=\"{ record }\">\n                            <a-avatar :size=\"28\">\n                                <img alt=\"头像\" :src=\"record.userAvatar\" v-if=\"record.userAvatar\" />\n                                <img alt=\"头像\" src=\"../../../assets/default-avatar.jpg\" v-else />\n                            </a-avatar>\n                        </template>\n                        <!-- 用户性别 -->\n                        <template #userSex=\"{ record }\">\n                            <dict-convert :dict-data=\"dicts[proxy.DICT.userSex]\" :dict-key=\"record.userSex\" />\n                        </template>\n                        <!-- 用户状态 -->\n                        <template #status=\"{ record }\">\n                            <dict-convert :dict-data=\"dicts[proxy.DICT.commonNumberStatus]\" :dict-key=\"record.status\" />\n                        </template>\n                        <!-- 操作 -->\n                        <template #operation=\"{ record }\">\n                            <a-button v-perm=\"['basic:user:update']\" type=\"text\" size=\"mini\" @click=\"updateBtnClick(record.userId)\">\n                                <template #icon>\n                                    <icon-edit />\n                                </template>\n                                <template #default>修改</template>\n                            </a-button>\n                            <a-popconfirm content=\"确认要删除吗?\" @ok=\"deleteBtnOkClick(record.userId)\">\n                                <a-button v-perm=\"['basic:user:delete']\" type=\"text\" status=\"danger\" size=\"mini\">\n                                    <template #icon>\n                                        <icon-delete />\n                                    </template>\n                                    <template #default>删除</template>\n                                </a-button>\n                            </a-popconfirm>\n                        </template>\n                    </a-table>\n                </a-row>\n\n                <!-- 分页 -->\n                <a-row class=\"w-full flex justify-end mt-2\">\n                    <pagination v-if=\"datatable.total > 0\"\n                        v-model:page-num=\"searchForm.pageNum\"\n                        v-model:page-size=\"searchForm.pageSize\"\n                        :total=\"datatable.total\" @pagination=\"getPageList(false)\" />\n                </a-row>\n            </a-row>\n        </div>\n\n        <!-- 添加/修改 -->\n        <a-modal v-model:visible=\"modal.visible\" width=\"50%\" :esc-to-close=\"false\" :mask-closable=\"false\" draggable :footer=\"false\">\n            <template #title>{{ modal.title }}</template>\n            <component :is=\"modal.component\" :params=\"modal.params\" @ok=\"onOk\" @cancel=\"onCancel\" v-if=\"modal.visible\" />\n        </a-modal>\n\n    </a-card>\n</template>\n\n<script setup>\nimport {ref, reactive, getCurrentInstance, shallowRef, onMounted} from 'vue'\nimport { getPageUserListApi, deleteUserByUserIdApi } from \"~/api/user.js\";\nimport UserEdit from \"~/pages/basic/user/UserEdit.vue\";\nimport UserDetail from \"~/pages/basic/user/UserDetail.vue\";\nimport {getDeptListApi} from \"~/api/dept.js\";\n\n//全局实例\nconst {proxy} = getCurrentInstance()\n//加载字典\nconst dicts = proxy.LoadDicts([proxy.DICT.commonNumberStatus, proxy.DICT.deptList, proxy.DICT.userSex])\n//是否展示搜索区域\nconst showSearchRow = ref(true)\n//搜索参数表单\nconst searchForm = reactive({\n    //用户真实姓名\n    userRealName: null,\n    //用户手机\n    phone: null,\n    //用户状态\n    status: null,\n    //页码\n    pageNum: 1,\n    //条数\n    pageSize: 10\n})\n//数据列表\nconst datatable = reactive({\n    //列配置\n    columns: [\n        {title: '用户账号', dataIndex: 'username', slotName: 'username', align: 'center', width: 150, ellipsis: true, tooltip: true},\n        {title: '用户昵称', dataIndex: 'nickname', align: 'center', width: 150, ellipsis: true, tooltip: true},\n        {title: '用户头像', dataIndex: 'userAvatar', slotName: 'userAvatar', align: 'center', width: 100},\n        {title: '用户姓名', dataIndex: 'userRealName', align: 'center', width: 100},\n        {title: '用户性别', dataIndex: 'userSex', slotName: 'userSex', align: 'center', width: 90},\n        {title: '手机号', dataIndex: 'phone', align: 'center', width: 125},\n        {title: '邮箱', dataIndex: 'email', align: 'center', width: 200, ellipsis: true, tooltip: true},\n        {title: '用户状态', dataIndex: 'status', slotName: 'status', align: 'center', width: 90},\n        {title: '操作', slotName: 'operation', align: 'center', width: 160, fixed: 'right'}\n    ],\n    //加载\n    loading: false,\n    //数据列表\n    records: [],\n    //总条数\n    total: 0\n})\n//查询数据列表\nconst getPageList = (isReset = false) => {\n    if (isReset) {\n        searchForm.userRealName = null\n        searchForm.phone = null\n        searchForm.status = null\n        searchForm.pageNum = 1\n        searchForm.pageSize = 10\n    }\n    datatable.loading = true\n    searchForm.deptId = selectDept.value[0]\n    getPageUserListApi(searchForm).then(res => {\n        datatable.records = res.records\n        datatable.total = res.total\n    }).finally(() => {\n        datatable.loading = false\n    })\n}\n//公共模态框\nconst modal = reactive({\n    //是否显示\n    visible: false,\n    //模态框标题\n    title: '用户管理',\n    //传递参数\n    params: {},\n    //组件名称\n    component: null\n});\n//添加按钮 -> 点击事件\nconst addBtnClick = () => {\n    modal.visible = true\n    modal.title = '添加用户'\n    modal.params = { operationType: proxy.operationType.add.type }\n    modal.component = shallowRef(UserEdit)\n}\n//表格行数据 \"修改\" -> 点击\nconst updateBtnClick = (userId) => {\n    modal.visible = true\n    modal.title = '修改用户'\n    modal.params = { operationType: proxy.operationType.update.type, userId: userId }\n    modal.component = shallowRef(UserEdit)\n}\n//表格行数据 \"查看\" -> 点击\nconst detailBtnClick = (userId) => {\n    modal.visible = true\n    modal.title = '用户详细信息'\n    modal.params = { operationType: proxy.operationType.detail.type, userId: userId }\n    modal.component = shallowRef(UserDetail)\n}\n//表格行数据 \"删除\" -> 确认\nconst deleteBtnOkClick = (userId) => {\n    deleteUserByUserIdApi(userId).then(() => {\n        proxy.$msg.success(proxy.operationType.delete.success)\n        //刷新列表\n        getPageList()\n    })\n}\n//模态框 -> 确认\nconst onOk = () => {\n    modal.visible = false\n    //查询数据列表\n    getPageList()\n}\n//模态框 -> 取消\nconst onCancel = () => {\n    modal.visible = false\n}\n\n//当前选中的部门\nconst selectDept = ref(['0'])\n//查询部门数据列表\nconst treeRef = ref()\nconst deptTree = ref([])\nconst loadDeptListLoading = ref(false)\nconst getDeptList = () => {\n    loadDeptListLoading.value = true\n    getDeptListApi({status: 1}).then(res => {\n        deptTree.value = [{deptId: '0', deptName: '全部', children: res}]\n    }).finally(() => {\n        loadDeptListLoading.value = false\n    })\n}\n//点击\nconst selectDeptChange = (selectedKeys, data) => {\n    //查询数据列表\n    getPageList()\n}\n\nonMounted(() => {\n    //查询数据列表\n    getPageList()\n    //查询部门数据列表\n    getDeptList()\n})\n</script>\n<style scoped>\n/* 侧边栏滚动条 */\n:deep(.arco-tree::-webkit-scrollbar) {\n    width: 16px;\n    height: 4px;\n}\n:deep(.arco-tree::-webkit-scrollbar-thumb) {\n    border: 4px solid transparent;\n    background-clip: padding-box;\n    border-radius: 7px;\n    background-color: var(--color-text-4);\n}\n:deep(.arco-tree::-webkit-scrollbar-thumb:hover) {\n    background-color: var(--color-text-3);\n}\n</style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/basic/user/UserSetting.vue",
    "content": "<template>\n    <div>\n        <a-space direction=\"vertical\" fill>\n            <a-card>\n                <div class=\"flex items-center\">\n                    <a-avatar :size=\"100\" @click=\"avatarBtnClick()\" class=\"bg-blue-300\">\n                        <template #trigger-icon>\n                            <IconCamera />\n                        </template>\n                        <template v-if=\"sysStore.user?.userAvatar\">\n                            <img :src=\"sysStore.user.userAvatar\" alt=\"头像\" />\n                        </template>\n                        <template v-else>\n                            <img src=\"../../../assets/default-avatar.jpg\" alt=\"头像\" />\n                        </template>\n                    </a-avatar>\n                    <a-descriptions :column=\"2\" bordered class=\"ml-10\">\n                        <a-descriptions-item label=\"用户账号\">{{ sysStore.user.username }}</a-descriptions-item>\n                        <a-descriptions-item label=\"用户昵称\">{{ userInfoForm.nickname }}</a-descriptions-item>\n                        <a-descriptions-item label=\"用户姓名\">{{ userInfoForm.userRealName }}</a-descriptions-item>\n                        <a-descriptions-item label=\"用户手机\">{{ userInfoForm.phone }}</a-descriptions-item>\n                        <a-descriptions-item label=\"用户邮箱\">{{ userInfoForm.email }}</a-descriptions-item>\n                        <a-descriptions-item label=\"用户性别\">\n                            <dict-convert :dict-data=\"dicts[proxy.DICT.userSex]\" :dict-key=\"userInfoForm.userSex\" />\n                        </a-descriptions-item>\n                        <a-descriptions-item label=\"用户岗位\">\n                            <template v-for=\"(post, index) in sysStore.user.postList\" :key=\"index\">\n                                {{ post.postName }}\n                                {{index < sysStore.user.postList.length - 1 ? '   ' : ''}}\n                            </template>\n                        </a-descriptions-item>\n                        <a-descriptions-item label=\"所属部门\">\n                            <template v-for=\"(dept, index) in sysStore.user.deptList\" :key=\"index\">\n                                {{ dept.deptName }}\n                                {{index < sysStore.user.deptList.length - 1 ? '   ' : ''}}\n                            </template>\n                        </a-descriptions-item>\n                    </a-descriptions>\n                </div>\n            </a-card>\n            <a-card>\n                <a-spin class=\"w-[100%]\" :size=\"35\" :loading=\"spinLoading\" tip=\"正在处理, 请稍候...\">\n                    <a-tabs type=\"rounded\" default-active-key=\"user-info\">\n                        <a-tab-pane key=\"user-info\" title=\"基本信息\">\n                            <a-form :model=\"userInfoForm\" ref=\"userInfoFormRef\" :rules=\"userInfoFormRules\" auto-label-width>\n                                <a-form-item class=\"w-[350px]\" field=\"nickname\" label=\"用户昵称\" required>\n                                    <a-input v-model=\"userInfoForm.nickname\" placeholder=\"用户昵称\" />\n                                </a-form-item>\n                                <a-form-item class=\"w-[350px]\" field=\"userRealName\" label=\"用户真实姓名\" required>\n                                    <a-input v-model=\"userInfoForm.userRealName\" placeholder=\"用户真实姓名\" />\n                                </a-form-item>\n                                <a-form-item class=\"w-[350px]\" field=\"phone\" label=\"手机号\" required>\n                                    <a-input v-model=\"userInfoForm.phone\" placeholder=\"手机号\" />\n                                </a-form-item>\n                                <a-form-item class=\"w-[350px]\" field=\"email\" label=\"邮箱\">\n                                    <a-input v-model=\"userInfoForm.email\" placeholder=\"邮箱\" />\n                                </a-form-item>\n                                <a-form-item class=\"w-[350px]\" field=\"userSex\" label=\"用户性别\" required>\n                                    <a-select v-model=\"userInfoForm.userSex\" placeholder=\"用户性别\" allow-clear allow-search>\n                                        <a-option v-for=\"(d, index) in dicts[proxy.DICT.userSex]\" :key=\"index\" :value=\"d.dictKey\" :label=\"d.dictValue\" />\n                                    </a-select>\n                                </a-form-item>\n                            </a-form>\n\n                            <!-- 分割线 -->\n                            <a-divider class=\"mt-0\" />\n\n                            <div class=\"w-[300px] flex justify-end\">\n                                <a-space>\n                                    <a-button @click=\"closePage()\">关闭</a-button>\n                                    <a-button type=\"primary\" @click=\"updateUserInfoOkBtnClick()\">确定</a-button>\n                                </a-space>\n                            </div>\n                        </a-tab-pane>\n                        <a-tab-pane key=\"re-password\" title=\"修改密码\">\n                            <a-form :model=\"rePasswordForm\" ref=\"rePasswordFormRef\" :rules=\"rePasswordFormRules\" auto-label-width>\n                                <a-form-item class=\"w-[350px]\" field=\"oldPassword\" label=\"旧密码\" required>\n                                    <a-input-password v-model=\"rePasswordForm.oldPassword\" placeholder=\"旧密码\" />\n                                </a-form-item>\n                                <a-form-item class=\"w-[350px]\" field=\"newPassword\" label=\"新密码\" required>\n                                    <a-input-password v-model=\"rePasswordForm.newPassword\" placeholder=\"新密码\" />\n                                </a-form-item>\n                                <a-form-item class=\"w-[350px]\" field=\"reNewPassword\" label=\"重复新密码\" required>\n                                    <a-input-password v-model=\"rePasswordForm.reNewPassword\" placeholder=\"重复新密码\" />\n                                </a-form-item>\n                            </a-form>\n\n                            <!-- 分割线 -->\n                            <a-divider class=\"mt-0\" />\n\n                            <div class=\"w-[300px] flex justify-end\">\n                                <a-space>\n                                    <a-button @click=\"closePage()\">关闭</a-button>\n                                    <a-button type=\"primary\" @click=\"rePasswordOkBtnClick()\">确定</a-button>\n                                </a-space>\n                            </div>\n                        </a-tab-pane>\n                    </a-tabs>\n                </a-spin>\n            </a-card>\n        </a-space>\n\n        <!-- 裁剪头像模态框 -->\n        <a-modal v-model:visible=\"avatarModal.visible\" width=\"400px\" :esc-to-close=\"false\" :mask-closable=\"false\" draggable :footer=\"false\">\n            <template #title>修改头像</template>\n            <div class=\"flex items-center\">\n                <div class=\"w-[100%] h-[100%] flex flex-col items-center\">\n                    <a-spin :size=\"35\" :loading=\"userAvatarLoading\" tip=\"正在处理, 请稍候...\">\n                        <vue-cropper style=\"width: 300px; height: 300px;\" ref=\"cropperRef\"\n                            :img=\"avatarModal.img\"\n                            :outputSize=\"avatarModal.outputSize\"\n                            :outputType=\"avatarModal.outputType\"\n                            :info=\"avatarModal.info\"\n                            :canScale=\"avatarModal.canScale\"\n                            :autoCrop=\"avatarModal.autoCrop\"\n                            :autoCropWidth=\"avatarModal.autoCropWidth\"\n                            :autoCropHeight=\"avatarModal.autoCropHeight\"\n                            :fixedBox=\"avatarModal.fixedBox\"\n                            :fixed=\"avatarModal.fixed\"\n                            :fixedNumber=\"avatarModal.fixedNumber\"\n                            :canMove=\"avatarModal.canMove\"\n                            :canMoveBox=\"avatarModal.canMoveBox\"\n                            :original=\"avatarModal.original\"\n                            :centerBox=\"avatarModal.centerBox\"\n                            :infoTrue=\"avatarModal.infoTrue\"\n                            :full=\"avatarModal.full\"\n                            :enlarge=\"avatarModal.enlarge\"\n                            :mode=\"avatarModal.mode\"\n                            >\n                        </vue-cropper>\n                    </a-spin>\n                    <p class=\"mt-3\">提示: 头像文件大小需小于100kb。</p>\n                </div>\n            </div>\n\n            <!-- 分割线 -->\n            <a-divider />\n\n            <div class=\"flex justify-between\">\n                <a-upload v-model=\"avatarFileList\" action=\"\" :show-file-list=\"false\" :auto-upload=\"false\" @change=\"customUpload\" accept=\".jpg, .jpeg, .png, .jfif, .bmp, .webp\" />\n                <a-space>\n                    <a-button @click=\"avatarModalClose()\">关闭</a-button>\n                    <a-button type=\"primary\" @click=\"avatarOkBtnClick()\">确定</a-button>\n                </a-space>\n            </div>\n        </a-modal>\n    </div>\n</template>\n\n<script setup>\nimport {ref, reactive, getCurrentInstance} from 'vue'\nimport { useSysStore } from '~/store/module/sys-store.js'\nimport { useRouter } from 'vue-router'\nimport {resetPasswordApi, updateUserInfoApi, updateUserAvatarApi} from \"~/api/user.js\";\nimport 'vue-cropper/dist/index.css'\nimport { VueCropper }  from \"vue-cropper\";\n\n\n//路由\nconst router = useRouter()\n//全局实例\nconst {proxy} = getCurrentInstance()\n//加载字典\nconst dicts = proxy.LoadDicts([proxy.DICT.userSex])\n//缓存\nconst sysStore = useSysStore()\n//裁剪头像组件ref\nconst cropperRef = ref(null)\n//头像文件\nconst avatarFileList = ref([])\n//裁剪头像模态框\nconst avatarModal = reactive({\n    //是否显示\n    visible: false,\n    //裁剪图片的地址 url 地址, base64, blob\n    img: '',\n    //裁剪生成图片的质量\n    outputSize: 0.6,\n    //裁剪生成图片的格式 jpeg, png, webp\n    outputType: 'jpeg',\n    //裁剪框的大小信息\n    info: false,\n    //图片是否允许滚轮缩放\n    canScale: false,\n    //是否默认生成截图框\n    autoCrop: true,\n    //默认生成截图框宽度\n    autoCropWidth: 300,\n    //默认生成截图框高度\n    autoCropHeight: 300,\n    //固定截图框大小 不允许改变\n    fixedBox: false,\n    //是否开启截图框宽高固定比例，这个如果设置为true，截图框会是固定比例缩放的，如果设置为false，则截图框的宽高比例就不固定了\n    fixed: true,\n    //上传图片是否可以移动\n    canMove: false,\n    //截图框能否拖动\n    canMoveBox: true,\n    //上传图片按照原始比例渲染\n    original: false,\n    //截图框是否被限制在图片里面\n    centerBox: true,\n    //true展示真实输出图片宽高 false展示看到的截图框宽高\n    infoTrue: true,\n    //是否输出原图比例的截图\n    full: true,\n    //图片根据截图框输出比例倍数\n    enlarge: '1',\n    //图片默认渲染方式 contain , cover, 100px, 100% auto\n    mode: 'contain'\n})\n//加载中...\nconst spinLoading = ref(false)\n//修改密码表单ref\nconst rePasswordFormRef = ref(null)\n//修改密码表单\nconst rePasswordForm = reactive({\n    oldPassword: null,\n    newPassword: null,\n    reNewPassword: null,\n})\n//修改密码表单校验规则\nconst rePasswordFormRules = {\n    oldPassword: [{required: true, message: '旧密码输入不能为空', trigger: 'submit'}],\n    newPassword: [{required: true, message: '新密码输入不能为空', trigger: 'submit'}],\n    reNewPassword: [{required: true, message: '重复输入新密码不能为空', trigger: 'submit'}]\n}\n//修改基本信息表单ref\nconst userInfoFormRef = ref(null)\n//修改基本信息表单\nconst userInfoForm = reactive({\n    //用户昵称\n    nickname: sysStore.user.nickname,\n    //用户真实姓名\n    userRealName: sysStore.user.userRealName,\n    //手机号\n    phone: sysStore.user.phone,\n    //邮箱\n    email: sysStore.user.email,\n    //用户性别\n    userSex: sysStore.user.userSex\n})\n//修改基本信息表单校验规则\nconst userInfoFormRules = {\n    nickname: [{required: true, message: '用户昵称不能为空', trigger: 'submit'}],\n    userRealName: [{required: true, message: '用户姓名不能为空', trigger: 'submit'}],\n    phone: [{required: true, message: '手机号不能为空', trigger: 'submit'}],\n    userSex: [{required: true, message: '性别不能为空', trigger: 'submit'}]\n}\n//关闭页面\nconst closePage = () => {\n    //跳转页面到后台首页\n    router.push('/')\n}\n//点击头像\nconst avatarBtnClick = () => {\n    avatarModal.visible = true\n    if (sysStore.user.userAvatar) {\n        avatarModal.img = sysStore.user.userAvatar\n    }\n}\n//关闭裁剪头像\nconst avatarModalClose = () => {\n    avatarModal.visible = false\n}\n//头像自定义上传处理\nconst customUpload = (fileList, fileItem) => {\n    //校验图片大小\n    if (fileItem.file.size > 100 * 1024) {\n        proxy.$msg.error('头像文件需小于100kb')\n    } else {\n        //本地文件展示让用户裁剪\n        avatarModal.img = fileItem.url\n    }\n}\n//修改密码确定\nconst rePasswordOkBtnClick = () => {\n    //表单验证\n    rePasswordFormRef.value.validate((valid) => {\n        if (valid) {return false}\n        //校验两次输入的密码是否一致\n        if (rePasswordForm.newPassword !== rePasswordForm.reNewPassword) {\n            proxy.$msg.error('新密码两次输入的不一致')\n            return false\n        }\n        //加载中\n        spinLoading.value = true\n        //登录请求\n        resetPasswordApi(rePasswordForm).then(res => {\n            //消息提示\n            proxy.$msg.success(proxy.operationType.update.success + '，下次登录请使用新密码登录')\n            //跳转到登录页\n            router.push('/login')\n        }).finally(() => {\n            //加载完毕\n            spinLoading.value = false\n        })\n    })\n}\n//修改用户基本信息确定\nconst updateUserInfoOkBtnClick = () => {\n    //表单验证\n    userInfoFormRef.value.validate((valid) => {\n        if (valid) {return false}\n        //加载中\n        spinLoading.value = true\n        //登录请求\n        updateUserInfoApi(userInfoForm).then(res => {\n            //消息提示\n            proxy.$msg.success(proxy.operationType.update.success)\n        }).finally(() => {\n            //加载完毕\n            spinLoading.value = false\n        })\n    })\n}\n//上传头像加载中...\nconst userAvatarLoading = ref(false)\n//裁剪头像确定\nconst avatarOkBtnClick = () => {\n    userAvatarLoading.value = true\n    //获取截图base64数据\n    cropperRef.value.getCropData(data => {\n        updateUserAvatarApi(data).then(res => {\n            //消息提示\n            proxy.$msg.success(proxy.operationType.update.success)\n            //关闭模态框\n            avatarModal.visible = false\n            //头像变更\n            sysStore.updateUserAvatar(data)\n        }).finally(() => {\n            userAvatarLoading.value = false\n        })\n    })\n}\n</script>\n<style scoped>\n:deep(.arco-avatar-trigger-icon-button) {\n    width: 35px;\n    height: 35px;\n    line-height: 35px;\n}\n:deep(.arco-avatar-trigger-icon-button .arco-icon-camera) {\n    margin-top: 8px;\n    color: rgb(var(--arcoblue-6));\n    font-size: 18px;\n}\n</style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/container.vue",
    "content": "<template>\n    <a-layout class=\"w-[100%] h-[100%]\">\n        <!-- 头部header -->\n        <a-layout-header class=\"m-header\">\n            <m-header @tenant-change=\"tenantChange\" />\n        </a-layout-header>\n        <a-layout>\n            <!-- 左侧菜单 -->\n            <a-layout-sider\n                class=\"m-sider\"\n                breakpoint=\"lg\"\n                collapsible\n                hide-trigger\n                :width=\"siderWidth\"\n                :collapsed=\"siderCollapsed\">\n                <div class=\"sider-div\">\n                    <m-sider />\n                </div>\n            </a-layout-sider>\n            <a-layout-content class=\"m-content\" :style=\"{left: siderWidth + 'px'}\">\n                <!-- tab页 -->\n                <PageTabList ref=\"pageTabListRef\" />\n                <!-- 主体内容，通过router动态变换 -->\n                <router-view v-slot=\"{ Component }\">\n                    <!-- transition动画效果，使用transition 每个页面必须只有1个根节点 -->\n                    <transition name=\"fade\">\n                        <keep-alive :include=\"sysStore.includePage\">\n                            <component :is=\"Component\"></component>\n                        </keep-alive>\n                    </transition>\n                </router-view>\n            </a-layout-content>\n        </a-layout>\n    </a-layout>\n</template>\n\n<script setup>\nimport MSider from \"../components/MSider.vue\";\nimport MHeader from \"../components/MHeader.vue\";\nimport PageTabList from \"../components/PageTabList.vue\"\nimport { storeToRefs } from 'pinia'\nimport { useSysStore } from '~/store/module/sys-store.js'\nimport {getCurrentInstance, inject, ref} from \"vue\";\n\n//全局实例\nconst {proxy} = getCurrentInstance()\n//缓存\nconst sysStore = useSysStore()\n//响应式数据：siderCollapsed: sider是否展开，siderWidth: sider宽度\nconst { siderCollapsed, siderWidth } = storeToRefs(sysStore)\n\n//tabListRef\nconst pageTabListRef = ref()\n//App.vue提供的reload方法\nconst reload = inject('reload')\n//刷新倒计时\nconst reloadCountDown = ref(5)\n//租户切换\nconst tenantChange = () => {\n    //消息提示\n    reloadCountDown.value = 5\n    proxy.$msg.error(`注意：正在为您切换到该租户的管理员身份，${reloadCountDown.value} 秒后将刷新页面`)\n    reloadCountDown.value--\n    //关闭所有tab页\n    pageTabListRef.value.tabDropdownSelect('clearAll')\n    //清除页面缓存\n    sysStore.includePage = []\n\n    //倒计时刷新页面\n    const timer = setInterval(() => {\n        proxy.$msg.error(`注意：正在为您切换到该租户的管理员身份，${reloadCountDown.value} 秒后将刷新页面`)\n        reloadCountDown.value--\n        //倒计时 = 0，刷新页面\n        if (reloadCountDown.value <= 0) {\n            clearInterval(timer)\n            // 延迟1秒后执行\n            setTimeout(() => {\n                //重新加载\n                //reload()\n                //重新刷新页面\n                location.reload()\n            }, 1000)\n        }\n    }, 1000)\n}\n</script>\n<style scoped>\n.m-header {\n    @apply flex items-center w-[100%] h-[50px] fixed top-0 left-0 right-0 z-50 transition-all shadow;\n    background-color: var(--color-bg-2);\n}\n.m-sider {\n    @apply fixed top-0 left-0 z-40 top-[50px] transition-all;\n    height: calc(100% - 50px);\n    overflow-y: hidden;\n}\n.m-content {\n    @apply fixed top-[50px] right-0 bottom-0 overflow-y-auto p-3;\n    background-color: var(--color-neutral-1);\n    width: calc(100% - 200px);\n    height: calc(100% - 50px)\n}\n/* 隐藏侧边栏的滚动条 */\n:deep(.arco-layout-sider-children) {\n    overflow-y: hidden;\n}\n/* 侧边栏div */\n.sider-div {\n    height: 100%;\n    overflow: auto;\n    overflow-x: hidden;\n}\n/* 侧边栏滚动条 */\n:deep(.arco-menu ::-webkit-scrollbar) {\n    width: 16px;\n    height: 4px;\n}\n:deep(.arco-menu ::-webkit-scrollbar-thumb) {\n    border: 4px solid transparent;\n    background-clip: padding-box;\n    border-radius: 7px;\n    background-color: var(--color-text-4);\n}\n:deep(.arco-menu ::-webkit-scrollbar-thumb:hover) {\n    background-color: var(--color-text-3);\n}\n</style>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/index.vue",
    "content": "<template>\n    <div class=\"w-[100%]\" style=\"height: calc(100% - 50px)\">\n        <a-card class=\"w-[100%] text-2xl py-1\">\n            您好，{{sysStore.user?.username}}，祝您生活愉快!\n        </a-card>\n        <div class=\"w-[100%] flex justify-between mt-3\">\n            <a-card title=\"更新日志\" class=\"w-[50%] mr-2\">\n                <a-collapse :bordered=\"false\">\n                    <a-collapse-item header=\"V 1.0.0\" key=\"1.0.0\">\n                        <template #extra>\n                            2024-11-01\n                        </template>\n                        <div>架构上的重大改变或者其他重大改变，修改第一位版本号</div>\n                        <div>版本迭代，修改第二个版本号</div>\n                        <div>bug修复、小修改，修改第三个版本号</div>\n                    </a-collapse-item>\n                </a-collapse>\n            </a-card>\n            <a-card title=\"系统公告\" class=\"w-[50%] ml-2\">\n                <a-table class=\"w-[100%]\" :show-header=\"false\" :columns=\"datatable.columns\" :data=\"datatable.records\" :loading=\"datatable.loading\" :bordered=\"false\" :pagination=\"false\" table-layout-fixed>\n                    <!-- 公告标题 -->\n                    <template #noticeTitle=\"{ record }\">\n                        <a-link @click=\"detailBtnClick(record.noticeId)\" icon>{{ record.noticeTitle }}</a-link>\n                    </template>\n                </a-table>\n                <pagination class=\"mt-5\" v-if=\"datatable.total > 0\"\n                            v-model:page-num=\"searchForm.pageNum\"\n                            v-model:page-size=\"searchForm.pageSize\"\n                            :total=\"datatable.total\" @pagination=\"getPageNoticeList()\" />\n            </a-card>\n\n            <!-- 公告详情 -->\n            <a-modal v-model:visible=\"noticeModal.visible\" width=\"1080px\" simple draggable :footer=\"false\">\n                <a-spin class=\"w-[100%]\" :size=\"35\" :loading=\"spinLoading\" tip=\"正在处理, 请稍候...\">\n                    <div class=\"flex justify-center text-2xl\">{{noticeModal.noticeData.noticeTitle}}</div>\n                    <div class=\"flex justify-center mt-5\">\n                        发布时间：{{ noticeModal.noticeData.publishTime }}\n                    </div>\n                    <div class=\"flex mt-5\">\n                        <div v-html=\"noticeModal.noticeData.noticeContent\"></div>\n                    </div>\n                </a-spin>\n            </a-modal>\n        </div>\n    </div>\n</template>\n<script setup>\nimport {getNoticeByNoticeIdApi, getPageHomeNoticeListApi} from \"~/api/notice.js\";\nimport {reactive, ref} from \"vue\";\nimport {useSysStore} from \"~/store/module/sys-store.js\";\n\n//缓存\nconst sysStore = useSysStore()\n//搜索参数表单\nconst searchForm = reactive({\n    //页码\n    pageNum: 1,\n    //条数\n    pageSize: 10\n})\n//数据列表\nconst datatable = reactive({\n    //列配置\n    columns: [\n        {title: '公告标题', dataIndex: 'noticeTitle', slotName: 'noticeTitle', align: 'left', ellipsis: true, tooltip: true},\n        {title: '发布时间', dataIndex: 'publishTime', align: 'center', width: 170}\n    ],\n    //加载\n    loading: false,\n    //数据列表\n    records: [],\n    //总条数\n    total: 0\n})\n//查询数据列表\nconst getPageNoticeList = () => {\n    datatable.loading = true\n    getPageHomeNoticeListApi(searchForm).then(res => {\n        datatable.records = res.records\n        datatable.total = res.total\n    }).finally(() => {\n        datatable.loading = false\n    })\n}\n//公共模态框\nconst noticeModal = reactive({\n    //显示/隐藏\n    visible: false,\n    //公告数据\n    noticeData: {}\n});\n//加载中...\nconst spinLoading = ref(false)\n//加载公告详情\nconst detailBtnClick = (noticeId) => {\n    //加载公告数据\n    spinLoading.value = true\n    getNoticeByNoticeIdApi(noticeId).then(res => {\n        //数据赋值\n        if (res) {\n            //如果是外链，打开新页面跳转\n            if (res.noticeOutChain && res.noticeLink) {\n                window.open(res.noticeLink, '_blank')\n            } else {\n                //显示模态框\n                noticeModal.visible = true\n                //数据赋值\n                noticeModal.noticeData = res\n            }\n        }\n    }).finally(() => {\n        spinLoading.value = false\n    })\n}\n//查询数据列表\ngetPageNoticeList()\n</script>\n"
  },
  {
    "path": "minimalist-vue3/src/pages/login.vue",
    "content": "<template>\n    <a-row class=\"min-h-screen bg-indigo-400\" align=\"stretch\">\n        <a-col :lg=\"16\" :md=\"12\" :sm=\"24\" :xs=\"24\" class=\"flex items-center justify-center\">\n            <div>\n                <img class=\"login-pic-animation\" style=\"width: 35em;\" src=\"../assets/login-pic.png\" alt=\"\">\n            </div>\n        </a-col>\n        <a-col :lg=\"8\" :md=\"12\" :sm=\"24\" :xs=\"24\" class=\"flex flex-col items-center justify-center bg-light-50\">\n            <h2 class=\"font-bold text-3xl text-gray-800\">极简多租户管理系统</h2>\n            <div class=\"flex items-center justify-center my-5 text-gray-400 space-x-2\">\n                <span class=\"h-[1px] w-16 bg-gray-300\"></span>\n                <span>账号密码登录</span>\n                <span class=\"h-[1px] w-16 bg-gray-300\"></span>\n            </div>\n            <a-form ref=\"loginFormRef\" :rules=\"loginRules\" :model=\"loginForm\" class=\"w-[280px]\">\n                <a-form-item field=\"username\" feedback hide-label>\n                    <a-input v-model=\"loginForm.username\" placeholder=\"请输入用户名\" size=\"large\" allow-clear @keydown.enter=\"loginSubmitClick()\">\n                        <template #prefix>\n                            <icon-user />\n                        </template>\n                    </a-input>\n                </a-form-item>\n                <a-form-item field=\"password\" feedback hide-label>\n                    <a-input-password v-model=\"loginForm.password\" placeholder=\"请输入密码\" size=\"large\" allow-clear @keydown.enter=\"loginSubmitClick()\">\n                        <template #prefix>\n                            <icon-lock />\n                        </template>\n                    </a-input-password>\n                </a-form-item>\n                <a-form-item field=\"captcha\" feedback hide-label v-if=\"isCaptchaEnable\">\n                    <div class=\"flex items-center justify-between h-[100%]\">\n                        <a-input class=\"w-7/12\" v-model=\"loginForm.captcha\" placeholder=\"请输入验证码\" size=\"large\" allow-clear  @keydown.enter=\"loginSubmitClick()\">\n                            <template #prefix>\n                                <icon-robot />\n                            </template>\n                        </a-input>\n                        <div class=\"w-4/12\">\n                            <img :src=\"imageCaptchaBase64\" alt=\"刷新\" @click=\"getImageCaptcha\" class=\"w-[100%] h-[100%]\" />\n                        </div>\n                    </div>\n                </a-form-item>\n                <a-form-item hide-label>\n                    <a-button type=\"primary\" :loading=\"loginLoading\" class=\"w-[100%]\" @click=\"loginSubmitClick()\">登录</a-button>\n                </a-form-item>\n            </a-form>\n            <p>管理员账号/密码：admin/111111</p>\n            <p class=\"mt-2\"> 租户账号/密码：dongdong/111111</p>\n        </a-col>\n    </a-row>\n</template>\n<script setup>\nimport {ref, reactive, getCurrentInstance, onMounted} from 'vue'\nimport { getImageCaptchaApi, loginApi } from '~/api/user'\nimport { setToken } from '~/utils/cookie'\nimport { useRouter } from 'vue-router'\n//路由\nconst router = useRouter()\n//全局实例\nconst {proxy} = getCurrentInstance()\n\n/****************** 登录 ******************/\n//登录表单\nconst loginFormRef = ref(null)\nconst loginForm = reactive({\n    username: 'admin',\n    password: '111111',\n    captcha: null,\n    captchaId: null\n})\n//登录加载loading\nconst loginLoading = ref(false)\n//登录表单验证规则\nconst loginRules = {\n    username: [{required: true, message: '用户名不能为空', trigger: 'submit'}],\n    password: [{required: true, message: '密码不能为空', trigger: 'submit'}]\n}\n//登录点击事件\nconst loginSubmitClick = () => {\n    //登录表单验证\n    loginFormRef.value.validate((valid) => {\n        if (valid) {return false}\n        //加载中\n        loginLoading.value = true\n        //登录请求\n        loginApi(loginForm).then(res => {\n            //登录成功消息提示\n            proxy.$msg.success('登陆成功')\n            //存储token\n            setToken(res.tokenValue)\n            //跳转页面到后台首页\n            router.push('/')\n        }).catch(() => {\n            //清空输入的验证码\n            loginForm.captcha = null\n            loginForm.captchaId = null\n            //登录失败，重新刷新验证码\n            getImageCaptcha()\n        }).finally(() => {\n            //加载完毕\n            loginLoading.value = false\n        })\n    })\n}\n\n/****************** 验证码 ******************/\n//图形验证码base64\nconst imageCaptchaBase64 = ref('')\n//是否需要验证码\nconst isCaptchaEnable = ref(false)\n//获取图形验证码\nconst getImageCaptcha = () => {\n    getImageCaptchaApi().then(res => {\n        isCaptchaEnable.value = res.enable\n        if (isCaptchaEnable.value) {\n            loginRules.captcha = [{required: true, message: '验证码不能为空', trigger: 'submit'}]\n            imageCaptchaBase64.value = 'data:image/jpg;base64,' + res.captchaImg\n            loginForm.captchaId = res.captchaId\n        }\n    })\n}\n\nonMounted(() => {\n    //获取图形验证码\n    getImageCaptcha()\n})\n</script>\n<style scoped>\n@keyframes floating {\n    0% { transform: translateY(0); }\n    50% { transform: translateY(-20px); }\n    100% { transform: translateY(0); }\n}\n.login-pic-animation {\n    animation: floating 3s ease infinite;\n}\n</style>\n"
  },
  {
    "path": "minimalist-vue3/src/router/index.js",
    "content": "import {createRouter, createWebHashHistory} from \"vue-router\";\nimport {CHANGE_TENANT_ALLOW, getToken, setCookie} from \"~/utils/cookie\";\nimport Container from '~/pages/container.vue'\nimport Index from '~/pages/index.vue'\nimport UserSetting from '~/pages/basic/user/UserSetting.vue'\nimport {getUserInfoApi} from \"~/api/user.js\";\nimport { useSysStore } from '~/store/module/sys-store.js'\n//页面加载条\nimport NProgress from 'nprogress'\nimport'nprogress/nprogress.css'\n//登录页\nimport Login from '~/pages/login.vue'\n//404页面\nimport NotFound from '~/pages/404.vue'\n//网页标题\nconst pageTitle = '极简多租户管理系统'\n//公共路由，所有用户共享\nconst commonRoutes = [\n    {\n        path: '/',\n        name: 'Container',\n        component: Container,\n        children: [\n                {path: '/', component: Index, meta: {title: '控制台'}},\n                {path: '/user/setting', component: UserSetting, meta: {title: '用户设置'}},\n        ]\n    },\n    {path: '/login', name: 'Login', component: Login, meta: {title: '登录'}},\n    {path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound, meta: {title: ''}},\n]\n//导入所有路由，动态加载\nconst routeAll = import.meta.glob(`../pages/**/*.vue`);\nconst router = createRouter({\n    history: createWebHashHistory(),\n    routes: commonRoutes\n})\n\n//从路径中自动提取组件名\nconst getComponentNameFromPath = (path) => {\n    // 匹配最后一个 / 后的内容，并去掉 .vue 扩展名\n    const match = path.match(/\\/([^/.]+)\\.vue$/)\n    return match ? match[1] : null\n}\n\n/**\n * 动态添加路由\n * @param menus 菜单\n * @returns {boolean}\n */\nfunction dynamicAddRoutes(menus) {\n    //是否有新的路由\n    let hasNewRouter = false\n    //获取已有路由\n    let allRouter = router.getRoutes()\n    const findAndAddRoutesByMenus = (arr) => {\n        //遍历菜单\n        arr.forEach(e => {\n            //检查该路由是否已存在\n            let item = allRouter.find(o => o.path === e.permPath)\n            //如果未查找到，则该路由不存在，进行添加\n            if (!item) {\n                //如果指定了组件路径，则添加路由\n                if (e.component) {\n                    //查找路由\n                    let comp = findRouter(e.component)\n                    if (comp) {\n                        //向外层路由中添加子路由\n                        router.addRoute('Container', {\n                            path: e.permPath,\n                            meta: {title: e.permName, name: getComponentNameFromPath(e.component)},\n                            component: comp\n                        })\n                        hasNewRouter = true\n                    }\n                }\n            }\n            //如果包含子菜单，递归执行\n            if (e.children && e.children.length > 0) {\n                findAndAddRoutesByMenus(e.children)\n            }\n        })\n    }\n    findAndAddRoutesByMenus(menus)\n    return hasNewRouter\n}\n\n/**\n * 查找路由\n * @param component 路由\n * @returns import的路由\n */\nfunction findRouter(component) {\n    for (let key in routeAll) {\n        if (key.includes(component)) {\n            return routeAll[key]\n        }\n    }\n    //未找到路由\n    return null\n}\n\n//路由白名单 索引0为登录路由\nconst routerWhiteList = ['/login']\n//路由前置守卫\nrouter.beforeEach(async (to, from, next) => {\n    //显示加载条\n    NProgress.start()\n    //获取token\n    const token = getToken()\n    //如果已经登录 并且 还访问登录页\n    if (token && routerWhiteList[0] === to.path) {\n        //有原页面继续停留在原页面，否则将跳转至首页\n        return next({path: from.path ? from.path : '/'})\n    }\n    //白名单页面访问放行\n    if (routerWhiteList.includes(to.path)) {\n        return next()\n    }\n    //如果未登录，将跳转到登录页进行登录\n    if (!token) {\n        return next({path: routerWhiteList[0]})\n    }\n    //设置页面动态标题\n    document.title = (to.meta.title ? to.meta.title + ' - ' : '') + pageTitle\n    //是否有新添加的路由\n    let hasNewRouter = false\n\n    //缓存\n    let useStore = useSysStore()\n    //是否调用过getUserInfo接口，没有调用过则调用，调用后不再重复调用\n    let hasGetUserInfo = useStore.hasGetUserinfo\n    //未调用过getUserInfo，就执行获取用户信息、菜单等数据\n    if (!hasGetUserInfo) {\n        //获取用户信息\n        await getUserInfoApi().then(res => {\n            //用户信息存储到缓存\n            useStore.user = res\n            //已调用过getUserInfo\n            useStore.hasGetUserinfo = true\n            //渲染动态路由\n            hasNewRouter = dynamicAddRoutes(useStore.user.menus)\n            //此处将校验是否允许进行租户切换操作\n            //目前是系统管理员角色允许操作，可以自定义调整\n            //在MHeader.vue中租户切换功能配合v-if=\"getCookie(CHANGE_TENANT_ALLOW)\"来实现是否显示\n            //退出登录后，该cookie会被清除\n            let roles = res.roles || []\n            if (roles.includes('system_admin')) {\n                //允许操作租户切换\n                setCookie(CHANGE_TENANT_ALLOW, true)\n            }\n        }).catch(res => {\n            //401 认证失败，重新登录\n            if (res.response.status === 401) {\n                //退出\n                useStore.userLogoutHandler()\n                //跳转到登录页\n                return next({path: routerWhiteList[0]})\n            }\n        })\n    }\n\n    //放行，如果跳转到新添加的路由next()中需要给参数to.fullPath，否则刷新会404\n    hasNewRouter ?  next(to.fullPath) : next()\n})\n\n//路由后置守卫\nrouter.afterEach(() => {\n    //关闭加载条\n    NProgress.done()\n})\n\nexport default router\n"
  },
  {
    "path": "minimalist-vue3/src/store/index.js",
    "content": "import { createPinia } from 'pinia';\nconst pinia = createPinia();\nexport default pinia;"
  },
  {
    "path": "minimalist-vue3/src/store/module/sys-store.js",
    "content": "/**\n * 存储系统缓存\n */\nimport { defineStore } from 'pinia'\nimport { useCookies } from '@vueuse/integrations/useCookies'\nimport {\n    Authentication,\n    PAGE_TAB_LIST,\n    CHANGE_TENANT_ID,\n    CHANGE_TENANT_ID_BASE64,\n    CHANGE_TENANT_ALLOW\n} from \"~/utils/cookie.js\";\n\n//命名最好以 `use` 开头且以 `Store` 结尾，(比如 `useUserStore`，`useCartStore`，`useProductStore`)\n//\"sysStore\" -> 第一个参数是应用中 Store 的唯一ID\nexport const useSysStore = defineStore('sysStore', {\n\tstate: () => ({\n        //用户信息\n        user: null,\n        //是否调用过用户信息接口\n        hasGetUserinfo: false,\n        //sider展开/缩起，false展开，true缩起\n        siderCollapsed: false,\n        //sider当前宽度\n        siderWidth: 200,\n        //sider展开时的宽度\n        siderMaxWidth: 200,\n        //sider缩起时的宽度\n        siderMinWidth: 48,\n        //缓存的页面 - 首页index默认被缓存\n        includePage: ['index']\n    }),\n    actions: {\n        //修改用户头像\n        updateUserAvatar(userAvatar) {\n            this.user.userAvatar = userAvatar\n        },\n        //用户退出登录后的处理\n        userLogoutHandler() {\n            //清空cookie\n            let cookie = useCookies()\n            //清空请求头\n            cookie.remove(Authentication)\n            //清空租户切换的租户ID\n            cookie.remove(CHANGE_TENANT_ID)\n            //清空租户切换的租户ID\n            cookie.remove(CHANGE_TENANT_ID_BASE64)\n            //清空标签页\n            cookie.remove(PAGE_TAB_LIST)\n            //清空租户切换权限标记\n            cookie.remove(CHANGE_TENANT_ALLOW)\n            //清除当前用户状态\n            this.user = null\n            //是否获取过用户信息接口置为false\n            this.hasGetUserinfo = false\n        }\n    }\n})\n"
  },
  {
    "path": "minimalist-vue3/src/utils/cookie.js",
    "content": "import { useCookies } from '@vueuse/integrations/useCookies'\n\nconst cookie = useCookies()\n\n//租户切换的租户ID 字符串格式 key\nexport const CHANGE_TENANT_ID = 'change_tenant_id'\n//租户切换的租户ID base64格式 key\nexport const CHANGE_TENANT_ID_BASE64 = 'change_tenant_id_base64'\n//标签页 key\nexport const PAGE_TAB_LIST = 'pageTabList'\n//token key\nexport const Authentication = 'Authentication'\n//租户切换操作权限标记 key\nexport const CHANGE_TENANT_ALLOW = 'changeTenantAllow'\n\n//获取token\nexport function getToken() {\n    return cookie.get(Authentication)\n}\n\n//设置token\nexport function setToken(token) {\n    return cookie.set(Authentication, token)\n}\n\n//清除token\nexport function removeToken() {\n    return cookie.remove(Authentication)\n}\n\n\nexport function getCookie(key) {\n    return cookie.get(key)\n}\nexport function setCookie(key, value) {\n    return cookie.set(key, value)\n}\n"
  },
  {
    "path": "minimalist-vue3/src/utils/dict.js",
    "content": "import { getDictByDictTypeListApi } from '../api/dict'\nimport { reactive, onMounted } from 'vue';\n\nexport const DICT = {\n    //是/否\n    yesNo: 'yes-no',\n    //字典样式\n    dictClass: 'dict-class',\n    //权限类型\n    permType: 'perm-type',\n    //用户性别\n    userSex: 'user-sex',\n    //公告类型\n    noticeType: 'notice-type',\n    //文件来源\n    fileSource: 'file-source',\n    //通用状态 - 数字\n    commonNumberStatus: 'common-number-status',\n    //存储类型\n    storageType: 'storage-type',\n    //多租户数据隔离方式\n    tenantDataIsolation: 'tenant-data-isolation',\n\n    /**************** 额外字典 ***************/\n    //租户套餐列表 -> 额外字典\n    tenantPackageList: 'tenant-package-list',\n    //部门列表 -> 额外字典\n    deptList: 'dict-dept-list',\n    //用户列表 -> 额外字典\n    userList: 'dict-user-list',\n    //角色列表 -> 额外字典\n    roleList: 'dict-role-list',\n    //岗位列表 -> 额外字典\n    postList: 'dict-post-list',\n    //租户列表 -> 额外字典\n    tenantList: 'dict-tenant-list',\n    //存储列表 -> 额外字典\n    storageList: 'storage-list'\n}\n\n/**\n * 加载字典数据\n * @param dictTypeList 字典类型列表\n * @return 返回一个对象，key是字典类型，value是字典数据列表\n */\nexport const LoadDicts = (dictTypeList) => {\n    let result = reactive({})\n    if (!dictTypeList || dictTypeList.length === 0) {\n        return result;\n    }\n    //获取字典\n    onMounted(async () => {\n        await getDictByDictTypeListApi(dictTypeList).then(res => {\n            //处理数据\n            if (res && res.length > 0) {\n                res.forEach(dict => {\n                    let dictType = dict.dictType\n                    let dictList = dict.dictList || []\n                    dictList.forEach(d => {\n                        if (/^true|false$/.test(d.dictKey)) {\n                            //转布尔\n                            d.dictKey = d.dictKey === 'true'\n                        } else if (isNaN(d.dictKey)) {\n                            //转字符串\n                            d.dictKey = String(d.dictKey)\n                        } else {\n                            if (d.dictKey.length > 13) {\n                                //数字超过13位，转字符串\n                                d.dictKey = String(d.dictKey)\n                            } else {\n                                if (d.dictKey instanceof String) {\n                                    d.dictKey = String(d.dictKey)\n                                } else {\n                                    //转数字\n                                    d.dictKey = Number(d.dictKey)\n                                }\n                            }\n                        }\n                    })\n                    //存储到返回结果\n                    result[dictType] = dictList\n                })\n            }\n        })\n    });\n    return result\n}\n"
  },
  {
    "path": "minimalist-vue3/src/utils/msg.js",
    "content": "import { Message } from '@arco-design/web-vue';\n\nexport default {\n    success(msg) {\n        Message.success(msg || '操作成功')\n    },\n    error(msg) {\n        Message.error(msg || '操作失败')\n    }\n}"
  },
  {
    "path": "minimalist-vue3/src/utils/sys.js",
    "content": "import pinia from '../store'\nimport { useSysStore } from '../store/module/sys-store.js'\n\n//缓存\nconst sysStore = useSysStore(pinia)\n\n//公共的状态\nexport const status = {\n    status_0: {key: 0, value: '禁用'},\n    status_1: {key: 1, value: '正常'}\n}\n\n//全局 - '是/否' 枚举\nexport const yesNo = {\n    yes: {key: true, value: '是'},\n    no: {key: false, value: '否'}\n}\n//文件类型\nexport const fileType = {\n    image: {key: 'image', value: '图片'},\n    video: {key: 'video', value: '视频'}\n}\n//全局 - '文件来源' 枚举\nexport const fileSource = {\n    notice_cover_img: {key: 1, value: '系统公告封面图片'},\n    notice_content_img: {key: 2, value: '系统公告内容图片'},\n}\n//全局 - '文件类型' 枚举\nexport const fileAccept = {\n    //图片允许的类型\n    img: '.jpg,.jpeg,.png,.jfif,.bmp,.webp',\n    //视频允许的类型\n    video: '.mp4,.avi,.mov,.flv,.mpeg,.3gp,.rmvb,.ts',\n}\n//全局 - '上传文件列表样式' 枚举\nexport const fileListType = {\n    text: 'text',\n    picture: 'picture',\n    pictureCard: 'picture-card'\n}\n//操作类型\nexport const operationType = {\n    //新增\n    add: {type: 'add', success: '添加成功', error: '添加失败'},\n    //修改\n    update: {type: 'update', success: '修改成功', error: '修改失败'},\n    //查看详情\n    detail: {type: 'detail'},\n    //删除\n    delete: {type: 'delete', success: '删除成功', error: '删除失败'},\n    //上传\n    upload: {type: 'upload', success: '上传成功', error: '上传失败'},\n    //操作\n    operation: {type: 'operation', success: '操作成功', error: '操作失败'}\n}\n\n/**\n * 获取所有树中的父ID(只要有子集，就视为是父节点)\n * @param allTree 树\n * @param key 父ID的key名称\n * @returns [] 父ID\n */\nexport const getAllTreeParentId = (allTree, key) => {\n    //所有父ID\n    let allId = []\n    const findAllTree = (arr) => {\n        arr.forEach(node => {\n            //如果包含子集，递归执行\n            if (node.children && node.children.length > 0) {\n                //有子集，放入返回结果\n                allId.push(node[key])\n                //递归\n                findAllTree(node.children)\n            }\n        })\n    }\n    findAllTree(allTree)\n    return allId\n}\n\n//0~9\nconst characters = '0123456789';\n/**\n * 生成随机编码\n * @param length 编码位数\n */\nexport const randomCode = (length) => {\n    let result = '';\n    const charactersLength = characters.length;\n    for (let i = 0; i < length; i++) {\n        result += characters.charAt(Math.floor(Math.random() * charactersLength));\n    }\n    return result;\n}\n\n/**\n * 视频类型处理\n * @param url 视频url\n * @returns {`video/${string}`}\n */\nexport const videoTypeHandler = (url) => {\n    let type = url.split('.').pop().toLowerCase()\n    return `video/${type}`\n}\n\n/**\n * 检查权限，不止按照权限标识检查，若 userIdArr 中任意数据与当前登录用户一致，则放行\n * 场景举例：假设有一个项目管理系统，有一个产品需求列表，有很多行数据，\n * 当某一行数据的产品经理 = 当前登录的用户时，才显示修改和删除按钮，其余数据隐藏按钮\n * @param permArr 权限标识数组\n * @param userIdArr 放行的用户ID\n */\nexport const hasPerm = (permArr = [], userIdArr = []) => {\n    return checkPermOrRole(permArr, userIdArr, 'perm')\n}\n\n/**\n * 检查角色，不止按照角色标识检查，若 userIdArr 中任意数据与当前登录用户一致，则放行\n * 场景举例：假设有一个项目管理系统，有一个产品需求列表，有很多行数据，\n * 当某一行数据的产品经理 = 当前登录的用户时，才显示修改和删除按钮，其余数据隐藏按钮\n * @param roleArr 角色标识数组\n * @param userIdArr 放行的用户ID\n */\nexport const hasRole = (roleArr = [], userIdArr = []) => {\n    return checkPermOrRole(roleArr, userIdArr, 'role')\n}\n\nconst checkPermOrRole = (checkArr = [], userIdArr = [], checkType) => {\n    //当前登录用户ID\n    let currentLoginUserId = sysStore.user.userId\n    //传入的用户ID，与当前登录用户一致，则放行\n    let checkUser = userIdArr.includes(currentLoginUserId)\n    if (checkUser) { return true }\n    //校验权限或角色\n    let arr = checkType === 'role' ? sysStore.user.roles : sysStore.user.perms\n    return checkArr.some(elem => arr.includes(elem))\n}\n"
  },
  {
    "path": "minimalist-vue3/vite.config.js",
    "content": "import {defineConfig, loadEnv} from 'vite'\nimport vue from '@vitejs/plugin-vue'\nimport WindiCSS from 'vite-plugin-windicss'\nimport path from 'path'\n\nexport default defineConfig(({command, mode }) => {\n    //获取.env文件里定义的环境变量\n    const env = loadEnv(mode, process.cwd());\n    return {\n        resolve: {\n            //别名配置\n            alias: {\n                //输入 ~ ，相当于src目录\n                '~': path.resolve(__dirname, 'src')\n            }\n        },\n        //本地运行配置\n        server: {\n            //代理\n            proxy: {\n                //请求 /minimalist，相当于请求 env.VITE_REQUEST_URL\n                '/minimalist': {\n                    target: env.VITE_REQUEST_URL,\n                    //允许跨域\n                    changeOrigin: true,\n                    //规则匹配\n                    rewrite: (path) => path.replace(/^\\/minimalist/, '')\n                }\n            }\n        },\n        plugins: [vue(), WindiCSS()],\n    }\n})\n"
  },
  {
    "path": "pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>com.cn</groupId>\n    <artifactId>minimalist-saas</artifactId>\n    <version>1.0.0-SNAPSHOT</version>\n    <packaging>pom</packaging>\n\n    <modules>\n        <module>minimalist-basic</module>\n        <module>minimalist-application</module>\n    </modules>\n\n    <properties>\n        <!-- springboot版本 -->\n        <springboot.version>3.1.1</springboot.version>\n        <!-- redisson版本 -->\n        <redisson.version>3.36.0</redisson.version>\n        <!-- mysql驱动版本 -->\n        <mysql.version>8.0.33</mysql.version>\n        <!-- mybatis-flex版本 -->\n        <mybatis-flex.version>1.9.7</mybatis-flex.version>\n        <!-- mybatis-flex代码生成 -->\n        <mybatis-flex.codegen.version>1.8.3</mybatis-flex.codegen.version>\n        <!-- mybatis-flex使用的连接池 -->\n        <mybatis-flex.hikariCP.version>4.0.3</mybatis-flex.hikariCP.version>\n        <!-- 多数据源 -->\n        <dynamic-datasource.version>4.3.1</dynamic-datasource.version>\n        <!-- 七牛云 -->\n        <qiniu.version>7.18.0</qiniu.version>\n        <!-- hutool工具包 -->\n        <hutool.version>5.8.32</hutool.version>\n        <!-- sa-token -->\n        <satoken.version>1.39.0</satoken.version>\n        <!-- knife4j -->\n        <knife4j.version>4.1.0</knife4j.version>\n        <!-- spring-file -->\n        <spring-file.version>2.2.1</spring-file.version>\n        <!-- mica-xss -->\n        <mica-xss.version>3.3.2</mica-xss.version>\n        <!-- 缩略图处理 -->\n        <thumbnailator.version>0.4.20</thumbnailator.version>\n        <!-- webp配合做缩略图处理 -->\n        <webp-imageio.version>0.1.6</webp-imageio.version>\n        <!-- java -->\n        <java.version>17</java.version>\n        <!-- maven.compiler -->\n        <maven.compiler.source>17</maven.compiler.source>\n        <maven.compiler.target>17</maven.compiler.target>\n        <!-- 项目打包名称 -->\n        <project.package.name>minimalist-saas-backend</project.package.name>\n        <!-- 编码 -->\n        <project.package.encoding>UTF-8</project.package.encoding>\n    </properties>\n\n    <dependencyManagement>\n        <dependencies>\n            <!-- springboot统一依赖管理 -->\n            <dependency>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-dependencies</artifactId>\n                <version>${springboot.version}</version>\n                <type>pom</type>\n                <scope>import</scope>\n            </dependency>\n        </dependencies>\n    </dependencyManagement>\n\n    <dependencies>\n        <!-- spring-boot-test -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "resources/sql/mysql/2024-12-03 ~ 2025-03-12期间的SQL变更（第一次使用本项目忽略）.sql",
    "content": "-- 修改为mybatis flex框架后，deleted字段由 \"0已删除 1未删除\" 调整为 \"0未删除  1已删除\"\nALTER TABLE `minimalist`.`m_config` MODIFY COLUMN `deleted` bit(1) NULL DEFAULT b'0' COMMENT '逻辑删除  0未删除  1已删除' AFTER `update_time`;\nALTER TABLE `minimalist`.`m_dept` MODIFY COLUMN `deleted` bit(1) NULL DEFAULT b'0' COMMENT '逻辑删除  0未删除  1已删除' AFTER `update_time`;\nALTER TABLE `minimalist`.`m_dict` MODIFY COLUMN `deleted` bit(1) NULL DEFAULT b'0' COMMENT '逻辑删除  0未删除  1已删除' AFTER `update_time`;\nALTER TABLE `minimalist`.`m_file` MODIFY COLUMN `deleted` bit(1) NULL DEFAULT b'0' COMMENT '逻辑删除  0未删除  1已删除' AFTER `update_time`;\nALTER TABLE `minimalist`.`m_notice` MODIFY COLUMN `deleted` bit(1) NULL DEFAULT b'0' COMMENT '逻辑删除  0未删除  1已删除' AFTER `update_time`;\nALTER TABLE `minimalist`.`m_perms` MODIFY COLUMN `deleted` bit(1) NULL DEFAULT b'0' COMMENT '逻辑删除  0未删除  1已删除' AFTER `update_time`;\nALTER TABLE `minimalist`.`m_post` MODIFY COLUMN `deleted` bit(1) NULL DEFAULT b'0' COMMENT '逻辑删除  0未删除  1已删除' AFTER `update_time`;\nALTER TABLE `minimalist`.`m_role` MODIFY COLUMN `deleted` bit(1) NULL DEFAULT b'0' COMMENT '逻辑删除  0未删除  1已删除' AFTER `update_time`;\nALTER TABLE `minimalist`.`m_storage` MODIFY COLUMN `deleted` bit(1) NULL DEFAULT b'0' COMMENT '逻辑删除  0未删除  1已删除' AFTER `update_time`;\nALTER TABLE `minimalist`.`m_tenant_package` MODIFY COLUMN `deleted` bit(1) NULL DEFAULT b'0' COMMENT '逻辑删除  0未删除  1已删除' AFTER `update_time`;\nALTER TABLE `minimalist`.`m_user` MODIFY COLUMN `deleted` bit(1) NULL DEFAULT b'0' COMMENT '逻辑删除  0未删除  1已删除' AFTER `update_time`;\n\n\n-- 文件表删除 storage_type 存储类型 字段\nALTER TABLE `minimalist`.`m_file` DROP COLUMN `storage_type`;\n-- 文件表增加 storage_id 存储ID字段\nALTER TABLE `minimalist`.`m_file` ADD COLUMN `storage_id` bigint(0) NULL COMMENT '存储ID' AFTER `file_th_url`;\n-- 存储表删除 storage_default 默认存储字段\nALTER TABLE `minimalist`.`m_storage` DROP COLUMN `storage_default`;\n\n\n-- 租户表增加 data_isolation(数据隔离方式)、 datasource(数据源名称)、storage_id(存储ID) 字段\nALTER TABLE m_tenant\nADD COLUMN `data_isolation` varchar(6) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'column' COMMENT '数据隔离方式  column字段隔离(默认) db数据库隔离' AFTER `account_count`,\nADD COLUMN `datasource` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'master' COMMENT '数据源名称 master(默认使用主库) ' AFTER `data_isolation`,\nADD COLUMN `storage_id` bigint(0) NULL DEFAULT NULL COMMENT '存储ID 表示该租户使用哪个文件存储' AFTER `datasource`;\n\n-- 租户表字段顺序、字段默认值、注释调整\nALTER TABLE m_tenant\nMODIFY COLUMN `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注' AFTER `storage_id`,\nMODIFY COLUMN `status` tinyint(0) NULL DEFAULT 1 COMMENT '状态 0禁用 1正常' AFTER `remark`,\nMODIFY COLUMN `deleted` bit(1) NULL DEFAULT b'0' COMMENT '逻辑删除  0未删除  1已删除',\nMODIFY COLUMN `version` int(0) NULL DEFAULT 0 COMMENT '版本号';\n\n\n-- 新增 m_tenant_datasource 租户数据源表，目的是同时支持租户字段隔离和数据源隔离\nDROP TABLE IF EXISTS `m_tenant_datasource`;\nCREATE TABLE `m_tenant_datasource`  (\n  `id` bigint(0) NOT NULL AUTO_INCREMENT,\n  `datasource_id` bigint(0) NOT NULL COMMENT '数据源ID',\n  `tenant_id` bigint(0) NOT NULL COMMENT '租户ID',\n  `datasource_name` varchar(24) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '数据源名称',\n  `datasource_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '数据源连接',\n  `username` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '数据源用户名',\n  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '数据源密码',\n  PRIMARY KEY (`id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '租户数据源表' ROW_FORMAT = Dynamic;\n\n\n\n-- 新增七牛云存储数据\nINSERT INTO `minimalist`.`m_storage`(`storage_id`, `storage_name`, `storage_type`, `description`, `storage_config`, `status`, `create_id`, `create_time`, `update_id`, `update_time`, `deleted`, `version`) VALUES (1891827317205766144, '七牛云2', 'qiniu', NULL, '{\\\"accessKey\\\":\\\"2222\\\",\\\"secretKey\\\":\\\"2222\\\",\\\"endPoint\\\":\\\"2222\\\",\\\"bucketName\\\":\\\"2222\\\",\\\"regionId\\\":\\\"2222\\\"}', 1, 0, '2025-02-18 20:29:15.000000', 0, '2025-02-18 20:29:56.000000', b'0', 2);\n\n\n\n-- 新增字典数据\nINSERT INTO `minimalist`.`m_dict`(`dict_id`, `dict_type`, `dict_key`, `dict_value`, `dict_name`, `dict_desc`, `dict_order`, `dict_class`, `status`, `create_id`, `create_time`, `update_id`, `update_time`, `deleted`, `version`) VALUES (1890393477224509440, 'tenant-data-isolation', 'column', '字段隔离', '租户数据隔离方式', '多租户隔离方式：字段隔离 或 数据库隔离', 1, 'green', 1, 0, '2025-02-14 21:31:41.000000', 0, '2025-02-14 21:31:41.000000', b'0', 0);\nINSERT INTO `minimalist`.`m_dict`(`dict_id`, `dict_type`, `dict_key`, `dict_value`, `dict_name`, `dict_desc`, `dict_order`, `dict_class`, `status`, `create_id`, `create_time`, `update_id`, `update_time`, `deleted`, `version`) VALUES (1890393477329367040, 'tenant-data-isolation', 'db', '数据库隔离', '租户数据隔离方式', '多租户隔离方式：字段隔离 或 数据库隔离', 2, 'arcoblue', 1, 0, '2025-02-14 21:31:41.000000', 0, '2025-02-14 21:31:41.000000', b'0', 0);\nINSERT INTO `minimalist`.`m_dict`(`dict_id`, `dict_type`, `dict_key`, `dict_value`, `dict_name`, `dict_desc`, `dict_order`, `dict_class`, `status`, `create_id`, `create_time`, `update_id`, `update_time`, `deleted`, `version`) VALUES (1891825581577605120, 'storage-type', 'qiniu', '七牛云', '存储类型', '存储平台，如阿里云oss、腾讯云oss、七牛云oss等', 3, 'gold', 1, 0, '2025-02-18 20:22:21.000000', 0, '2025-02-18 20:29:34.000000', b'0', 2);\nINSERT INTO `minimalist`.`m_dict`(`dict_id`, `dict_type`, `dict_key`, `dict_value`, `dict_name`, `dict_desc`, `dict_order`, `dict_class`, `status`, `create_id`, `create_time`, `update_id`, `update_time`, `deleted`, `version`) VALUES (1899098996038688768, 'file-type', 'image', '图片', '文件类型', '文件类型，与file表中file_type字段对应', 0, NULL, 1, 0, '2025-03-10 22:04:18.000000', 0, '2025-03-10 22:04:18.000000', b'0', 0);\nINSERT INTO `minimalist`.`m_dict`(`dict_id`, `dict_type`, `dict_key`, `dict_value`, `dict_name`, `dict_desc`, `dict_order`, `dict_class`, `status`, `create_id`, `create_time`, `update_id`, `update_time`, `deleted`, `version`) VALUES (1899098996160323584, 'file-type', 'video', '视频', '文件类型', '文件类型，与file表中file_type字段对应', 0, NULL, 1, 0, '2025-03-10 22:04:18.000000', 0, '2025-03-10 22:04:18.000000', b'0', 0);\nINSERT INTO `minimalist`.`m_dict`(`dict_id`, `dict_type`, `dict_key`, `dict_value`, `dict_name`, `dict_desc`, `dict_order`, `dict_class`, `status`, `create_id`, `create_time`, `update_id`, `update_time`, `deleted`, `version`) VALUES (1899780153331810304, 'file-source-path', '-1', 'common/', '文件来源-存储位置', '文件来源对应的存储位置，配合\\\"文件来源\\\"一起使用', 0, NULL, 1, 0, '2025-03-12 19:11:03.000000', 0, '2025-03-12 19:11:03.000000', b'0', 0);\n\n\n\n"
  },
  {
    "path": "resources/sql/mysql/minimalist_全部sql,如果是第一次使用本项目直接执行这个.sql",
    "content": "/*\n Navicat Premium Data Transfer\n\n Source Server         : 优客得\n Source Server Type    : MySQL\n Source Server Version : 80024\n Source Host           : 117.50.179.102:12306\n Source Schema         : minimalist\n\n Target Server Type    : MySQL\n Target Server Version : 80024\n File Encoding         : 65001\n\n Date: 12/03/2025 22:00:20\n*/\n\nSET NAMES utf8mb4;\nSET FOREIGN_KEY_CHECKS = 0;\n\n-- ----------------------------\n-- Table structure for m_config\n-- ----------------------------\nDROP TABLE IF EXISTS `m_config`;\nCREATE TABLE `m_config`  (\n  `id` bigint(0) NOT NULL AUTO_INCREMENT,\n  `config_id` bigint(0) NOT NULL COMMENT '参数ID',\n  `config_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '参数名称',\n  `config_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '参数键名',\n  `config_value` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '参数键值',\n  `description` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '说明',\n  `status` tinyint(0) NULL DEFAULT 1 COMMENT '状态  0禁用 1正常',\n  `create_id` bigint(0) NULL DEFAULT 0 COMMENT '创建人ID',\n  `create_time` datetime(6) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_id` bigint(0) NULL DEFAULT 0 COMMENT '更新人ID',\n  `update_time` datetime(6) NULL DEFAULT NULL COMMENT '更新时间',\n  `deleted` bit(1) NULL DEFAULT b'0' COMMENT '逻辑删除  0未删除  1已删除',\n  `version` int(0) NULL DEFAULT 0 COMMENT '版本号',\n  PRIMARY KEY (`id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '参数配置表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of m_config\n-- ----------------------------\nINSERT INTO `m_config` VALUES (1, 1833741434936000512, '系统参数-多租户开关', 'system.config.tenant', 'true', 'true开启多租户，false关闭多租户', 1, 0, '2024-09-11 13:36:42.050638', 0, '2024-10-07 18:42:57.798908', b'0', 7);\nINSERT INTO `m_config` VALUES (3, 1833773478936207360, '系统参数-用户头像大小限制(kb)', 'system.config.user.avatar.size', '102400', '用户头像图片的大小，单位是kb', 1, 0, '2024-09-11 15:44:01.932583', 0, '2024-10-07 18:45:32.477747', b'0', 2);\nINSERT INTO `m_config` VALUES (4, 1833773744087523328, '系统参数-登录验证码是否开启', 'system.config.captcha.enable', 'true', '系统登录时的验证码，true打开，false关闭', 1, 0, '2024-09-11 15:45:05.149415', 0, '2024-10-21 21:36:23.000000', b'0', 5);\n\n-- ----------------------------\n-- Table structure for m_dept\n-- ----------------------------\nDROP TABLE IF EXISTS `m_dept`;\nCREATE TABLE `m_dept`  (\n  `id` bigint(0) NOT NULL AUTO_INCREMENT,\n  `dept_id` bigint(0) NOT NULL COMMENT '部门id',\n  `parent_dept_id` bigint(0) NULL DEFAULT 0 COMMENT '父部门id',\n  `ancestors` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '祖级列表',\n  `dept_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '部门名称',\n  `dept_leader` bigint(0) NULL DEFAULT NULL COMMENT '部门负责人',\n  `dept_sort` int(0) NULL DEFAULT 0 COMMENT '显示顺序',\n  `phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '联系电话',\n  `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '邮箱',\n  `status` tinyint(0) NULL DEFAULT 1 COMMENT '状态  0禁用 1正常',\n  `tenant_id` bigint(0) NULL DEFAULT NULL COMMENT '租户编号',\n  `create_id` bigint(0) NULL DEFAULT 0 COMMENT '创建人ID',\n  `create_time` datetime(6) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_id` bigint(0) NULL DEFAULT 0 COMMENT '更新人ID',\n  `update_time` datetime(6) NULL DEFAULT NULL COMMENT '更新时间',\n  `deleted` bit(1) NULL DEFAULT b'0' COMMENT '逻辑删除  0未删除  1已删除',\n  `version` int(0) NULL DEFAULT 0 COMMENT '版本号',\n  PRIMARY KEY (`id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 30 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '部门表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of m_dept\n-- ----------------------------\nINSERT INTO `m_dept` VALUES (5, 1677964029214371840, 0, NULL, '极简多租户', 0, 0, '10086', '123456@qq.com', 1, 0, 0, '2023-07-09 16:52:54.285322', 0, '2024-09-12 13:42:59.939494', b'0', 2);\nINSERT INTO `m_dept` VALUES (6, 1677964231673425920, 1677964029214371840, '1677964029214371840', '北京总部', 0, 0, '10010', '123456@qq.com', 1, 0, 0, '2023-07-09 16:53:42.555301', 0, '2024-09-12 13:55:20.471072', b'0', 2);\nINSERT INTO `m_dept` VALUES (7, 1677964435873116160, 1677964231673425920, '1677964029214371840,1677964231673425920', '基础架构部', 0, 20, '123456', '12121212', 1, 0, 0, '2023-07-09 16:54:31.240378', 0, '2024-09-12 13:48:55.319535', b'0', 3);\nINSERT INTO `m_dept` VALUES (8, 1677964531410972672, 1677964231673425920, '1677964029214371840,1677964231673425920', '产品设计部', 0, 10, '123456', '233223', 1, 0, 0, '2023-07-09 16:54:54.018152', 0, '2024-09-12 13:48:49.612112', b'0', 2);\nINSERT INTO `m_dept` VALUES (9, 1677964682431082496, 1677964231673425920, '1677964029214371840,1677964231673425920', '人力资源部', 0, 0, '456789', '4565346', 1, 0, 0, '2023-07-09 16:55:30.024647', 0, '2024-09-12 13:48:16.133344', b'0', 4);\nINSERT INTO `m_dept` VALUES (10, 1686015040692744192, 1677964231673425920, '1677964029214371840,1677964231673425920', '市场营销部', 0, 30, '123456', '123456@qq.com', 1, 0, 0, '2023-07-31 22:04:45.032834', 0, '2024-09-12 13:48:59.471250', b'0', 2);\nINSERT INTO `m_dept` VALUES (11, 1686019822887170048, 1677964231673425920, '1677964029214371840,1677964231673425920', '销售部', 0, 5, '1234567', '123456@qq.com', 1, 0, 0, '2023-07-31 22:23:45.167050', 0, '2024-09-12 13:48:43.809374', b'0', 3);\nINSERT INTO `m_dept` VALUES (12, 1686021322711560192, 1677964231673425920, '1677964029214371840,1677964231673425920', '财务部', 0, 40, '123456', '123456@qq.com', 1, 0, 0, '2023-07-31 22:29:42.752069', 0, '2024-09-12 13:49:04.612000', b'0', 2);\nINSERT INTO `m_dept` VALUES (13, 1686021452114227200, 1677964231673425920, '1677964029214371840,1677964231673425920', '客户服务部', 0, 50, '123456', '123456@qq.com', 1, 0, 0, '2023-07-31 22:30:13.605403', 0, '2024-09-12 13:49:08.625494', b'0', 2);\nINSERT INTO `m_dept` VALUES (14, 1686021621496999936, 1677964231673425920, '1677964029214371840,1677964231673425920', '运营部', 0, 60, '123456', '123456@qq.com', 1, 0, 0, '2023-07-31 22:30:53.989690', 0, '2024-09-12 13:49:16.259154', b'0', 2);\nINSERT INTO `m_dept` VALUES (15, 1686021776396840960, 1677964029214371840, '1677964029214371840', '河北分部', 0, 10, '123456', '123456@qq.com', 1, 0, 0, '2023-07-31 22:31:30.920687', 0, '2024-09-12 13:49:24.009569', b'0', 1);\nINSERT INTO `m_dept` VALUES (16, 1686349179535036416, 1686021776396840960, '1677964029214371840,1686021776396840960', '销售部', 0, 10, '123456', '123456@qq.com', 1, 0, 0, '2023-08-01 20:12:29.912108', 0, '2024-09-12 13:49:36.360331', b'0', 1);\nINSERT INTO `m_dept` VALUES (17, 1686349332929122304, 1686021776396840960, '1677964029214371840,1686021776396840960', '财务部', 0, 20, '123456', '123456@qq.com', 1, 0, 0, '2023-08-01 20:13:06.483608', 0, '2024-09-12 13:49:42.360847', b'0', 1);\nINSERT INTO `m_dept` VALUES (18, 1688102484086906880, 1686021776396840960, '1677964029214371840,1686021776396840960', '运营部', 0, 30, '13888888888', '13888888888@phone.com', 1, 0, 0, '2023-08-06 16:19:30.292609', 0, '2024-09-12 13:49:46.840120', b'0', 1);\nINSERT INTO `m_dept` VALUES (24, 1834773502369406976, 0, NULL, '东东胡辣汤', 1834763884364705792, 0, '11111', '11111', 1, 1834763883194494976, 1834763884364705792, '2024-09-14 09:57:46.111124', 1834763884364705792, '2024-09-14 10:01:30.581557', b'0', 1);\nINSERT INTO `m_dept` VALUES (25, 1834774513448329216, 1834773502369406976, '1834773502369406976', '东东胡辣汤一部', 1834763884364705792, 0, '111', '111', 1, 1834763883194494976, 1834763884364705792, '2024-09-14 10:01:48.475618', 1834763884364705792, '2024-09-14 10:01:48.475618', b'0', 0);\nINSERT INTO `m_dept` VALUES (26, 1834774974033240064, 1834773502369406976, '1834773502369406976', '东东胡辣汤二部', 1834763884364705792, 0, '222', '222', 1, 1834763883194494976, 1834763884364705792, '2024-09-14 10:03:37.111552', 1834763884364705792, '2024-09-14 10:03:37.111552', b'0', 0);\nINSERT INTO `m_dept` VALUES (27, 1836060155556687872, 0, NULL, '测试租户111', 1836055978990497792, 100, '1111111', '1111111', 1, 1836055976968843264, 1836055978990497792, '2024-09-17 23:10:28.128713', 1836055978990497792, '2024-09-17 23:10:28.128713', b'0', 0);\nINSERT INTO `m_dept` VALUES (28, 1836060210875363328, 1836060155556687872, '1836060155556687872', '一部', 1836059921829097472, 11, '11', '11', 1, 1836055976968843264, 1836055978990497792, '2024-09-17 23:10:41.436305', 1836055978990497792, '2024-09-17 23:10:41.436305', b'0', 0);\nINSERT INTO `m_dept` VALUES (29, 1836060257859956736, 1836060155556687872, '1836060155556687872', '二部', 1836060007078326272, 22, '22', '22', 1, 1836055976968843264, 1836055978990497792, '2024-09-17 23:10:52.640320', 1836055978990497792, '2024-09-17 23:10:52.640320', b'0', 0);\n\n-- ----------------------------\n-- Table structure for m_dict\n-- ----------------------------\nDROP TABLE IF EXISTS `m_dict`;\nCREATE TABLE `m_dict`  (\n  `id` bigint(0) NOT NULL AUTO_INCREMENT,\n  `dict_id` bigint(0) NULL DEFAULT NULL COMMENT '字典ID',\n  `dict_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '字典类型',\n  `dict_key` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '字典key',\n  `dict_value` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '字典value',\n  `dict_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '字典名称',\n  `dict_desc` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '字典描述',\n  `dict_order` int(0) NULL DEFAULT NULL COMMENT '字典排序值',\n  `dict_class` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '字典样式，对应前端Tag组件的type',\n  `status` tinyint(0) NULL DEFAULT 1 COMMENT '状态  0禁用 1正常',\n  `create_id` bigint(0) NULL DEFAULT 0 COMMENT '创建人ID',\n  `create_time` datetime(6) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_id` bigint(0) NULL DEFAULT 0 COMMENT '更新人ID',\n  `update_time` datetime(6) NULL DEFAULT NULL COMMENT '更新时间',\n  `deleted` bit(1) NULL DEFAULT b'0' COMMENT '逻辑删除  0未删除  1已删除',\n  `version` int(0) NULL DEFAULT 0 COMMENT '版本号',\n  PRIMARY KEY (`id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 139 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '字典表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of m_dict\n-- ----------------------------\nINSERT INTO `m_dict` VALUES (63, 1666983160652914688, 'yes-no', 'true', '是', '是/否', '公共字典，是/否', 1, 'arcoblue', 1, 0, '2024-10-09 14:45:18.000000', 0, '2024-10-10 14:47:09.609727', b'0', 0);\nINSERT INTO `m_dict` VALUES (64, 1666983160652914689, 'yes-no', 'false', '否', '是/否', '公共字典，是/否', 2, 'red', 1, 0, '2024-10-09 14:45:18.000000', 0, '2024-10-10 14:47:09.732989', b'0', 0);\nINSERT INTO `m_dict` VALUES (66, 1667413525115789313, 'perm-type', 'M', '菜单', '权限类型', '权限管理中的权限类型', 5, 'arcoblue', 1, 0, '2024-10-09 14:45:18.000000', 0, '2023-06-28 10:58:51.929748', b'0', 0);\nINSERT INTO `m_dict` VALUES (67, 1667413525115789314, 'perm-type', 'B', '按钮', '权限类型', '权限管理中的权限类型', 10, 'magenta', 1, 0, '2024-10-09 14:45:18.000000', 0, '2023-06-28 10:58:51.929748', b'0', 0);\nINSERT INTO `m_dict` VALUES (68, 1669515928599478272, 'dict-class', 'default', '默认', '字典样式', '字典管理中的字典样式，对应Tag标签的样式', 0, 'default', 1, 0, '2023-06-16 09:23:10.142052', 0, '2023-06-17 11:25:04.092472', b'0', 0);\nINSERT INTO `m_dict` VALUES (69, 1669515928603672576, 'dict-class', 'orange', '橙', '字典样式', '字典管理中的字典样式，对应Tag标签的样式', 10, 'orange', 1, 0, '2023-06-16 09:23:10.142052', 0, '2023-06-17 11:25:04.092472', b'0', 0);\nINSERT INTO `m_dict` VALUES (70, 1669515928603672577, 'dict-class', 'gold', '金', '字典样式', '字典管理中的字典样式，对应Tag标签的样式', 20, 'gold', 1, 0, '2023-06-16 09:23:10.142052', 0, '2023-06-17 11:25:04.092472', b'0', 0);\nINSERT INTO `m_dict` VALUES (71, 1669515928603672578, 'dict-class', 'lime', '绿黄', '字典样式', '字典管理中的字典样式，对应Tag标签的样式', 40, 'lime', 1, 0, '2023-06-16 09:23:10.142052', 0, '2023-06-17 11:25:04.092472', b'0', 0);\nINSERT INTO `m_dict` VALUES (72, 1669515928603672579, 'dict-class', 'green', '绿', '字典样式', '字典管理中的字典样式，对应Tag标签的样式', 50, 'green', 1, 0, '2023-06-16 09:23:10.142052', 0, '2023-06-17 11:25:04.092472', b'0', 0);\nINSERT INTO `m_dict` VALUES (73, 1669515928603672580, 'dict-class', 'cyan', '青', '字典样式', '字典管理中的字典样式，对应Tag标签的样式', 60, 'cyan', 1, 0, '2023-06-16 09:23:10.142052', 0, '2023-06-17 11:25:04.092472', b'0', 0);\nINSERT INTO `m_dict` VALUES (74, 1669515928603672581, 'dict-class', 'blue', '蓝', '字典样式', '字典管理中的字典样式，对应Tag标签的样式', 70, 'blue', 1, 0, '2023-06-16 09:23:10.142052', 0, '2023-06-17 11:25:04.092472', b'0', 0);\nINSERT INTO `m_dict` VALUES (75, 1669515928603672582, 'dict-class', 'arcoblue', '湖蓝', '字典样式', '字典管理中的字典样式，对应Tag标签的样式', 80, 'arcoblue', 1, 0, '2023-06-16 09:23:10.142052', 0, '2023-06-17 11:25:04.092472', b'0', 0);\nINSERT INTO `m_dict` VALUES (76, 1669515928603672583, 'dict-class', 'purple', '紫', '字典样式', '字典管理中的字典样式，对应Tag标签的样式', 90, 'purple', 1, 0, '2023-06-16 09:23:10.142052', 0, '2023-06-17 11:25:04.092472', b'0', 0);\nINSERT INTO `m_dict` VALUES (77, 1669515928603672584, 'dict-class', 'pinkpurple', '紫粉', '字典样式', '字典管理中的字典样式，对应Tag标签的样式', 100, 'pinkpurple', 1, 0, '2023-06-16 09:23:10.142052', 0, '2023-06-17 11:25:04.092472', b'0', 0);\nINSERT INTO `m_dict` VALUES (78, 1669515928603672585, 'dict-class', 'magenta', '洋红', '字典样式', '字典管理中的字典样式，对应Tag标签的样式', 110, 'magenta', 1, 0, '2023-06-16 09:23:10.142052', 0, '2023-06-17 11:25:04.092472', b'0', 0);\nINSERT INTO `m_dict` VALUES (79, 1669515928603672586, 'dict-class', 'gray', '灰', '字典样式', '字典管理中的字典样式，对应Tag标签的样式', 120, 'gray', 1, 0, '2023-06-16 09:23:10.142052', 0, '2023-06-17 11:25:04.092472', b'0', 0);\nINSERT INTO `m_dict` VALUES (80, 1669518820391731200, 'dict-class', 'red', '红', '字典样式', '字典管理中的字典样式，对应Tag标签的样式', 30, 'red', 1, 0, '2024-10-09 14:45:18.000000', 0, '2023-06-17 11:25:04.092472', b'0', 0);\nINSERT INTO `m_dict` VALUES (98, 1677961643871744000, 'user-sex', '1', '男', '用户性别', '用户管理中的用户性别', 1, 'arcoblue', 1, 0, '2023-07-09 16:43:25.575566', 0, '2023-07-09 16:43:25.575566', b'0', 0);\nINSERT INTO `m_dict` VALUES (99, 1677961643871744001, 'user-sex', '2', '女', '用户性别', '用户管理中的用户性别', 2, 'orange', 1, 0, '2023-07-09 16:43:25.575566', 0, '2023-07-09 16:43:25.575566', b'0', 0);\nINSERT INTO `m_dict` VALUES (104, 1681499349985316864, 'notice-type', '1', '公告', '系统公告类型', '系统公告中的公告类型', 0, 'arcoblue', 1, 0, '2023-07-19 11:01:00.440762', 0, '2023-07-23 22:51:26.805770', b'0', 0);\nINSERT INTO `m_dict` VALUES (105, 1683127629625241600, 'notice-type', '2', '新闻', '系统公告类型', '系统公告中的公告类型', 5, 'orange', 1, 0, '2024-10-09 14:45:18.000000', 0, '2023-07-23 22:51:26.805770', b'0', 0);\nINSERT INTO `m_dict` VALUES (108, 1683308098322038784, 'file-source', '1', '公告封面', '文件来源', '标识文件的用途，配合\\\"文件来源-存储位置\\\"一起使用', 1, 'orange', 1, 0, '2024-10-14 10:48:15.649000', 0, '2024-10-15 10:21:45.183768', b'0', 0);\nINSERT INTO `m_dict` VALUES (109, 1683308098322038785, 'file-source', '2', '公告内容', '文件来源', '标识文件的用途，配合\\\"文件来源-存储位置\\\"一起使用', 2, 'lime', 1, 0, '2024-10-14 10:48:19.649000', 0, '2024-10-15 10:21:45.306306', b'0', 0);\nINSERT INTO `m_dict` VALUES (126, 1843908761895776256, 'common-number-status', '0', '禁用', '通用状态-数字', '0禁用 1正常', 1, 'red', 1, 0, '2024-10-09 14:45:18.000000', 0, '2024-10-10 16:59:41.651688', b'0', 0);\nINSERT INTO `m_dict` VALUES (127, 1843908761895776257, 'common-number-status', '1', '正常', '通用状态-数字', '0禁用 1正常', 2, 'green', 1, 0, '2024-10-11 14:45:26.000000', 0, '2024-10-10 16:59:41.770223', b'0', 0);\nINSERT INTO `m_dict` VALUES (128, 1843932948186218496, 'storage-type', 'local', '本地', '存储类型', '存储平台，如阿里云oss、腾讯云oss、七牛云oss等', 1, 'orange', 1, 0, '2024-10-09 14:45:18.000000', 0, '2025-02-18 20:29:33.000000', b'0', 4);\nINSERT INTO `m_dict` VALUES (129, 1843932948186218497, 'storage-type', 'minio', 'MinIO', '存储类型', '存储平台，如阿里云oss、腾讯云oss、七牛云oss等', 2, 'blue', 1, 0, '2024-10-09 14:45:18.000000', 0, '2025-02-18 20:29:33.000000', b'0', 4);\nINSERT INTO `m_dict` VALUES (130, 1845016876603019264, 'file-source-path', '1', 'notice/cover/', '文件来源-存储位置', '文件来源对应的存储位置，配合\\\"文件来源\\\"一起使用', 1, NULL, 1, 0, '2024-10-15 10:13:34.000000', 0, '2025-03-12 19:11:03.000000', b'0', 4);\nINSERT INTO `m_dict` VALUES (131, 1845016876603019265, 'file-source-path', '2', 'notice/content/', '文件来源-存储位置', '文件来源对应的存储位置，配合\\\"文件来源\\\"一起使用', 2, NULL, 1, 0, '2024-10-15 10:13:38.000000', 0, '2025-03-12 19:11:03.000000', b'0', 4);\nINSERT INTO `m_dict` VALUES (133, 1890393477224509440, 'tenant-data-isolation', 'column', '字段隔离', '租户数据隔离方式', '多租户隔离方式：字段隔离 或 数据库隔离', 1, 'green', 1, 0, '2025-02-14 21:31:41.000000', 0, '2025-02-14 21:31:41.000000', b'0', 0);\nINSERT INTO `m_dict` VALUES (134, 1890393477329367040, 'tenant-data-isolation', 'db', '数据库隔离', '租户数据隔离方式', '多租户隔离方式：字段隔离 或 数据库隔离', 2, 'arcoblue', 1, 0, '2025-02-14 21:31:41.000000', 0, '2025-02-14 21:31:41.000000', b'0', 0);\nINSERT INTO `m_dict` VALUES (136, 1891825581577605120, 'storage-type', 'qiniu', '七牛云', '存储类型', '存储平台，如阿里云oss、腾讯云oss、七牛云oss等', 3, 'gold', 1, 0, '2025-02-18 20:22:21.000000', 0, '2025-02-18 20:29:34.000000', b'0', 2);\nINSERT INTO `m_dict` VALUES (137, 1899098996038688768, 'file-type', 'image', '图片', '文件类型', '文件类型，与file表中file_type字段对应', 0, NULL, 1, 0, '2025-03-10 22:04:18.000000', 0, '2025-03-10 22:04:18.000000', b'0', 0);\nINSERT INTO `m_dict` VALUES (138, 1899098996160323584, 'file-type', 'video', '视频', '文件类型', '文件类型，与file表中file_type字段对应', 0, NULL, 1, 0, '2025-03-10 22:04:18.000000', 0, '2025-03-10 22:04:18.000000', b'0', 0);\nINSERT INTO `m_dict` VALUES (139, 1899780153331810304, 'file-source-path', '-1', 'common/', '文件来源-存储位置', '文件来源对应的存储位置，配合\\\"文件来源\\\"一起使用', 0, NULL, 1, 0, '2025-03-12 19:11:03.000000', 0, '2025-03-12 19:11:03.000000', b'0', 0);\n\n-- ----------------------------\n-- Table structure for m_file\n-- ----------------------------\nDROP TABLE IF EXISTS `m_file`;\nCREATE TABLE `m_file`  (\n  `id` bigint(0) NOT NULL AUTO_INCREMENT,\n  `file_id` bigint(0) NOT NULL COMMENT '文件ID',\n  `file_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '原文件名',\n  `new_file_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '现文件名',\n  `file_size` bigint(0) NOT NULL DEFAULT 0 COMMENT '文件大小',\n  `file_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '文件类型',\n  `file_base_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '文件基础路径',\n  `file_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '文件相对路径',\n  `file_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '文件url',\n  `file_source` int(0) NULL DEFAULT NULL COMMENT '文件来源',\n  `file_th_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '文件缩略图url',\n  `storage_id` bigint(0) NULL DEFAULT NULL COMMENT '存储ID',\n  `file_th_filename` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '文件缩略图文件名',\n  `file_th_size` bigint(0) NULL DEFAULT NULL COMMENT '缩略图文件大小',\n  `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',\n  `tenant_id` bigint(0) NULL DEFAULT NULL COMMENT '租户ID',\n  `status` tinyint(0) NULL DEFAULT 0 COMMENT '状态  0未使用 1已使用，默认未使用，代码中控制修改为已使用，可以定期清理未使用的文件',\n  `create_id` bigint(0) NULL DEFAULT 0 COMMENT '创建人ID',\n  `create_time` datetime(6) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_id` bigint(0) NULL DEFAULT 0 COMMENT '更新人ID',\n  `update_time` datetime(6) NULL DEFAULT NULL COMMENT '更新时间',\n  `deleted` bit(1) NULL DEFAULT b'0' COMMENT '逻辑删除  0未删除  1已删除',\n  `version` int(0) NULL DEFAULT 0 COMMENT '版本号',\n  PRIMARY KEY (`id`) USING BTREE,\n  INDEX `idx_file_id`(`file_id`) USING BTREE COMMENT '文件ID索引',\n  INDEX `idx_file_url`(`file_url`) USING BTREE COMMENT '文件URL索引'\n) ENGINE = InnoDB AUTO_INCREMENT = 203 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '文件表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of m_file\n-- ----------------------------\nINSERT INTO `m_file` VALUES (204, 1899820597440864256, '微信图片_20241122215116.jpg', '67d1916e29479ffe018772d7.jpg', 28263, 'image/jpeg', 'D:/application/minimalist-saas/', 'D:/application/minimalist-saas/common/', 'http://localhost:9090/minimalist/files/common/67d1916e29479ffe018772d7.jpg', -1, 'http://localhost:9090/minimalist/files/common/thumbnail-67d1916e29479ffe018772d7.jpg', 1843939697907720192, 'thumbnail-67d1916e29479ffe018772d7.jpg', 5286, NULL, 0, 1, 0, '2025-03-12 21:51:41.000000', 0, '2025-03-12 21:51:41.000000', b'0', 0);\nINSERT INTO `m_file` VALUES (205, 1899821417918029824, '微信图片_20250301225402.png', '67d1923129479ffe018772d8.png', 303148, 'image/png', 'D:/application/minimalist-saas/', 'D:/application/minimalist-saas/common/', 'http://localhost:9090/minimalist/files/common/67d1923129479ffe018772d8.png', -1, 'http://localhost:9090/minimalist/files/common/thumbnail-67d1923129479ffe018772d8.png', 1843939697907720192, 'thumbnail-67d1923129479ffe018772d8.png', 58592, NULL, 0, 1, 0, '2025-03-12 21:54:57.000000', 0, '2025-03-12 21:54:57.000000', b'0', 0);\nINSERT INTO `m_file` VALUES (206, 1899821823532392448, '无标题.png', '67d1929229479ffe018772d9.png', 295216, 'image/png', 'D:/application/minimalist-saas/', 'D:/application/minimalist-saas/common/', 'http://localhost:9090/minimalist/files/common/67d1929229479ffe018772d9.png', -1, 'http://localhost:9090/minimalist/files/common/thumbnail-67d1929229479ffe018772d9.png', 1843939697907720192, 'thumbnail-67d1929229479ffe018772d9.png', 92429, NULL, 0, 1, 0, '2025-03-12 21:56:34.000000', 0, '2025-03-12 21:56:34.000000', b'0', 0);\n\n-- ----------------------------\n-- Table structure for m_notice\n-- ----------------------------\nDROP TABLE IF EXISTS `m_notice`;\nCREATE TABLE `m_notice`  (\n  `id` bigint(0) NOT NULL AUTO_INCREMENT,\n  `notice_id` bigint(0) NOT NULL COMMENT '公告ID',\n  `notice_title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '公告标题',\n  `notice_type` tinyint(0) NOT NULL COMMENT '公告类型（1公告）',\n  `notice_content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '公告内容',\n  `notice_pic_file_id` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '封面图文件ID，多个使用 , 分割',\n  `notice_top` bit(1) NULL DEFAULT b'0' COMMENT '是否置顶 0否 1是',\n  `notice_time_interval` datetime(6) NULL DEFAULT NULL COMMENT '延时发布的时间',\n  `notice_sort` int(0) NULL DEFAULT 0 COMMENT '排序',\n  `notice_out_chain` bit(1) NULL DEFAULT NULL COMMENT '是否外链 0否 1是',\n  `notice_link` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '外部跳转链接',\n  `publish_dept_id` bigint(0) NULL DEFAULT NULL COMMENT '发布部门',\n  `publish_author_id` bigint(0) NULL DEFAULT NULL COMMENT '发布人',\n  `publish_time` datetime(6) NULL DEFAULT NULL COMMENT '发布时间',\n  `status` tinyint(0) NULL DEFAULT 1 COMMENT '状态  0禁用 1正常',\n  `create_id` bigint(0) NULL DEFAULT 0 COMMENT '创建人ID',\n  `create_time` datetime(6) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_id` bigint(0) NULL DEFAULT 0 COMMENT '更新人ID',\n  `update_time` datetime(6) NULL DEFAULT NULL COMMENT '更新时间',\n  `deleted` bit(1) NULL DEFAULT b'0' COMMENT '逻辑删除  0未删除  1已删除',\n  `version` int(0) NULL DEFAULT 0 COMMENT '版本号',\n  PRIMARY KEY (`id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 17 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '通知公告表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of m_notice\n-- ----------------------------\nINSERT INTO `m_notice` VALUES (16, 1849729827784355840, '极简多租户管理系统1.0.0版本发布', 1, '&lt;p&gt;极简多租户管理系统是一个多租户管理系统，基于SpringBoot3+Vue3的前后端分离的后台开发脚手架，具备一些常用的基础功能。&lt;/p&gt;\\n&lt;p&gt;演示地址：&lt;a href=&quot;https://www.jjian.com.cn&quot; target=&quot;_blank&quot;&gt;https://www.jjian.com.cn&lt;/a&gt;&lt;/p&gt;\\n&lt;p&gt;Gitee：&lt;a href=&quot;https://gitee.com/marlife/minimalist-saas&quot; target=&quot;_blank&quot;&gt;https://gitee.com/marlife/minimalist-saas&lt;/a&gt;&lt;/p&gt;\\n&lt;p&gt;Github：&lt;a href=&quot;https://github.com/lmq2582609/minimalist-saas&quot; target=&quot;_blank&quot;&gt;https://github.com/lmq2582609/minimalist-saas&lt;/a&gt;&lt;/p&gt;\\n&lt;p&gt;管理员账号/密码： admin/111111&lt;/p&gt;\\n&lt;p&gt;租户账号/密码：dongdong/111111&lt;/p&gt;', '1851164126132932608', b'1', NULL, 1, b'0', NULL, 1677964029214371840, 0, '2024-10-25 16:28:51.919183', 1, 0, '2024-10-25 16:28:52.000000', 0, '2024-10-29 15:28:42.000000', b'0', 12);\n\n-- ----------------------------\n-- Table structure for m_perms\n-- ----------------------------\nDROP TABLE IF EXISTS `m_perms`;\nCREATE TABLE `m_perms`  (\n  `id` bigint(0) NOT NULL AUTO_INCREMENT,\n  `perm_id` bigint(0) NOT NULL COMMENT '权限ID',\n  `perm_code` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '权限标识',\n  `perm_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '权限名称',\n  `parent_perm_id` bigint(0) NULL DEFAULT 0 COMMENT '父权限ID',\n  `perm_sort` int(0) NULL DEFAULT 0 COMMENT '显示顺序',\n  `perm_path` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '路由地址',\n  `perm_icon` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '权限图标 菜单或目录时可传图标',\n  `perm_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'M' COMMENT '权限类型  M菜单 B按钮',\n  `component` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组件路径',\n  `external_link` bit(1) NULL DEFAULT b'0' COMMENT '是否为外部链接  0否 1是',\n  `visible` bit(1) NULL DEFAULT b'1' COMMENT '是否可见 0隐藏 1显示）',\n  `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',\n  `status` tinyint(0) NULL DEFAULT 1 COMMENT '状态  0禁用 1正常',\n  `create_id` bigint(0) NULL DEFAULT 0 COMMENT '创建人ID',\n  `create_time` datetime(6) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_id` bigint(0) NULL DEFAULT 0 COMMENT '更新人ID',\n  `update_time` datetime(6) NULL DEFAULT NULL COMMENT '更新时间',\n  `deleted` bit(1) NULL DEFAULT b'0' COMMENT '逻辑删除  0未删除  1已删除',\n  `version` int(0) NULL DEFAULT 0 COMMENT '版本号',\n  PRIMARY KEY (`id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 89 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '权限表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of m_perms\n-- ----------------------------\nINSERT INTO `m_perms` VALUES (6, 1669336412647133184, NULL, '系统管理', 0, 20, NULL, 'IconSettings', 'M', NULL, b'0', b'1', '', 1, 0, '2023-06-15 21:29:50.202052', 0, '2023-07-26 21:42:24.834876', b'0', 4);\nINSERT INTO `m_perms` VALUES (7, 1669338708936298496, NULL, '用户管理', 1669336412647133184, 10, '/basic/user/mgt', 'IconUser', 'M', '/basic/user/UserMgt.vue', b'0', b'1', '', 1, 0, '2023-06-15 21:38:57.680970', 0, '2023-07-31 21:29:03.510290', b'0', 6);\nINSERT INTO `m_perms` VALUES (8, 1669339147886989312, NULL, '菜单权限', 1669336412647133184, 30, '/basic/perm/mgt', 'IconNav', 'M', '/basic/perm/PermMgt.vue', b'0', b'1', '', 1, 0, '2023-06-15 21:40:42.334745', 0, '2024-09-13 13:59:01.298846', b'0', 8);\nINSERT INTO `m_perms` VALUES (9, 1669339375922909184, NULL, '字典管理', 1669336412647133184, 50, '/basic/dict/mgt', 'IconDice', 'M', '/basic/dict/DictMgt.vue', b'0', b'1', '', 1, 0, '2023-06-15 21:41:36.702313', 0, '2023-07-31 21:29:40.385520', b'0', 6);\nINSERT INTO `m_perms` VALUES (15, 1673167646340120576, NULL, '角色管理', 1669336412647133184, 20, '/basic/role/mgt', 'IconUserGroup', 'M', '/basic/role/RoleMgt.vue', b'0', b'1', '', 1, 0, '2023-06-26 11:13:47.496133', 0, '2023-07-31 21:29:13.988209', b'0', 6);\nINSERT INTO `m_perms` VALUES (16, 1673524365792530432, NULL, '部门管理', 1669336412647133184, 40, '/basic/dept/mgt', 'IconMindMapping', 'M', '/basic/dept/DeptMgt.vue', b'0', b'1', '', 1, 0, '2023-06-27 10:51:16.041741', 0, '2023-07-31 21:29:27.845462', b'0', 5);\nINSERT INTO `m_perms` VALUES (17, 1673524591861321728, NULL, '岗位管理', 1669336412647133184, 45, '/basic/post/mgt', 'IconBulb', 'M', '/basic/post/PostMgt.vue', b'0', b'1', '', 1, 0, '2023-06-27 10:52:09.940810', 0, '2023-07-31 21:29:33.111096', b'0', 5);\nINSERT INTO `m_perms` VALUES (19, 1673863803093557248, NULL, '开发文档', 0, 10, 'https://apebbs.cn/docs/minimalist-saas/1.0.0/introduction.html', 'IconBookmark', 'M', NULL, b'1', b'1', '', 1, 0, '2023-06-28 09:20:04.200055', 0, '2024-10-29 14:59:51.000000', b'0', 6);\nINSERT INTO `m_perms` VALUES (20, 1676499330464649216, NULL, '租户维护', 1669336412647133184, 60, NULL, 'IconCloud', 'M', NULL, b'0', b'1', '', 1, 0, '2023-07-05 15:52:42.891799', 0, '2023-07-26 21:53:05.189805', b'0', 4);\nINSERT INTO `m_perms` VALUES (21, 1676499559247155200, NULL, '套餐管理', 1676499330464649216, 10, '/basic/tenantPackage/mgt', 'IconCommon', 'M', '/basic/tenantPackage/TenantPackageMgt.vue', b'0', b'1', '', 1, 0, '2023-07-05 15:53:37.437053', 0, '2023-07-31 21:29:59.129666', b'0', 5);\nINSERT INTO `m_perms` VALUES (22, 1676501907755405312, NULL, '租户管理', 1676499330464649216, 20, '/basic/tenant/mgt', 'IconRelation', 'M', '/basic/tenant/TenantMgt.vue', b'0', b'1', '', 1, 0, '2023-07-05 16:02:57.365341', 0, '2023-07-31 21:30:04.168813', b'0', 4);\nINSERT INTO `m_perms` VALUES (28, 1680749851857862656, '', '接口文档', 1669336412647133184, 100, '/basic/swagger', 'IconBook', 'M', '/basic/system/Swagger.vue', b'0', b'1', '将swagger页面嵌入到系统', 1, 0, '2023-07-17 09:22:46.162278', 0, '2024-10-17 15:44:19.846129', b'0', 4);\nINSERT INTO `m_perms` VALUES (29, 1681273378598850560, NULL, '系统公告', 1669336412647133184, 60, '/basic/notice/mgt', 'IconSubscribe', 'M', '/basic/notice/NoticeMgt.vue', b'0', b'1', '', 1, 0, '2023-07-18 20:03:04.663282', 0, '2023-07-31 21:30:08.721061', b'0', 2);\nINSERT INTO `m_perms` VALUES (30, 1683285343149096960, NULL, '文件管理', 1669336412647133184, 55, '/basic/file/mgt', 'IconFile', 'M', '/basic/file/FileMgt.vue', b'0', b'1', '', 1, 0, '2023-07-24 09:17:54.393048', 0, '2023-07-31 21:29:48.832685', b'0', 3);\nINSERT INTO `m_perms` VALUES (31, 1685985572129398784, 'basic:user:add', '添加用户', 1669338708936298496, 10, NULL, NULL, 'B', NULL, b'0', b'1', '', 1, 0, '2023-07-31 20:07:39.152606', 0, '2023-07-31 20:07:39.152606', b'0', 0);\nINSERT INTO `m_perms` VALUES (32, 1685985875515990016, 'basic:user:delete', '删除用户', 1669338708936298496, 20, NULL, NULL, 'B', NULL, b'0', b'1', '', 1, 0, '2023-07-31 20:08:51.482954', 0, '2023-07-31 20:08:51.482954', b'0', 0);\nINSERT INTO `m_perms` VALUES (33, 1685985977349496832, 'basic:user:update', '修改用户', 1669338708936298496, 30, NULL, NULL, 'B', NULL, b'0', b'1', '', 1, 0, '2023-07-31 20:09:15.762162', 0, '2023-07-31 20:09:15.762162', b'0', 0);\nINSERT INTO `m_perms` VALUES (34, 1685986276453703680, 'basic:user:get', '查询用户', 1669338708936298496, 40, NULL, NULL, 'B', NULL, b'0', b'1', '', 1, 0, '2023-07-31 20:10:27.074176', 0, '2023-07-31 20:10:27.074176', b'0', 0);\nINSERT INTO `m_perms` VALUES (35, 1685986566443687936, 'basic:role:add', '添加角色', 1673167646340120576, 10, NULL, NULL, 'B', NULL, b'0', b'1', '', 1, 0, '2023-07-31 20:11:36.213812', 0, '2023-07-31 20:11:36.213812', b'0', 0);\nINSERT INTO `m_perms` VALUES (36, 1685986702834065408, 'basic:role:delete', '删除角色', 1673167646340120576, 20, NULL, NULL, 'B', NULL, b'0', b'1', '', 1, 0, '2023-07-31 20:12:08.733552', 0, '2023-07-31 20:12:08.733552', b'0', 0);\nINSERT INTO `m_perms` VALUES (37, 1685986806387236864, 'basic:role:update', '修改角色', 1673167646340120576, 30, NULL, NULL, 'B', NULL, b'0', b'1', '', 1, 0, '2023-07-31 20:12:33.419706', 0, '2023-07-31 20:12:33.419706', b'0', 0);\nINSERT INTO `m_perms` VALUES (38, 1685986887102423040, 'basic:role:get', '查询角色', 1673167646340120576, 40, NULL, NULL, 'B', NULL, b'0', b'1', '', 1, 0, '2023-07-31 20:12:52.664113', 0, '2023-07-31 20:12:52.664113', b'0', 0);\nINSERT INTO `m_perms` VALUES (39, 1685987612687654912, 'basic:perm:add', '添加权限', 1669339147886989312, 10, NULL, NULL, 'B', NULL, b'0', b'1', '', 1, 0, '2023-07-31 20:15:45.657682', 0, '2023-07-31 20:15:45.657682', b'0', 0);\nINSERT INTO `m_perms` VALUES (40, 1685987708229705728, 'basic:perm:delete', '删除权限', 1669339147886989312, 20, NULL, NULL, 'B', NULL, b'0', b'1', '', 1, 0, '2023-07-31 20:16:08.439529', 0, '2023-07-31 20:16:08.439529', b'0', 0);\nINSERT INTO `m_perms` VALUES (41, 1685988419789185024, 'basic:perm:update', '修改权限', 1669339147886989312, 30, NULL, NULL, 'B', NULL, b'0', b'1', '', 1, 0, '2023-07-31 20:18:58.085960', 0, '2023-08-06 10:29:38.265543', b'0', 1);\nINSERT INTO `m_perms` VALUES (42, 1685988623951126528, 'basic:perm:get', '查询权限', 1669339147886989312, 40, NULL, NULL, 'B', NULL, b'0', b'1', '', 1, 0, '2023-07-31 20:19:46.761240', 0, '2023-08-06 10:29:55.392998', b'0', 1);\nINSERT INTO `m_perms` VALUES (43, 1685996318275985408, 'basic:dept:add', '添加部门', 1673524365792530432, 10, NULL, NULL, 'B', NULL, b'0', b'1', '', 1, 0, '2023-07-31 20:50:21.231736', 0, '2023-07-31 20:50:21.231736', b'0', 0);\nINSERT INTO `m_perms` VALUES (44, 1685996426530971648, 'basic:dept:delete', '删除部门', 1673524365792530432, 20, NULL, NULL, 'B', NULL, b'0', b'1', '', 1, 0, '2023-07-31 20:50:47.041079', 0, '2023-07-31 20:50:47.041079', b'0', 0);\nINSERT INTO `m_perms` VALUES (45, 1685996547448561664, 'basic:dept:update', '修改部门', 1673524365792530432, 30, NULL, NULL, 'B', NULL, b'0', b'1', '', 1, 0, '2023-07-31 20:51:15.869518', 0, '2023-07-31 20:51:15.869518', b'0', 0);\nINSERT INTO `m_perms` VALUES (46, 1685996623386435584, 'basic:dept:get', '查询部门', 1673524365792530432, 40, NULL, NULL, 'B', NULL, b'0', b'1', '', 1, 0, '2023-07-31 20:51:33.974273', 0, '2023-07-31 20:51:33.974273', b'0', 0);\nINSERT INTO `m_perms` VALUES (47, 1685996875623489536, 'basic:post:add', '添加岗位', 1673524591861321728, 10, NULL, NULL, 'B', NULL, b'0', b'1', '', 1, 0, '2023-07-31 20:52:34.113986', 0, '2023-07-31 20:52:34.113986', b'0', 0);\nINSERT INTO `m_perms` VALUES (48, 1685997536914235392, 'basic:post:delete', '删除岗位', 1673524591861321728, 20, NULL, NULL, 'B', NULL, b'0', b'1', '', 1, 0, '2023-07-31 20:55:11.777212', 0, '2023-07-31 20:55:11.777212', b'0', 0);\nINSERT INTO `m_perms` VALUES (49, 1685997640341577728, 'basic:post:update', '修改岗位', 1673524591861321728, 30, NULL, NULL, 'B', NULL, b'0', b'1', '', 1, 0, '2023-07-31 20:55:36.435155', 0, '2023-07-31 20:55:36.435155', b'0', 0);\nINSERT INTO `m_perms` VALUES (50, 1685997732956004352, 'basic:post:get', '查询岗位', 1673524591861321728, 40, NULL, NULL, 'B', NULL, b'0', b'1', '', 1, 0, '2023-07-31 20:55:58.516359', 0, '2023-07-31 20:55:58.516359', b'0', 0);\nINSERT INTO `m_perms` VALUES (51, 1685998041879076864, 'basic:dict:add', '添加字典', 1669339375922909184, 10, NULL, NULL, 'B', NULL, b'0', b'1', '', 1, 0, '2023-07-31 20:57:12.169238', 0, '2023-07-31 20:57:12.169238', b'0', 0);\nINSERT INTO `m_perms` VALUES (52, 1685998171407572992, 'basic:dict:delete', '删除字典', 1669339375922909184, 20, NULL, NULL, 'B', NULL, b'0', b'1', '', 1, 0, '2023-07-31 20:57:43.052637', 0, '2023-07-31 20:57:43.052637', b'0', 0);\nINSERT INTO `m_perms` VALUES (53, 1685998244715618304, 'basic:dict:update', '修改字典', 1669339375922909184, 30, NULL, NULL, 'B', NULL, b'0', b'1', '', 1, 0, '2023-07-31 20:58:00.530266', 0, '2023-07-31 20:58:00.530266', b'0', 0);\nINSERT INTO `m_perms` VALUES (54, 1685998340425441280, 'basic:dict:get', '查询字典', 1669339375922909184, 40, NULL, NULL, 'B', NULL, b'0', b'1', '', 1, 0, '2023-07-31 20:58:23.349675', 0, '2023-07-31 20:58:23.349675', b'0', 0);\nINSERT INTO `m_perms` VALUES (55, 1686001693859569664, 'basic:notice:add', '添加公告', 1681273378598850560, 10, NULL, NULL, 'B', NULL, b'0', b'1', '', 1, 0, '2023-07-31 21:11:42.870516', 0, '2023-07-31 21:11:42.870516', b'0', 0);\nINSERT INTO `m_perms` VALUES (56, 1686001770980237312, 'basic:notice:delete', '删除公告', 1681273378598850560, 20, NULL, NULL, 'B', NULL, b'0', b'1', '', 1, 0, '2023-07-31 21:12:01.257352', 0, '2023-07-31 21:12:01.257352', b'0', 0);\nINSERT INTO `m_perms` VALUES (57, 1686002193703165952, 'basic:notice:update', '修改公告', 1681273378598850560, 30, NULL, NULL, 'B', NULL, b'0', b'1', '', 1, 0, '2023-07-31 21:13:42.042772', 0, '2023-07-31 21:13:42.042772', b'0', 0);\nINSERT INTO `m_perms` VALUES (58, 1686002291338174464, 'basic:notice:get', '查询公告', 1681273378598850560, 40, NULL, NULL, 'B', NULL, b'0', b'1', '', 1, 0, '2023-07-31 21:14:05.320984', 0, '2023-07-31 21:14:05.320984', b'0', 0);\nINSERT INTO `m_perms` VALUES (59, 1686002657173757952, 'basic:tenantPackage:add', '添加套餐', 1676499559247155200, 10, NULL, NULL, 'B', NULL, b'0', b'1', '', 1, 0, '2023-07-31 21:15:32.542913', 0, '2023-07-31 21:15:32.542913', b'0', 0);\nINSERT INTO `m_perms` VALUES (60, 1686002750660599808, 'basic:tenantPackage:delete', '删除套餐', 1676499559247155200, 20, NULL, NULL, 'B', NULL, b'0', b'1', '', 1, 0, '2023-07-31 21:15:54.831477', 0, '2023-07-31 21:15:54.831477', b'0', 0);\nINSERT INTO `m_perms` VALUES (61, 1686002863185387520, 'basic:tenantPackage:update', '修改套餐', 1676499559247155200, 30, NULL, NULL, 'B', NULL, b'0', b'1', '', 1, 0, '2023-07-31 21:16:21.658749', 0, '2023-07-31 21:16:21.658749', b'0', 0);\nINSERT INTO `m_perms` VALUES (62, 1686002975089418240, 'basic:tenantPackage:get', '查询套餐', 1676499559247155200, 40, NULL, NULL, 'B', NULL, b'0', b'1', '', 1, 0, '2023-07-31 21:16:48.338212', 0, '2023-07-31 21:16:48.338212', b'0', 0);\nINSERT INTO `m_perms` VALUES (63, 1686004858881368064, 'basic:tenant:add', '添加租户', 1676501907755405312, 10, NULL, NULL, 'B', NULL, b'0', b'1', '', 1, 0, '2023-07-31 21:24:17.469065', 0, '2023-07-31 21:24:17.469065', b'0', 0);\nINSERT INTO `m_perms` VALUES (64, 1686005292303966208, 'basic:tenant:delete', '删除租户', 1676501907755405312, 20, NULL, NULL, 'B', NULL, b'0', b'1', '', 1, 0, '2023-07-31 21:26:00.806007', 0, '2023-07-31 21:26:00.806007', b'0', 0);\nINSERT INTO `m_perms` VALUES (65, 1686005483111243776, 'basic:tenant:update', '修改租户', 1676501907755405312, 30, NULL, NULL, 'B', NULL, b'0', b'1', '', 1, 0, '2023-07-31 21:26:46.297456', 0, '2023-07-31 21:26:46.297456', b'0', 0);\nINSERT INTO `m_perms` VALUES (66, 1686005567790047232, 'basic:tenant:get', '查询租户', 1676501907755405312, 40, NULL, NULL, 'B', NULL, b'0', b'1', '', 1, 0, '2023-07-31 21:27:06.487964', 0, '2023-07-31 21:27:06.487964', b'0', 0);\nINSERT INTO `m_perms` VALUES (73, 1833687152849211392, '', '参数配置', 1669336412647133184, 70, '/basic/config/mgt', 'IconSettings', 'M', '/basic/config/ConfigMgt.vue', b'0', b'1', NULL, 1, 0, '2024-09-11 10:01:00.193232', 0, '2024-09-11 10:02:35.271112', b'0', 5);\nINSERT INTO `m_perms` VALUES (74, 1833688664656728064, 'basic:config:add', '添加参数', 1833687152849211392, 10, NULL, NULL, 'B', NULL, b'0', b'1', NULL, 1, 0, '2024-09-11 10:07:00.633835', 0, '2024-09-11 10:07:00.633835', b'0', 0);\nINSERT INTO `m_perms` VALUES (75, 1833688838682595328, 'basic:config:delete', '删除参数', 1833687152849211392, 20, NULL, NULL, 'B', NULL, b'0', b'1', NULL, 1, 0, '2024-09-11 10:07:42.123295', 0, '2024-09-11 10:07:42.123295', b'0', 0);\nINSERT INTO `m_perms` VALUES (76, 1833688910472302592, 'basic:config:update', '修改参数', 1833687152849211392, 30, NULL, NULL, 'B', NULL, b'0', b'1', NULL, 1, 0, '2024-09-11 10:07:59.239329', 0, '2024-09-11 10:07:59.239329', b'0', 0);\nINSERT INTO `m_perms` VALUES (77, 1833688972682219520, 'basic:config:get', '查询参数', 1833687152849211392, 40, NULL, NULL, 'B', NULL, b'0', b'1', NULL, 1, 0, '2024-09-11 10:08:14.070779', 0, '2024-09-11 10:08:14.070779', b'0', 0);\nINSERT INTO `m_perms` VALUES (78, 1843898617245937664, NULL, '存储管理', 1669336412647133184, 58, '/basic/storage/mgt', 'IconStorage', 'M', '/basic/storage/StorageMgt.vue', b'0', b'1', NULL, 1, 0, '2024-10-09 14:17:43.024177', 0, '2024-10-09 14:22:46.976870', b'0', 2);\nINSERT INTO `m_perms` VALUES (79, 1843900739123359744, 'basic:storage:add', '添加存储', 1843898617245937664, 10, NULL, NULL, 'B', NULL, b'0', b'1', NULL, 1, 0, '2024-10-09 14:26:08.915829', 0, '2024-10-09 14:26:08.915829', b'0', 0);\nINSERT INTO `m_perms` VALUES (80, 1843900820899704832, 'basic:storage:delete', '删除存储', 1843898617245937664, 20, NULL, NULL, 'B', NULL, b'0', b'1', NULL, 1, 0, '2024-10-09 14:26:28.413099', 0, '2024-10-09 14:26:28.413099', b'0', 0);\nINSERT INTO `m_perms` VALUES (81, 1843900873001349120, 'basic:storage:update', '修改存储', 1843898617245937664, 30, NULL, NULL, 'B', NULL, b'0', b'1', NULL, 1, 0, '2024-10-09 14:26:40.835720', 0, '2024-10-09 14:26:40.835720', b'0', 0);\nINSERT INTO `m_perms` VALUES (82, 1843900946787545088, 'basic:storage:get', '查询存储', 1843898617245937664, 40, NULL, NULL, 'B', NULL, b'0', b'1', NULL, 1, 0, '2024-10-09 14:26:58.428091', 0, '2024-10-09 14:27:06.999102', b'0', 1);\nINSERT INTO `m_perms` VALUES (84, 1846813160695615488, 'basic:file:upload', '文件上传', 1683285343149096960, 10, NULL, NULL, 'B', NULL, b'0', b'1', NULL, 1, 0, '2024-10-17 15:19:04.331491', 0, '2024-10-17 15:19:04.331491', b'0', 0);\nINSERT INTO `m_perms` VALUES (85, 1846813266043949056, 'basic:file:delete', '删除文件', 1683285343149096960, 20, NULL, NULL, 'B', NULL, b'0', b'1', NULL, 1, 0, '2024-10-17 15:19:29.445881', 0, '2024-10-17 15:19:29.445881', b'0', 0);\nINSERT INTO `m_perms` VALUES (86, 1846813437867806720, 'basic:file:download', '下载文件', 1683285343149096960, 30, NULL, NULL, 'B', NULL, b'0', b'1', NULL, 1, 0, '2024-10-17 15:20:10.412608', 0, '2024-10-17 15:20:10.412608', b'0', 0);\nINSERT INTO `m_perms` VALUES (87, 1846813517064654848, 'basic:file:get', '查询文件', 1683285343149096960, 40, NULL, NULL, 'B', NULL, b'0', b'1', NULL, 1, 0, '2024-10-17 15:20:29.294472', 0, '2024-10-17 15:20:29.294472', b'0', 0);\nINSERT INTO `m_perms` VALUES (88, 1846819585257852928, 'system:swagger', '查看接口', 1680749851857862656, 10, NULL, NULL, 'B', NULL, b'0', b'1', NULL, 1, 0, '2024-10-17 15:44:36.063573', 0, '2024-10-17 15:44:36.063573', b'0', 0);\n\n-- ----------------------------\n-- Table structure for m_post\n-- ----------------------------\nDROP TABLE IF EXISTS `m_post`;\nCREATE TABLE `m_post`  (\n  `id` bigint(0) NOT NULL AUTO_INCREMENT,\n  `post_id` bigint(0) NOT NULL COMMENT '岗位ID',\n  `post_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '岗位编码',\n  `post_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '岗位名称',\n  `post_sort` int(0) NOT NULL COMMENT '显示顺序',\n  `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',\n  `status` tinyint(0) NULL DEFAULT 1 COMMENT '状态  0禁用 1正常',\n  `tenant_id` bigint(0) NULL DEFAULT NULL COMMENT '租户编号',\n  `create_id` bigint(0) NULL DEFAULT 0 COMMENT '创建人ID',\n  `create_time` datetime(6) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_id` bigint(0) NULL DEFAULT 0 COMMENT '更新人ID',\n  `update_time` datetime(6) NULL DEFAULT NULL COMMENT '更新时间',\n  `deleted` bit(1) NULL DEFAULT b'0' COMMENT '逻辑删除  0未删除  1已删除',\n  `version` int(0) NULL DEFAULT 0 COMMENT '版本号',\n  PRIMARY KEY (`id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 12 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '岗位表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of m_post\n-- ----------------------------\nINSERT INTO `m_post` VALUES (1, 1676044813214400512, 'super-admin', '超级管理员', 0, '超级无敌管理员', 1, 0, 0, '2023-07-04 09:46:37.534734', 0, '2023-07-27 23:23:36.287549', b'0', 3);\nINSERT INTO `m_post` VALUES (3, 1676046492030717952, 'develop-post', '开发', 20, NULL, 1, 0, 0, '2023-07-04 09:53:17.795741', 0, '2023-07-12 13:55:35.942153', b'0', 3);\nINSERT INTO `m_post` VALUES (4, 1679006310662467584, 'sell-post', '销售', 30, NULL, 1, 0, 0, '2023-07-12 13:54:33.547206', 0, '2023-07-12 13:55:38.805938', b'0', 1);\nINSERT INTO `m_post` VALUES (5, 1679006401066496000, 'finance-post', '财务', 40, NULL, 1, 0, 0, '2023-07-12 13:54:55.102228', 0, '2023-07-12 13:54:55.102228', b'0', 0);\nINSERT INTO `m_post` VALUES (6, 1685180496364257280, 'tester-post', '测试岗', 100, NULL, 1, 0, 0, '2023-07-29 14:48:37.544778', 0, '2023-07-31 22:32:33.193652', b'0', 1);\nINSERT INTO `m_post` VALUES (7, 1685181450862055424, 'hr-post', '人力资源', 50, NULL, 1, 0, 0, '2023-07-29 14:52:21.704777', 0, '2023-07-31 22:32:40.815096', b'0', 2);\nINSERT INTO `m_post` VALUES (8, 1836060404593487872, 'xiaoshou', '销售1', 1, NULL, 1, 1836055976968843264, 1836055978990497792, '2024-09-17 23:11:27.505296', 1836055978990497792, '2024-09-17 23:18:33.795853', b'0', 1);\nINSERT INTO `m_post` VALUES (9, 1836060433332858880, 'caiwu', '财务2', 2, NULL, 1, 1836055976968843264, 1836055978990497792, '2024-09-17 23:11:34.356145', 1836055978990497792, '2024-09-17 23:18:37.233269', b'0', 1);\nINSERT INTO `m_post` VALUES (10, 1836060483333156864, 'dongshizhang', '董事长', 0, NULL, 1, 1836055976968843264, 1836055978990497792, '2024-09-17 23:11:46.279929', 1836055978990497792, '2024-09-17 23:11:46.279929', b'0', 0);\nINSERT INTO `m_post` VALUES (11, 1863857681786707968, 'security-test-post', '安全测试', 60, NULL, 1, 0, 0, '2024-12-03 16:07:55.000000', 0, '2024-12-03 16:07:55.000000', b'0', 0);\n\n-- ----------------------------\n-- Table structure for m_role\n-- ----------------------------\nDROP TABLE IF EXISTS `m_role`;\nCREATE TABLE `m_role`  (\n  `id` bigint(0) NOT NULL AUTO_INCREMENT,\n  `role_id` bigint(0) NOT NULL COMMENT '角色ID',\n  `role_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色名称',\n  `role_code` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色编码',\n  `role_sort` int(0) NULL DEFAULT 0 COMMENT '显示顺序',\n  `status` tinyint(0) NULL DEFAULT 1 COMMENT '状态  0禁用 1正常',\n  `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',\n  `tenant_id` bigint(0) NULL DEFAULT NULL COMMENT '租户编号',\n  `create_id` bigint(0) NULL DEFAULT 0 COMMENT '创建人ID',\n  `create_time` datetime(6) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_id` bigint(0) NULL DEFAULT 0 COMMENT '更新人ID',\n  `update_time` datetime(6) NULL DEFAULT NULL COMMENT '更新时间',\n  `deleted` bit(1) NULL DEFAULT b'0' COMMENT '逻辑删除  0未删除  1已删除',\n  `version` int(0) NULL DEFAULT 0 COMMENT '版本号',\n  PRIMARY KEY (`id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 118 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of m_role\n-- ----------------------------\nINSERT INTO `m_role` VALUES (101, 1671337763855073280, '系统管理员', 'system_admin', 0, 1, NULL, 0, 0, '2023-06-21 10:02:29.514290', 0, '2024-10-17 16:10:43.997451', b'0', 43);\nINSERT INTO `m_role` VALUES (104, 1671343260918325248, '销售人员', 'sell', 10, 1, NULL, 0, 0, '2023-06-21 10:24:20.116648', 0, '2024-10-29 16:41:07.000000', b'0', 21);\nINSERT INTO `m_role` VALUES (105, 1671389807492136960, '财务人员', 'financial', 20, 1, NULL, 0, 0, '2023-06-21 13:29:17.684715', 0, '2023-08-06 12:52:19.880964', b'0', 7);\nINSERT INTO `m_role` VALUES (106, 1677966412497596416, '开发人员', 'developer', 30, 1, NULL, 0, 0, '2023-07-09 17:02:22.504978', 0, '2023-08-06 12:52:25.197098', b'0', 1);\nINSERT INTO `m_role` VALUES (107, 1677966506420645888, '测试人员', 'tester', 40, 1, NULL, 0, 0, '2023-07-09 17:02:44.898094', 0, '2023-08-06 12:52:30.282735', b'0', 1);\nINSERT INTO `m_role` VALUES (108, 1677966630567849984, '运维人员', 'devopser', 50, 1, NULL, 0, 0, '2023-07-09 17:03:14.496421', 0, '2023-08-06 12:52:34.732570', b'0', 1);\nINSERT INTO `m_role` VALUES (109, 1834763883194494977, '管理员', 'admin', 0, 1, '添加租户系统自动创建角色', 1834763883194494976, 0, '2024-09-14 09:19:32.826724', 0, '2024-09-14 09:19:32.826724', b'0', 0);\nINSERT INTO `m_role` VALUES (110, 1834790150900002816, '普通用户', 'putongyonghu', 1, 1, NULL, 1834763883194494976, 1834763884364705792, '2024-09-14 11:03:55.423220', 0, '2024-10-04 21:44:55.734261', b'0', 2);\nINSERT INTO `m_role` VALUES (111, 1836055976973037568, '管理员', 'admin', 0, 1, '添加租户系统自动创建角色', 1836055976968843264, 0, '2024-09-17 22:53:52.221775', 1836055978990497792, '2024-09-17 23:29:36.280452', b'0', 1);\nINSERT INTO `m_role` VALUES (112, 1836056110565814273, '管理员', 'admin', 0, 1, '添加租户系统自动创建角色', 1836056110565814272, 0, '2024-09-17 22:54:23.727486', 0, '2024-09-17 22:54:23.727486', b'0', 0);\nINSERT INTO `m_role` VALUES (113, 1836057975659556864, '测试租户1普通', 'ceshi1-putong', 1, 1, NULL, 1836055976968843264, 1836055978990497792, '2024-09-17 23:01:48.394988', 1836055978990497792, '2024-09-17 23:01:48.394988', b'0', 0);\nINSERT INTO `m_role` VALUES (114, 1846431773459197953, '管理员', 'admin', 0, 1, '添加租户系统自动创建角色', 1846431773459197952, 0, '2024-10-16 14:03:34.527236', 0, '2024-10-16 14:03:34.527236', b'0', 0);\nINSERT INTO `m_role` VALUES (115, 1890404237937938432, '管理员', 'admin', 0, 1, '系统自动创建角色', 1890404237296209920, 0, '2025-02-14 22:14:26.000000', 0, '2025-02-14 22:14:26.000000', b'0', 0);\nINSERT INTO `m_role` VALUES (116, 1890405820998291456, '管理员', 'admin', 0, 1, '系统自动创建角色', 1890405820364951552, 0, '2025-02-14 22:20:44.000000', 0, '2025-02-14 22:20:44.000000', b'0', 0);\nINSERT INTO `m_role` VALUES (117, 1890406866659250176, '管理员', 'admin', 0, 1, '系统自动创建角色', 1890406866181099520, 0, '2025-02-14 22:24:53.000000', 0, '2025-02-14 22:24:53.000000', b'0', 0);\n\n-- ----------------------------\n-- Table structure for m_role_dept\n-- ----------------------------\nDROP TABLE IF EXISTS `m_role_dept`;\nCREATE TABLE `m_role_dept`  (\n  `id` bigint(0) NOT NULL AUTO_INCREMENT,\n  `role_id` bigint(0) NOT NULL COMMENT '角色ID',\n  `dept_id` bigint(0) NOT NULL COMMENT '部门ID',\n  PRIMARY KEY (`id`) USING BTREE,\n  INDEX `dept_id_idx`(`dept_id`) USING BTREE COMMENT '部门ID索引',\n  INDEX `role_id_idx`(`role_id`) USING BTREE COMMENT '角色ID索引'\n) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色与部门关联表 1角色-N部门' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Table structure for m_role_perm\n-- ----------------------------\nDROP TABLE IF EXISTS `m_role_perm`;\nCREATE TABLE `m_role_perm`  (\n  `id` bigint(0) NOT NULL AUTO_INCREMENT,\n  `role_id` bigint(0) NOT NULL COMMENT '角色ID',\n  `perm_id` bigint(0) NOT NULL COMMENT '权限ID',\n  PRIMARY KEY (`id`) USING BTREE,\n  INDEX `perm_id_idx`(`perm_id`) USING BTREE COMMENT '权限ID索引',\n  INDEX `role_id_idx`(`role_id`) USING BTREE COMMENT '角色ID索引'\n) ENGINE = InnoDB AUTO_INCREMENT = 2098 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色与权限关联表 1角色-N权限' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of m_role_perm\n-- ----------------------------\nINSERT INTO `m_role_perm` VALUES (747, 1671389807492136960, 1673863803093557248);\nINSERT INTO `m_role_perm` VALUES (748, 1677966412497596416, 1673863803093557248);\nINSERT INTO `m_role_perm` VALUES (749, 1677966506420645888, 1673863803093557248);\nINSERT INTO `m_role_perm` VALUES (750, 1677966630567849984, 1673863803093557248);\nINSERT INTO `m_role_perm` VALUES (880, 1836057975659556864, 1669336412647133184);\nINSERT INTO `m_role_perm` VALUES (881, 1836057975659556864, 1669338708936298496);\nINSERT INTO `m_role_perm` VALUES (882, 1836057975659556864, 1685985572129398784);\nINSERT INTO `m_role_perm` VALUES (883, 1836057975659556864, 1685985875515990016);\nINSERT INTO `m_role_perm` VALUES (884, 1836057975659556864, 1685985977349496832);\nINSERT INTO `m_role_perm` VALUES (885, 1836057975659556864, 1685986276453703680);\nINSERT INTO `m_role_perm` VALUES (1110, 1834790150900002816, 1685985572129398784);\nINSERT INTO `m_role_perm` VALUES (1111, 1834790150900002816, 1685985875515990016);\nINSERT INTO `m_role_perm` VALUES (1112, 1834790150900002816, 1685985977349496832);\nINSERT INTO `m_role_perm` VALUES (1113, 1834790150900002816, 1685986276453703680);\nINSERT INTO `m_role_perm` VALUES (1114, 1834790150900002816, 1669338708936298496);\nINSERT INTO `m_role_perm` VALUES (1115, 1834790150900002816, 1673167646340120576);\nINSERT INTO `m_role_perm` VALUES (1118, 1834790150900002816, 1669336412647133184);\nINSERT INTO `m_role_perm` VALUES (1119, 1834790150900002816, 1685986566443687936);\nINSERT INTO `m_role_perm` VALUES (1120, 1834790150900002816, 1685986702834065408);\nINSERT INTO `m_role_perm` VALUES (1121, 1834790150900002816, 1685986806387236864);\nINSERT INTO `m_role_perm` VALUES (1122, 1834790150900002816, 1685986887102423040);\nINSERT INTO `m_role_perm` VALUES (1880, 1671337763855073280, 1673863803093557248);\nINSERT INTO `m_role_perm` VALUES (1881, 1671337763855073280, 1669336412647133184);\nINSERT INTO `m_role_perm` VALUES (1882, 1671337763855073280, 1669338708936298496);\nINSERT INTO `m_role_perm` VALUES (1883, 1671337763855073280, 1685985572129398784);\nINSERT INTO `m_role_perm` VALUES (1884, 1671337763855073280, 1685985875515990016);\nINSERT INTO `m_role_perm` VALUES (1885, 1671337763855073280, 1685985977349496832);\nINSERT INTO `m_role_perm` VALUES (1886, 1671337763855073280, 1685986276453703680);\nINSERT INTO `m_role_perm` VALUES (1887, 1671337763855073280, 1673167646340120576);\nINSERT INTO `m_role_perm` VALUES (1888, 1671337763855073280, 1685986566443687936);\nINSERT INTO `m_role_perm` VALUES (1889, 1671337763855073280, 1685986702834065408);\nINSERT INTO `m_role_perm` VALUES (1890, 1671337763855073280, 1685986806387236864);\nINSERT INTO `m_role_perm` VALUES (1891, 1671337763855073280, 1685986887102423040);\nINSERT INTO `m_role_perm` VALUES (1892, 1671337763855073280, 1669339147886989312);\nINSERT INTO `m_role_perm` VALUES (1893, 1671337763855073280, 1685987612687654912);\nINSERT INTO `m_role_perm` VALUES (1894, 1671337763855073280, 1685987708229705728);\nINSERT INTO `m_role_perm` VALUES (1895, 1671337763855073280, 1685988419789185024);\nINSERT INTO `m_role_perm` VALUES (1896, 1671337763855073280, 1685988623951126528);\nINSERT INTO `m_role_perm` VALUES (1897, 1671337763855073280, 1673524365792530432);\nINSERT INTO `m_role_perm` VALUES (1898, 1671337763855073280, 1685996318275985408);\nINSERT INTO `m_role_perm` VALUES (1899, 1671337763855073280, 1685996426530971648);\nINSERT INTO `m_role_perm` VALUES (1900, 1671337763855073280, 1685996547448561664);\nINSERT INTO `m_role_perm` VALUES (1901, 1671337763855073280, 1685996623386435584);\nINSERT INTO `m_role_perm` VALUES (1902, 1671337763855073280, 1673524591861321728);\nINSERT INTO `m_role_perm` VALUES (1903, 1671337763855073280, 1685996875623489536);\nINSERT INTO `m_role_perm` VALUES (1904, 1671337763855073280, 1685997536914235392);\nINSERT INTO `m_role_perm` VALUES (1905, 1671337763855073280, 1685997640341577728);\nINSERT INTO `m_role_perm` VALUES (1906, 1671337763855073280, 1685997732956004352);\nINSERT INTO `m_role_perm` VALUES (1907, 1671337763855073280, 1669339375922909184);\nINSERT INTO `m_role_perm` VALUES (1908, 1671337763855073280, 1685998041879076864);\nINSERT INTO `m_role_perm` VALUES (1909, 1671337763855073280, 1685998171407572992);\nINSERT INTO `m_role_perm` VALUES (1910, 1671337763855073280, 1685998244715618304);\nINSERT INTO `m_role_perm` VALUES (1911, 1671337763855073280, 1685998340425441280);\nINSERT INTO `m_role_perm` VALUES (1912, 1671337763855073280, 1683285343149096960);\nINSERT INTO `m_role_perm` VALUES (1913, 1671337763855073280, 1846813160695615488);\nINSERT INTO `m_role_perm` VALUES (1914, 1671337763855073280, 1846813266043949056);\nINSERT INTO `m_role_perm` VALUES (1915, 1671337763855073280, 1846813437867806720);\nINSERT INTO `m_role_perm` VALUES (1916, 1671337763855073280, 1846813517064654848);\nINSERT INTO `m_role_perm` VALUES (1917, 1671337763855073280, 1843898617245937664);\nINSERT INTO `m_role_perm` VALUES (1918, 1671337763855073280, 1843900739123359744);\nINSERT INTO `m_role_perm` VALUES (1919, 1671337763855073280, 1843900820899704832);\nINSERT INTO `m_role_perm` VALUES (1920, 1671337763855073280, 1843900873001349120);\nINSERT INTO `m_role_perm` VALUES (1921, 1671337763855073280, 1843900946787545088);\nINSERT INTO `m_role_perm` VALUES (1922, 1671337763855073280, 1676499330464649216);\nINSERT INTO `m_role_perm` VALUES (1923, 1671337763855073280, 1676499559247155200);\nINSERT INTO `m_role_perm` VALUES (1924, 1671337763855073280, 1686002657173757952);\nINSERT INTO `m_role_perm` VALUES (1925, 1671337763855073280, 1686002750660599808);\nINSERT INTO `m_role_perm` VALUES (1926, 1671337763855073280, 1686002863185387520);\nINSERT INTO `m_role_perm` VALUES (1927, 1671337763855073280, 1686002975089418240);\nINSERT INTO `m_role_perm` VALUES (1928, 1671337763855073280, 1676501907755405312);\nINSERT INTO `m_role_perm` VALUES (1929, 1671337763855073280, 1686004858881368064);\nINSERT INTO `m_role_perm` VALUES (1930, 1671337763855073280, 1686005292303966208);\nINSERT INTO `m_role_perm` VALUES (1931, 1671337763855073280, 1686005483111243776);\nINSERT INTO `m_role_perm` VALUES (1932, 1671337763855073280, 1686005567790047232);\nINSERT INTO `m_role_perm` VALUES (1933, 1671337763855073280, 1681273378598850560);\nINSERT INTO `m_role_perm` VALUES (1934, 1671337763855073280, 1686001693859569664);\nINSERT INTO `m_role_perm` VALUES (1935, 1671337763855073280, 1686001770980237312);\nINSERT INTO `m_role_perm` VALUES (1936, 1671337763855073280, 1686002193703165952);\nINSERT INTO `m_role_perm` VALUES (1937, 1671337763855073280, 1686002291338174464);\nINSERT INTO `m_role_perm` VALUES (1938, 1671337763855073280, 1833687152849211392);\nINSERT INTO `m_role_perm` VALUES (1939, 1671337763855073280, 1833688664656728064);\nINSERT INTO `m_role_perm` VALUES (1940, 1671337763855073280, 1833688838682595328);\nINSERT INTO `m_role_perm` VALUES (1941, 1671337763855073280, 1833688910472302592);\nINSERT INTO `m_role_perm` VALUES (1942, 1671337763855073280, 1833688972682219520);\nINSERT INTO `m_role_perm` VALUES (1943, 1671337763855073280, 1680749851857862656);\nINSERT INTO `m_role_perm` VALUES (1944, 1671337763855073280, 1846819585257852928);\nINSERT INTO `m_role_perm` VALUES (1945, 1671343260918325248, 1669336412647133184);\nINSERT INTO `m_role_perm` VALUES (1946, 1671343260918325248, 1685985572129398784);\nINSERT INTO `m_role_perm` VALUES (1947, 1671343260918325248, 1685985875515990016);\nINSERT INTO `m_role_perm` VALUES (1948, 1671343260918325248, 1685985977349496832);\nINSERT INTO `m_role_perm` VALUES (1949, 1671343260918325248, 1685986276453703680);\nINSERT INTO `m_role_perm` VALUES (1950, 1671343260918325248, 1669338708936298496);\nINSERT INTO `m_role_perm` VALUES (1951, 1834763883194494977, 1669336412647133184);\nINSERT INTO `m_role_perm` VALUES (1952, 1834763883194494977, 1685986566443687936);\nINSERT INTO `m_role_perm` VALUES (1953, 1834763883194494977, 1685986702834065408);\nINSERT INTO `m_role_perm` VALUES (1954, 1834763883194494977, 1685986806387236864);\nINSERT INTO `m_role_perm` VALUES (1955, 1834763883194494977, 1685986887102423040);\nINSERT INTO `m_role_perm` VALUES (1956, 1834763883194494977, 1673167646340120576);\nINSERT INTO `m_role_perm` VALUES (1957, 1834763883194494977, 1685985572129398784);\nINSERT INTO `m_role_perm` VALUES (1958, 1834763883194494977, 1685985875515990016);\nINSERT INTO `m_role_perm` VALUES (1959, 1834763883194494977, 1685985977349496832);\nINSERT INTO `m_role_perm` VALUES (1960, 1834763883194494977, 1685986276453703680);\nINSERT INTO `m_role_perm` VALUES (1961, 1834763883194494977, 1669338708936298496);\nINSERT INTO `m_role_perm` VALUES (1962, 1834763883194494977, 1685996318275985408);\nINSERT INTO `m_role_perm` VALUES (1963, 1834763883194494977, 1685996426530971648);\nINSERT INTO `m_role_perm` VALUES (1964, 1834763883194494977, 1685996547448561664);\nINSERT INTO `m_role_perm` VALUES (1965, 1834763883194494977, 1685996623386435584);\nINSERT INTO `m_role_perm` VALUES (1966, 1834763883194494977, 1673524365792530432);\nINSERT INTO `m_role_perm` VALUES (1967, 1834763883194494977, 1685996875623489536);\nINSERT INTO `m_role_perm` VALUES (1968, 1834763883194494977, 1685997536914235392);\nINSERT INTO `m_role_perm` VALUES (1969, 1834763883194494977, 1685997640341577728);\nINSERT INTO `m_role_perm` VALUES (1970, 1834763883194494977, 1685997732956004352);\nINSERT INTO `m_role_perm` VALUES (1971, 1834763883194494977, 1673524591861321728);\nINSERT INTO `m_role_perm` VALUES (1972, 1836055976973037568, 1669336412647133184);\nINSERT INTO `m_role_perm` VALUES (1973, 1836055976973037568, 1685986566443687936);\nINSERT INTO `m_role_perm` VALUES (1974, 1836055976973037568, 1685986702834065408);\nINSERT INTO `m_role_perm` VALUES (1975, 1836055976973037568, 1685986806387236864);\nINSERT INTO `m_role_perm` VALUES (1976, 1836055976973037568, 1685986887102423040);\nINSERT INTO `m_role_perm` VALUES (1977, 1836055976973037568, 1673167646340120576);\nINSERT INTO `m_role_perm` VALUES (1978, 1836055976973037568, 1685985572129398784);\nINSERT INTO `m_role_perm` VALUES (1979, 1836055976973037568, 1685985875515990016);\nINSERT INTO `m_role_perm` VALUES (1980, 1836055976973037568, 1685985977349496832);\nINSERT INTO `m_role_perm` VALUES (1981, 1836055976973037568, 1685986276453703680);\nINSERT INTO `m_role_perm` VALUES (1982, 1836055976973037568, 1669338708936298496);\nINSERT INTO `m_role_perm` VALUES (1983, 1836055976973037568, 1685996318275985408);\nINSERT INTO `m_role_perm` VALUES (1984, 1836055976973037568, 1685996426530971648);\nINSERT INTO `m_role_perm` VALUES (1985, 1836055976973037568, 1685996547448561664);\nINSERT INTO `m_role_perm` VALUES (1986, 1836055976973037568, 1685996623386435584);\nINSERT INTO `m_role_perm` VALUES (1987, 1836055976973037568, 1673524365792530432);\nINSERT INTO `m_role_perm` VALUES (1988, 1836055976973037568, 1685996875623489536);\nINSERT INTO `m_role_perm` VALUES (1989, 1836055976973037568, 1685997536914235392);\nINSERT INTO `m_role_perm` VALUES (1990, 1836055976973037568, 1685997640341577728);\nINSERT INTO `m_role_perm` VALUES (1991, 1836055976973037568, 1685997732956004352);\nINSERT INTO `m_role_perm` VALUES (1992, 1836055976973037568, 1673524591861321728);\nINSERT INTO `m_role_perm` VALUES (1993, 1836056110565814273, 1669336412647133184);\nINSERT INTO `m_role_perm` VALUES (1994, 1836056110565814273, 1685986566443687936);\nINSERT INTO `m_role_perm` VALUES (1995, 1836056110565814273, 1685986702834065408);\nINSERT INTO `m_role_perm` VALUES (1996, 1836056110565814273, 1685986806387236864);\nINSERT INTO `m_role_perm` VALUES (1997, 1836056110565814273, 1685986887102423040);\nINSERT INTO `m_role_perm` VALUES (1998, 1836056110565814273, 1673167646340120576);\nINSERT INTO `m_role_perm` VALUES (1999, 1836056110565814273, 1685985572129398784);\nINSERT INTO `m_role_perm` VALUES (2000, 1836056110565814273, 1685985875515990016);\nINSERT INTO `m_role_perm` VALUES (2001, 1836056110565814273, 1685985977349496832);\nINSERT INTO `m_role_perm` VALUES (2002, 1836056110565814273, 1685986276453703680);\nINSERT INTO `m_role_perm` VALUES (2003, 1836056110565814273, 1669338708936298496);\nINSERT INTO `m_role_perm` VALUES (2004, 1836056110565814273, 1685996318275985408);\nINSERT INTO `m_role_perm` VALUES (2005, 1836056110565814273, 1685996426530971648);\nINSERT INTO `m_role_perm` VALUES (2006, 1836056110565814273, 1685996547448561664);\nINSERT INTO `m_role_perm` VALUES (2007, 1836056110565814273, 1685996623386435584);\nINSERT INTO `m_role_perm` VALUES (2008, 1836056110565814273, 1673524365792530432);\nINSERT INTO `m_role_perm` VALUES (2009, 1836056110565814273, 1685996875623489536);\nINSERT INTO `m_role_perm` VALUES (2010, 1836056110565814273, 1685997536914235392);\nINSERT INTO `m_role_perm` VALUES (2011, 1836056110565814273, 1685997640341577728);\nINSERT INTO `m_role_perm` VALUES (2012, 1836056110565814273, 1685997732956004352);\nINSERT INTO `m_role_perm` VALUES (2013, 1836056110565814273, 1673524591861321728);\nINSERT INTO `m_role_perm` VALUES (2014, 1846431773459197953, 1669336412647133184);\nINSERT INTO `m_role_perm` VALUES (2015, 1846431773459197953, 1685986566443687936);\nINSERT INTO `m_role_perm` VALUES (2016, 1846431773459197953, 1685986702834065408);\nINSERT INTO `m_role_perm` VALUES (2017, 1846431773459197953, 1685986806387236864);\nINSERT INTO `m_role_perm` VALUES (2018, 1846431773459197953, 1685986887102423040);\nINSERT INTO `m_role_perm` VALUES (2019, 1846431773459197953, 1673167646340120576);\nINSERT INTO `m_role_perm` VALUES (2020, 1846431773459197953, 1685985572129398784);\nINSERT INTO `m_role_perm` VALUES (2021, 1846431773459197953, 1685985875515990016);\nINSERT INTO `m_role_perm` VALUES (2022, 1846431773459197953, 1685985977349496832);\nINSERT INTO `m_role_perm` VALUES (2023, 1846431773459197953, 1685986276453703680);\nINSERT INTO `m_role_perm` VALUES (2024, 1846431773459197953, 1669338708936298496);\nINSERT INTO `m_role_perm` VALUES (2025, 1846431773459197953, 1685996318275985408);\nINSERT INTO `m_role_perm` VALUES (2026, 1846431773459197953, 1685996426530971648);\nINSERT INTO `m_role_perm` VALUES (2027, 1846431773459197953, 1685996547448561664);\nINSERT INTO `m_role_perm` VALUES (2028, 1846431773459197953, 1685996623386435584);\nINSERT INTO `m_role_perm` VALUES (2029, 1846431773459197953, 1673524365792530432);\nINSERT INTO `m_role_perm` VALUES (2030, 1846431773459197953, 1685996875623489536);\nINSERT INTO `m_role_perm` VALUES (2031, 1846431773459197953, 1685997536914235392);\nINSERT INTO `m_role_perm` VALUES (2032, 1846431773459197953, 1685997640341577728);\nINSERT INTO `m_role_perm` VALUES (2033, 1846431773459197953, 1685997732956004352);\nINSERT INTO `m_role_perm` VALUES (2034, 1846431773459197953, 1673524591861321728);\nINSERT INTO `m_role_perm` VALUES (2035, 1890404237937938432, 1669336412647133184);\nINSERT INTO `m_role_perm` VALUES (2036, 1890404237937938432, 1685986566443687936);\nINSERT INTO `m_role_perm` VALUES (2037, 1890404237937938432, 1685986702834065408);\nINSERT INTO `m_role_perm` VALUES (2038, 1890404237937938432, 1685986806387236864);\nINSERT INTO `m_role_perm` VALUES (2039, 1890404237937938432, 1685986887102423040);\nINSERT INTO `m_role_perm` VALUES (2040, 1890404237937938432, 1673167646340120576);\nINSERT INTO `m_role_perm` VALUES (2041, 1890404237937938432, 1685985572129398784);\nINSERT INTO `m_role_perm` VALUES (2042, 1890404237937938432, 1685985875515990016);\nINSERT INTO `m_role_perm` VALUES (2043, 1890404237937938432, 1685985977349496832);\nINSERT INTO `m_role_perm` VALUES (2044, 1890404237937938432, 1685986276453703680);\nINSERT INTO `m_role_perm` VALUES (2045, 1890404237937938432, 1669338708936298496);\nINSERT INTO `m_role_perm` VALUES (2046, 1890404237937938432, 1685996318275985408);\nINSERT INTO `m_role_perm` VALUES (2047, 1890404237937938432, 1685996426530971648);\nINSERT INTO `m_role_perm` VALUES (2048, 1890404237937938432, 1685996547448561664);\nINSERT INTO `m_role_perm` VALUES (2049, 1890404237937938432, 1685996623386435584);\nINSERT INTO `m_role_perm` VALUES (2050, 1890404237937938432, 1673524365792530432);\nINSERT INTO `m_role_perm` VALUES (2051, 1890404237937938432, 1685996875623489536);\nINSERT INTO `m_role_perm` VALUES (2052, 1890404237937938432, 1685997536914235392);\nINSERT INTO `m_role_perm` VALUES (2053, 1890404237937938432, 1685997640341577728);\nINSERT INTO `m_role_perm` VALUES (2054, 1890404237937938432, 1685997732956004352);\nINSERT INTO `m_role_perm` VALUES (2055, 1890404237937938432, 1673524591861321728);\nINSERT INTO `m_role_perm` VALUES (2056, 1890405820998291456, 1669336412647133184);\nINSERT INTO `m_role_perm` VALUES (2057, 1890405820998291456, 1685986566443687936);\nINSERT INTO `m_role_perm` VALUES (2058, 1890405820998291456, 1685986702834065408);\nINSERT INTO `m_role_perm` VALUES (2059, 1890405820998291456, 1685986806387236864);\nINSERT INTO `m_role_perm` VALUES (2060, 1890405820998291456, 1685986887102423040);\nINSERT INTO `m_role_perm` VALUES (2061, 1890405820998291456, 1673167646340120576);\nINSERT INTO `m_role_perm` VALUES (2062, 1890405820998291456, 1685985572129398784);\nINSERT INTO `m_role_perm` VALUES (2063, 1890405820998291456, 1685985875515990016);\nINSERT INTO `m_role_perm` VALUES (2064, 1890405820998291456, 1685985977349496832);\nINSERT INTO `m_role_perm` VALUES (2065, 1890405820998291456, 1685986276453703680);\nINSERT INTO `m_role_perm` VALUES (2066, 1890405820998291456, 1669338708936298496);\nINSERT INTO `m_role_perm` VALUES (2067, 1890405820998291456, 1685996318275985408);\nINSERT INTO `m_role_perm` VALUES (2068, 1890405820998291456, 1685996426530971648);\nINSERT INTO `m_role_perm` VALUES (2069, 1890405820998291456, 1685996547448561664);\nINSERT INTO `m_role_perm` VALUES (2070, 1890405820998291456, 1685996623386435584);\nINSERT INTO `m_role_perm` VALUES (2071, 1890405820998291456, 1673524365792530432);\nINSERT INTO `m_role_perm` VALUES (2072, 1890405820998291456, 1685996875623489536);\nINSERT INTO `m_role_perm` VALUES (2073, 1890405820998291456, 1685997536914235392);\nINSERT INTO `m_role_perm` VALUES (2074, 1890405820998291456, 1685997640341577728);\nINSERT INTO `m_role_perm` VALUES (2075, 1890405820998291456, 1685997732956004352);\nINSERT INTO `m_role_perm` VALUES (2076, 1890405820998291456, 1673524591861321728);\nINSERT INTO `m_role_perm` VALUES (2077, 1890406866659250176, 1669336412647133184);\nINSERT INTO `m_role_perm` VALUES (2078, 1890406866659250176, 1685986566443687936);\nINSERT INTO `m_role_perm` VALUES (2079, 1890406866659250176, 1685986702834065408);\nINSERT INTO `m_role_perm` VALUES (2080, 1890406866659250176, 1685986806387236864);\nINSERT INTO `m_role_perm` VALUES (2081, 1890406866659250176, 1685986887102423040);\nINSERT INTO `m_role_perm` VALUES (2082, 1890406866659250176, 1673167646340120576);\nINSERT INTO `m_role_perm` VALUES (2083, 1890406866659250176, 1685985572129398784);\nINSERT INTO `m_role_perm` VALUES (2084, 1890406866659250176, 1685985875515990016);\nINSERT INTO `m_role_perm` VALUES (2085, 1890406866659250176, 1685985977349496832);\nINSERT INTO `m_role_perm` VALUES (2086, 1890406866659250176, 1685986276453703680);\nINSERT INTO `m_role_perm` VALUES (2087, 1890406866659250176, 1669338708936298496);\nINSERT INTO `m_role_perm` VALUES (2088, 1890406866659250176, 1685996318275985408);\nINSERT INTO `m_role_perm` VALUES (2089, 1890406866659250176, 1685996426530971648);\nINSERT INTO `m_role_perm` VALUES (2090, 1890406866659250176, 1685996547448561664);\nINSERT INTO `m_role_perm` VALUES (2091, 1890406866659250176, 1685996623386435584);\nINSERT INTO `m_role_perm` VALUES (2092, 1890406866659250176, 1673524365792530432);\nINSERT INTO `m_role_perm` VALUES (2093, 1890406866659250176, 1685996875623489536);\nINSERT INTO `m_role_perm` VALUES (2094, 1890406866659250176, 1685997536914235392);\nINSERT INTO `m_role_perm` VALUES (2095, 1890406866659250176, 1685997640341577728);\nINSERT INTO `m_role_perm` VALUES (2096, 1890406866659250176, 1685997732956004352);\nINSERT INTO `m_role_perm` VALUES (2097, 1890406866659250176, 1673524591861321728);\n\n-- ----------------------------\n-- Table structure for m_storage\n-- ----------------------------\nDROP TABLE IF EXISTS `m_storage`;\nCREATE TABLE `m_storage`  (\n  `id` bigint(0) NOT NULL AUTO_INCREMENT,\n  `storage_id` bigint(0) NOT NULL,\n  `storage_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '名称',\n  `storage_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '存储类型，用于标识存储平台，如本地、阿里云oss、七牛云oss等',\n  `description` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '说明',\n  `storage_config` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '存储配置，JSON数据',\n  `status` tinyint(0) NULL DEFAULT 1 COMMENT '状态  0禁用 1正常',\n  `create_id` bigint(0) NULL DEFAULT 0 COMMENT '创建人ID',\n  `create_time` datetime(6) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_id` bigint(0) NULL DEFAULT 0 COMMENT '更新人ID',\n  `update_time` datetime(6) NULL DEFAULT NULL COMMENT '更新时间',\n  `deleted` bit(1) NULL DEFAULT b'0' COMMENT '逻辑删除  0未删除  1已删除',\n  `version` int(0) NULL DEFAULT 0 COMMENT '版本号',\n  PRIMARY KEY (`id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '存储管理表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of m_storage\n-- ----------------------------\nINSERT INTO `m_storage` VALUES (1, 1843939697907720192, '本地存储测试', 'local', '1', '{\\\"storagePath\\\":\\\"D:/application/minimalist-saas/\\\"}', 1, 0, NULL, 0, '2025-03-12 21:43:52.000000', b'0', 2);\nINSERT INTO `m_storage` VALUES (2, 1844270972333924352, 'minio', 'minio', '2', '{\\\"accessKey\\\":\\\"5555\\\",\\\"secretKey\\\":\\\"5555\\\",\\\"endPoint\\\":\\\"5555\\\",\\\"bucketName\\\":\\\"5555\\\"}', 1, 0, NULL, 0, NULL, b'0', 0);\nINSERT INTO `m_storage` VALUES (4, 1891827317205766144, '七牛云2', 'qiniu', NULL, '{\\\"accessKey\\\":\\\"2222\\\",\\\"secretKey\\\":\\\"2222\\\",\\\"endPoint\\\":\\\"2222\\\",\\\"bucketName\\\":\\\"2222\\\",\\\"regionId\\\":\\\"2222\\\"}', 1, 0, '2025-02-18 20:29:15.000000', 0, '2025-02-18 20:29:56.000000', b'0', 2);\n\n-- ----------------------------\n-- Table structure for m_tenant\n-- ----------------------------\nDROP TABLE IF EXISTS `m_tenant`;\nCREATE TABLE `m_tenant`  (\n  `id` bigint(0) NOT NULL AUTO_INCREMENT,\n  `tenant_id` bigint(0) NOT NULL COMMENT '租户ID',\n  `user_id` bigint(0) NOT NULL COMMENT '用户ID',\n  `package_id` bigint(0) NOT NULL COMMENT '租户套餐ID',\n  `tenant_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '租户名',\n  `expire_time` datetime(6) NOT NULL COMMENT '租户过期时间',\n  `account_count` int(0) NOT NULL DEFAULT 0 COMMENT '可创建账号数量',\n  `data_isolation` varchar(6) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'column' COMMENT '数据隔离方式  column字段隔离(默认) db数据库隔离',\n  `datasource` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'master' COMMENT '数据源名称 master(默认使用主库) ',\n  `storage_id` bigint(0) NULL DEFAULT NULL COMMENT '存储ID 表示该租户使用哪个文件存储',\n  `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',\n  `status` tinyint(0) NULL DEFAULT 1 COMMENT '状态 0禁用 1正常',\n  `create_id` bigint(0) NULL DEFAULT NULL COMMENT '创建人ID',\n  `create_time` datetime(6) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_id` bigint(0) NULL DEFAULT NULL COMMENT '更新人ID',\n  `update_time` datetime(6) NULL DEFAULT NULL COMMENT '更新时间',\n  `deleted` bit(1) NULL DEFAULT b'0' COMMENT '逻辑删除  0未删除  1已删除',\n  `version` int(0) NULL DEFAULT 0 COMMENT '版本号',\n  PRIMARY KEY (`id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '租户表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of m_tenant\n-- ----------------------------\nINSERT INTO `m_tenant` VALUES (1, 0, 0, 0, '系统租户', '2030-12-31 23:59:59.000000', 20, 'column', 'master', NULL, NULL, 1, 0, '2025-02-14 21:54:35.000000', 0, '2025-02-14 21:54:58.000000', b'0', 0);\nINSERT INTO `m_tenant` VALUES (3, 1834763883194494976, 1834763884364705792, 1676850220841287680, '东东胡辣汤', '2035-09-30 09:15:21.000000', 2, 'column', 'master', 1843939697907720192, NULL, 1, 0, '2025-02-14 21:54:38.000000', 0, '2025-02-18 21:56:23.000000', b'0', 2);\nINSERT INTO `m_tenant` VALUES (4, 1836055976968843264, 1836055978990497792, 1676850220841287680, '测试租户111', '2024-09-30 22:38:09.000000', 3, 'column', 'master', 1843939697907720192, NULL, 1, 0, '2025-02-14 21:54:41.000000', 0, '2025-02-18 21:56:32.000000', b'0', 2);\nINSERT INTO `m_tenant` VALUES (5, 1836056110565814272, 1836056110997827584, 1676850220841287680, '测试租户222', '2024-09-30 22:54:04.000000', 2, 'column', 'master', 1843939697907720192, NULL, 1, 0, '2025-02-14 21:54:43.000000', 0, '2025-02-18 21:56:39.000000', b'0', 2);\nINSERT INTO `m_tenant` VALUES (6, 1846431773459197952, 1846431773870239744, 1676850220841287680, '测试租户333', '2024-10-31 14:03:03.000000', 3, 'column', 'master', 1843939697907720192, NULL, 1, 0, '2025-02-14 21:54:46.000000', 0, '2025-02-18 21:56:42.000000', b'0', 2);\nINSERT INTO `m_tenant` VALUES (9, 1890406866181099520, 1890406866181099521, 1676850220841287680, '测试租户44', '2025-02-28 22:22:49.000000', 2, 'column', 'master', 1843939697907720192, NULL, 1, 0, '2025-02-14 22:24:53.000000', 0, '2025-02-19 11:01:00.000000', b'0', 6);\n\n-- ----------------------------\n-- Table structure for m_tenant_datasource\n-- ----------------------------\nDROP TABLE IF EXISTS `m_tenant_datasource`;\nCREATE TABLE `m_tenant_datasource`  (\n  `id` bigint(0) NOT NULL AUTO_INCREMENT,\n  `datasource_id` bigint(0) NOT NULL COMMENT '数据源ID',\n  `tenant_id` bigint(0) NOT NULL COMMENT '租户ID',\n  `datasource_name` varchar(24) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '数据源名称',\n  `datasource_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '数据源连接',\n  `username` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '数据源用户名',\n  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '数据源密码',\n  PRIMARY KEY (`id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '租户数据源表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Table structure for m_tenant_package\n-- ----------------------------\nDROP TABLE IF EXISTS `m_tenant_package`;\nCREATE TABLE `m_tenant_package`  (\n  `id` bigint(0) NOT NULL AUTO_INCREMENT,\n  `package_id` bigint(0) NOT NULL COMMENT '套餐ID',\n  `package_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '套餐名称',\n  `status` tinyint(0) NULL DEFAULT 1 COMMENT '状态 0禁用 1正常',\n  `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',\n  `create_id` bigint(0) NULL DEFAULT NULL COMMENT '创建人ID',\n  `create_time` datetime(6) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_id` bigint(0) NULL DEFAULT NULL COMMENT '更新人ID',\n  `update_time` datetime(6) NULL DEFAULT NULL COMMENT '更新时间',\n  `deleted` bit(1) NULL DEFAULT b'0' COMMENT '逻辑删除  0未删除  1已删除',\n  `version` int(0) NULL DEFAULT 0 COMMENT '版本号',\n  PRIMARY KEY (`id`) USING BTREE,\n  UNIQUE INDEX `unq_package_id`(`package_id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '租户套餐表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of m_tenant_package\n-- ----------------------------\nINSERT INTO `m_tenant_package` VALUES (5, 1676850220841287680, '基础套餐', 1, '基础套餐', 0, '2023-07-06 15:07:01.676599', 0, '2024-12-03 15:54:37.000000', b'0', 21);\nINSERT INTO `m_tenant_package` VALUES (9, 1863859488525045760, 'CRM套餐', 1, NULL, 0, '2024-12-03 16:15:05.000000', 0, '2024-12-03 16:15:05.000000', b'0', 0);\n\n-- ----------------------------\n-- Table structure for m_tenant_package_perm\n-- ----------------------------\nDROP TABLE IF EXISTS `m_tenant_package_perm`;\nCREATE TABLE `m_tenant_package_perm`  (\n  `id` bigint(0) NOT NULL AUTO_INCREMENT,\n  `package_id` bigint(0) NOT NULL COMMENT '租户套餐ID',\n  `perm_id` bigint(0) NOT NULL COMMENT '权限ID',\n  PRIMARY KEY (`id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 679 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '租户套餐与权限关联表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of m_tenant_package_perm\n-- ----------------------------\nINSERT INTO `m_tenant_package_perm` VALUES (609, 1676850220841287680, 1669336412647133184);\nINSERT INTO `m_tenant_package_perm` VALUES (610, 1676850220841287680, 1685986566443687936);\nINSERT INTO `m_tenant_package_perm` VALUES (611, 1676850220841287680, 1685986702834065408);\nINSERT INTO `m_tenant_package_perm` VALUES (612, 1676850220841287680, 1685986806387236864);\nINSERT INTO `m_tenant_package_perm` VALUES (613, 1676850220841287680, 1685986887102423040);\nINSERT INTO `m_tenant_package_perm` VALUES (614, 1676850220841287680, 1673167646340120576);\nINSERT INTO `m_tenant_package_perm` VALUES (615, 1676850220841287680, 1685985572129398784);\nINSERT INTO `m_tenant_package_perm` VALUES (616, 1676850220841287680, 1685985875515990016);\nINSERT INTO `m_tenant_package_perm` VALUES (617, 1676850220841287680, 1685985977349496832);\nINSERT INTO `m_tenant_package_perm` VALUES (618, 1676850220841287680, 1685986276453703680);\nINSERT INTO `m_tenant_package_perm` VALUES (619, 1676850220841287680, 1669338708936298496);\nINSERT INTO `m_tenant_package_perm` VALUES (620, 1676850220841287680, 1685996318275985408);\nINSERT INTO `m_tenant_package_perm` VALUES (621, 1676850220841287680, 1685996426530971648);\nINSERT INTO `m_tenant_package_perm` VALUES (622, 1676850220841287680, 1685996547448561664);\nINSERT INTO `m_tenant_package_perm` VALUES (623, 1676850220841287680, 1685996623386435584);\nINSERT INTO `m_tenant_package_perm` VALUES (624, 1676850220841287680, 1673524365792530432);\nINSERT INTO `m_tenant_package_perm` VALUES (625, 1676850220841287680, 1685996875623489536);\nINSERT INTO `m_tenant_package_perm` VALUES (626, 1676850220841287680, 1685997536914235392);\nINSERT INTO `m_tenant_package_perm` VALUES (627, 1676850220841287680, 1685997640341577728);\nINSERT INTO `m_tenant_package_perm` VALUES (628, 1676850220841287680, 1685997732956004352);\nINSERT INTO `m_tenant_package_perm` VALUES (629, 1676850220841287680, 1673524591861321728);\nINSERT INTO `m_tenant_package_perm` VALUES (673, 1863859488525045760, 1669336412647133184);\nINSERT INTO `m_tenant_package_perm` VALUES (674, 1863859488525045760, 1669338708936298496);\nINSERT INTO `m_tenant_package_perm` VALUES (675, 1863859488525045760, 1685985572129398784);\nINSERT INTO `m_tenant_package_perm` VALUES (676, 1863859488525045760, 1685985875515990016);\nINSERT INTO `m_tenant_package_perm` VALUES (677, 1863859488525045760, 1685985977349496832);\nINSERT INTO `m_tenant_package_perm` VALUES (678, 1863859488525045760, 1685986276453703680);\n\n-- ----------------------------\n-- Table structure for m_user\n-- ----------------------------\nDROP TABLE IF EXISTS `m_user`;\nCREATE TABLE `m_user`  (\n  `id` bigint(0) NOT NULL AUTO_INCREMENT,\n  `user_id` bigint(0) NOT NULL COMMENT '用户ID',\n  `username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户账号',\n  `password` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '密码',\n  `salt` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '盐值',\n  `nickname` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户昵称',\n  `user_real_name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户真实姓名',\n  `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户邮箱',\n  `phone` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号码',\n  `user_sex` tinyint(0) NULL DEFAULT 1 COMMENT '用户性别  0未知 1男 2女',\n  `user_avatar` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '头像base64编码',\n  `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',\n  `status` tinyint(0) NULL DEFAULT 1 COMMENT '状态  0禁用 1正常',\n  `tenant_id` bigint(0) NULL DEFAULT NULL COMMENT '租户编号',\n  `create_id` bigint(0) NULL DEFAULT 0 COMMENT '创建人ID',\n  `create_time` datetime(6) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_id` bigint(0) NULL DEFAULT 0 COMMENT '更新人ID',\n  `update_time` datetime(6) NULL DEFAULT NULL COMMENT '更新时间',\n  `deleted` bit(1) NULL DEFAULT b'0' COMMENT '逻辑删除  0未删除  1已删除',\n  `version` int(0) NULL DEFAULT 0 COMMENT '版本号',\n  PRIMARY KEY (`id`) USING BTREE,\n  UNIQUE INDEX `username_unq`(`username`) USING BTREE COMMENT '用户名唯一索引',\n  INDEX `user_id_idx`(`user_id`) USING BTREE COMMENT '用户ID索引'\n) ENGINE = InnoDB AUTO_INCREMENT = 15 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of m_user\n-- ----------------------------\nINSERT INTO `m_user` VALUES (1, 0, 'admin', 'c43fc233541ac2183e68159618167149', 'admin', '无敌的我', '小太阳', '438562332@qq.com', '15011384542', 1, 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/4gIoSUNDX1BST0ZJTEUAAQEAAAIYAAAAAAIQAABtbnRyUkdCIFhZWiAAAAAAAAAAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAAHRyWFlaAAABZAAAABRnWFlaAAABeAAAABRiWFlaAAABjAAAABRyVFJDAAABoAAAAChnVFJDAAABoAAAAChiVFJDAAABoAAAACh3dHB0AAAByAAAABRjcHJ0AAAB3AAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAFgAAAAcAHMAUgBHAEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAAAABvogAAOPUAAAOQWFlaIAAAAAAAAGKZAAC3hQAAGNpYWVogAAAAAAAAJKAAAA+EAAC2z3BhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABYWVogAAAAAAAA9tYAAQAAAADTLW1sdWMAAAAAAAAAAQAAAAxlblVTAAAAIAAAABwARwBvAG8AZwBsAGUAIABJAG4AYwAuACAAMgAwADEANv/bAEMADQkKCwoIDQsKCw4ODQ8TIBUTEhITJxweFyAuKTEwLiktLDM6Sj4zNkY3LC1AV0FGTE5SU1IyPlphWlBgSlFST//bAEMBDg4OExETJhUVJk81LTVPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT//AABEIAn8CfwMBIgACEQEDEQH/xAAbAAEAAgMBAQAAAAAAAAAAAAAAAQYDBAUCB//EAEoQAAEDAgMEBwUFBAcHBAMAAAABAgMEEQUhMRJBUYEGEyIyYXGRFEJSofAHIzNisUNy0eEkNFNUc5LBFRY1NmOC8SUmk6JFg7L/xAAaAQEAAwEBAQAAAAAAAAAAAAAAAQMEAgUG/8QAKREBAAICAgIBBAMBAAMBAAAAAAECAxEEEiExMhMiQVEFFEJhIzNScf/aAAwDAQACEQMRAD8AugAPBbQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3XtkAjcbAAEgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEOWyKtyTXe7aVTqtVWW/WBXK5cyWO2VPBOpYydp3tsIt0JMUTr9kylVo1LbjtFqgAIdgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEV3dQIAN4AAAJAAAAAAAAAAAAIXQmEMcrsrGNeViXLtLexHHItj0w5LdpQnIlPrMjkTyCsRbKi8DYRbpc1zLEq2VFIvC/BbU6ZAAVNgAAAAAAAAAAAAJQAxPnjZq668EMS1nwsL6cXLfzEKbcjHT3LaBhgmSVF3OTcZirJjtjnrZZS8XjdQAHDsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADZiYjWXtmpgjTaeiG0c3nUK7ShzEd3kMD4Vaiqi3Q2dUGhzF3MWaQNiSJHZpr4GBWq1c0VDuJiVkSgAEpAAEgAAAAAeJV7NuJ7MMqrtWud0jyqyzqrH9aDLPT0QDmWMIEt9IOZOfECLp9IemLZ1yEvxGYlNZ1LZQHli3ah6KZ9vRr5jYACEgAAAAAAYZp0iSyZu4FuPFbJbrVXe8UjcvcsjY23caUlQ990RdlvgY3OVzlc5cyM8/4nu8bg0xxu3t5Ofl2v4qcxzGYzsb4jXpk9vcT9iRF8TopmmRy8+J0KZ21EnFMjyP5PF6vD0uDk/wygA8V6YAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyQJ95pobHMw09ruM5XefKq3sS1gQDhyk8uajkzselIETMDVexWa6Hk3FRHZKmprSMVi63QurbayttvAAJdAACQAADXdbaXQ2DXXU7ozcifDz9bh6fIniNy6ljKfW4ehGYS/AB9bhxFvBSdy5L6AZYVyVDIYYu9oZiu/tuwz9oADhaAAAAQ9yNaruBNazadQi06jbDUTdW2yL2lNG91uuu89Per3qrr5nn1PpeJxoxU3+Xh8jPOS3/AA5DiNOI46mtmPrUcgnMJoSH1qbdE7JzOGZqepno1tL5oY+bTtilp4tuuSG8AD5l7oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAz0/dXzMpip0TY5mXiVW9qrewAHLkF1G8ACHN2mqikk7hHgaat2VVF3EGWoRNpF4mIvj0ugAASAAAay2uuZsmB7V2skunkd0UZ6zMPOX1Yg9bLkTS3JSLa/wACxk6yglBnxANSjIE5ntI1dnoE1rayIU7WpnPLW7Oh6KrT5bcVesAAOVgAABrVjtliNRe8bJqViLdqpoa+FETljbNyZmMctXmE8x5hND6d4ZlxG5RmNy6BBzACXCS6cTJAv3zfMx5nqNVSRFy1Ks8bpLvF84dMAHyk+30NfQADlIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANiBPu+ZlMUC/d8zLcqv7UyEEg5QgkgASR7qhVREuq2Q15JdrJNDqtZlMRKJnbb8l0PABatj0AAJAAAAAAABGixHIkE7k1CMk3EgDckRAACEgAAAAAeJGJIxWu0PYO6Wms9oRNYtGpc2WJY32XTcY7J4HUexHtVHGhLE6Jc9F0yPe4fNjJHW3t4/J4s0ntHpj3aoF0UcxzPRYhLAeoTz+QEZZntlttPM88z1Em1I1L7yrL8JWY/lDpgA+Ut8pfQ19AAOUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAz0y9hUMyGCnXNyGb0K7x5Uz7F0JQeh4c9rUX9DiImUPZjfKjMkzUxPmV2TckMZZWmncVS97nr2lIAO3cQAAJAAAAASAwy1VPD+JKxvhc1JMZpGd1XPt8KHcY7SmKWn8OiDjux1P2dOv8A3ONd+N1K91kbfVSyMFlkYLysAK07F61dJGongxDw7E6xdZ3cksdf17O4411oBVvb6v8AvD/Uj2+r/vD/AFH9ayf6tlqBVm4jWN0ndzspkbi1amfWIvgrR/WsieNdZQV9uNVSL2mRu5KhnZjqZdZAvmi3OZwWcTx7w7IOfHjFI/vK5nmhtxTwzJ91I13kpXOO0K5paPcMoAOHIeXNR7VRyXuegTWZidwiYiY1LnzQLHmmbTFxyOrrkpqy0mqx+iqe1xefE/bd5efhzvtRqW8ByJc1WrZUXmQerFotHhgmsxPkMtM1HTJ6mI3KONUar3aqZeZlimKf3LRxcc2u2QAfMvcgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAemP2HXMqztt3TABqEdYe3TOdkmSHgAERAAAkAAAGnU4jTU90V+074WnLqMZqH3SJEjb6qXVw2stphvb07skscTdqR7Wp4qaE2M0rL9WqyL4ZIcF8j5XbUj1c7xPHM0V48R7aacSP9OnNjU7l+6Y1ieqmlJVzy36yZy33Xy9DDzJLopWGmuKlfwjK448BqOP8Dp31SRkTzIzJScAmgHMBvJ3DmPAIQg3E8xuAgaak8xzCUEplnoN/wDIjcQiY22ocQqoV7EzlTguZ0IMcRcp4rfmacXfqTuK5xVt7VWwUstkFVBUNvDI1y703oZimtcrXXa5UVN6HSpMXmi7E33jOO9DPfjzHxZMnFtX4rADBTVUVSy8b0VeBnM0xNZZZrMe3lzUenaRFMTqWNdLoZwWUz5KepV2xUt7hgZSxsW65+ZnTQAi+W+T5SmuOtPiAArdAACQAAAAEANGuxfD8PRfa6qNi/AmbvRCvVnTumZdKOkfKvxPXZQ04+Llv6hE3iFvFz5vU9NMWmVepdFCn5Y7/rc0H9I8ZeqquITX/LknpY11/i8k+3E5IfVwfMqLpdi1NIiyz+0M3tkbn6oX/CMTgxWhbUwaLk5vwrwM2fh3w+ZdVtEt4AGR2AAAAAAAAAAAAAAAAAHKrMUfHI6OFtrZbS5iZiHVaTafDqi5XVxGrcv47uSIhHt9Un7d3yI7wt/r3WMXK8mJVf8Abf8A1QhcQq3Zdc5PJLfoO0H9e6xXPKvamrm+pWXSyPvtve7zU8Ed4dxxpWpHNXRbklURVTNFVDq4XWSPf1Mi7SWyVdUJi0S4vgmsbdYAEqAAAAAAAAAGrV10NKnbdd25qHDrMSnqbpfYj+FC7HhtZbjw2u69XisEF2tXrH8G6IcapxGoqLo5+yz4W5GoPU10xVq3Y+PWooyJ9QWtEeEDjoPUbtCQBOd9CPUByAJAgchvADkE3kkAASQACkgANw9SPUCUIzCX8SdwBPIBL8FI9QAJI4geo3ujejmOVrk3odmgxdHIkdVk7c7icTO4K744tHlTkw1v7XNFRUugK5QYm+mVGSdqLx3FghlZNGkkbtpqmHJimjzsmK2OfL2ACpWAAAAAABSOk3Sx6PfRYY+yJ2XzJqvgn8S/j8e+a2qubW07+MdI6DCkVkj+sn3RM158Ck4n0txKvuyKT2aJfdj1XzU4bnOcquc5yuVbqq3I3f8Ak9/BwMeOPMblRa8ylVuquc67lzup5JzGf1c3RWIjw4PQbhYepIFs+z+rVlfNSqvYkbtInihU8/q53OhiqnSKD9136GXl1i2G0Oqz5fTwAfKNIAAkAAAAAAAAAAAAAQuilXlzlev5lLTuKzUNVlTInBynN/TVxp8yxbtRkAUthkOYS/gPQBzA9LDMBfxN7CG7VXf4UNFMuB2MFitE+RfeWyHdI8qc9tUdMAFrzgABIAYKqqjpY1fIvkm9SYiZnwREzOoZXvaxiueqI1NVONXYxe7KTTe80q2vlq39pdmNNGoam7U2YsER5lvw8aI82enOV6qr1Vzl1VVPIBpa4iI9IJyJIJSEfInmAAsgJ3eAAjIZggMhdE/8mSKnmlt1cT3eSG1HhFY/VjWJ+ZTmb1j8q5yUr7lo6jI7DcCf787UXwb/ADMzcDhTvSSL8jic1Fc8mjg/Wo3fzLEmDUm/rF/7j2mE0X9kv+df4nP9irj+1RWsvpRkWRcIo10jc3yep5dgtIqZdYn/AHiORVP9qquZE5HcfgcfuTPTzS5gfgcyIuxKx3ndDqM1HVeTRysgbkuG1cf7La8W5mq5r2LZ7VavBTuLVn0tjJW3p5yJIJO3QAQEp3kAnmBGQyAAZcDZo62Skk2o1u3e2+prXBFqxaPLm1YtGpWulqo6qJHRr5pwNgqNPPJTybcbrKnzLHQ10dXHktnpq25hy4Zr5h5ubBNJ8NsAGdQAADhdMMQfQYK/q1s+Zerb/qfMfrU+ldNaJ9VgrnsRdqB23yPmuZ9F/FxX6Xhnye0egyG8lND01aMvAZE53HqBGXD5gnO4z+rgRkd7oUl+kUOXuuX5HC9SydA2bWOKqp3Y1Uz8vxhsmvt9HAB8k1AACQAAAAAAAAAAAAAK/ikexWu/NmWA5WNRXSOVE0yUiY8LsFtWcnKwyHHUeZQ9EsRlkTl4j1CDiMhxAEtbtORqJmuSFlpokhp2RpuQ4+EwdZUbap2WZ8zul1I1DFyb7nQADpmADRxGvSlZZtlkXROB1Ws2nUJrWbTqHqvr46RnxPXRtyuzzvqJVfKt1PEkj5ZFke5XOXep59T0MeKKQ9LDgikeTKykcT16kcS1oMggt4AITkRlncnI85WCU5AlEutkQ3qXCqieyuTq2/m1OZtFY8uL3rX20NxsU9HUVC/dxLZd6pZDvU2GU0GeztuTe7M3E8DNfk//ACyX5X/y48GBprPLyb/E34cPpYLbELVXiuam0Ci2W0s1st7ShEsmRIBXM7cAAIcgAAAAJAAAPD42SNVJGtcnih7BMTMJ250+D00iKrEWNfy6HOqMHqYrqxGyN8NSxAtrntVZXPeqnOarHWcio5NynnncttRSw1DbSxo7x3ocirwZ8aK+ncr0T3d5ppnrZrx8ms/JyRxPTmuY6z2q1d9zxbXIv9tUTEx4SlhzFs9BZPEmEmQTfYEcQh63nqKR0L0fGtnJoeMhkQTET4lZsOrm1cdl7MiaobpT4pHRSJJGuy5N5ZMPrm1cdlykb3kMWXDr7oebnwTWdw3AAZmd5ljbLE6ORLtcllRT5LjmHPwzFJqZydm+0xeLdx9cK100whK7D/aYmXmgS+XvN3oeh/H5/p5NT6lXeu42+c2+rEZcCeQyPpY8+mcyFkIy4ACchZLEE2QAW37PGItfVPt3WIl+ZUsi8fZ3HaCsktq5EMXOtrDLuntcwAfLtIAAAAAAAAAAAAAAAAa9bF1tK9u+10NggFZ1O1V43Gm82K6FYKp7dyrdDXzKJjy9Slu0RIigAh2X1zCdpbINxvYVTLNP1ju6zPzU6rG5cZLxWu3UoIOopWtXvLmpsgFzy5nc7kAMFVUMpoVkevknFSYiZnREb8QxYhWspYlst3u0QrUkj5Xq97lVy8T3UTvqJXSSLmu65i5noYscVh6eDDFI2ccyUIJTzLmhBB6IuA5jIcz3FHJNIjI2q5ynKJmKx5eDco8Nmqu1bYj+JTp0OExxWfUdt/Dch00S2SGfJn14qxZeT+KtWkw+CmTst2n73ONsAyWtNp8sc2mZ8gAOUAAAAAAAAAAAAAAAAgAASAAAAAMFTSQ1LbSsRV3LvODW4ZLTXe3tx8UTNCykF1MtqrceW1FORSOZ3q/CWyXkp+y/VW7lOG9j2PVj2q1yblNuPJF48PQx5q3h55gjkTu8CxcAWUADJDM+CRHxrZUPBBz/APqJiJjUrVQVjKuHabk5O8hslTpamSlmSRi+acSz007KiFska3RfkYc2LrO3l58XSWUhURUVF0XUkFMKHzLpbgy4ZiKyxN/o0y3avBd6HAy+rH2HE8PgxKifTVDcnJk7ei8T5VidBNhtbJSzou01clz7SblPo+ByoyV629wz3rqWpl9WIv8AWRPqPU9FwEZEgCD6N0Ci6vBHSW/EkVfQ+dep9W6MQdRgFKxdVZtLzPM/k76xaWYvbqgA+daAAAAAAAAAAAAAAAAAAAc7F4NuFJWpm3XyOLlwLS9iPYrV0XJSuVUK087o1vZM08iu8NnGv/lh8hu0GXiNckuVtT3HGsr2xsbm5SxUsCU8CRt3a+Zq4ZRdSzrZE+8dp4IdAurXTBmydp0AA6UPLnI1quctkTVSs4jVrVT8GNyan+pv41Wq3+jRqt1zf/A4qaKbePi1HaW7jYv9Sn0AINTaDcABCEpoNToYdhrqhyPlRWxJ/wDY5taKx5V3yRSNyw0VFLVvRGpZiauLFSUkVLHsxtz3u4mWNjY2IyNuy1NEPRgyZptLzsua15AAUqQAAAAAAAAAAAAAAAAAhzkY1XOWyIl1UmEK50k6VMweX2eCJJqhUut1s1pWHdOsYV10bTNTgjDjYzVOr8WqKjvbT12fJDSVqtWzm2Xgenjw0isbZ7Wna5UHT+oSRG4hTRvYvvRZKnJS908zKmnZNEt2Pajmr4HxE+vdGZOs6P0bv+miFHJxVrG4WY7TLqAAxLQAAAAANOuoI6tueUiaONwHVbTWfCa2ms7hUqinkp5NiVtl48TCWyrpYqqLYenku9CtVdLJSyqx6X4O4obsWWLPRw54t4lgJCAvaUWJ4ggCcuBuYbWLSy2X8N2qX0NIHNqxaNOL1i0alcWuRzUc1boulj0cTBa2y+zSu17qr+h2zzslJpZ5V6TSdBW+meD+30HtULPv4EvkneTgWQhURUsuh1gyziv2hVau4fFCfQ73S7CFw3Elkib/AEebtNXgu9Dgn1eHJGSkWhlmNScyCbrnmCwZaWFZ6qKFqZvcjT7FTxpDTxxp7jUafPug+GrVYmtU9F6qnz83bj6KfP8A8nli14pH4XYq+AAHlLgAAAAAAAAAAAAAAAAAADUr6NKqPLJ6d1TbATW01lW/Y6lHqzqX38sjpUGHdUvWT9p+5NyHSBEViFts9rRoABKkMFZUJTU75Happ5mc4GN1SyTdS1eyzXxUtxU7WWYqd7ac171ke57lzdmpCWIJPRiNQ9aI14RckjmSSlGXiECHQwugdUyJJIn3Tfmc2tFY3Li94pG5e8Lw3rlSWVLMTRPiO+1Ea1ERLImQa1rWo1qWRNLEnn5Mk3l5eTJN53IACpWFAxfptXw4jPBRJAkUb1a1ysuq/MuGN1raDCampdq1i281Pjyo+RznWVy3uq2NvFxxMbspyWlYm9OMZat1WB3nH/BSx9GelsuLVqUVTTsY9WqqPYuS8j5xzLB0G/5kh/ccaMuGnWZ04radvqQAPKaQAAAAAAAAAADmdJKn2TAquVFs5I7JnvU6ZVvtAn6vBGxXzlkT5FmGu7w5t6cPoBh7KrEJauZm0kKdm+m0p0ftFjjbQ0r0Y1Hq9UuieBl+zmNEwuok3uksYftI/q1El8tt36GyLTObSvX2qF6n1ToVKknRqn17F2+h8s5qfSPs+lV2BvYq9yRSzlR9jnH7WkAHltAAAAAAAAAYKqmjqolY9E8F3opnBMTMTsiZidwqVVTyU0yxycltqYUsWmvo21cCt99O6pWJGPikcxydpuSnoYckWh6eDL3jUoIyJBc0IFwpPMA1ytVFRc008CzYZWJVU6bS9tuTk4lYNmgqlpahHp3VyXyKs2OL1Z8+PtG4WoENcj2I5q3R2dyTzp8eHm+mjjGGw4rQSU0trqnYdbuu4nymuo5qCqfTVLNh7F0tqnE+yHLxrA6TGIbTN2JW9yVveT+R6HB5n0p629Kr036fKMvAy0tNLV1DKeBm1I9bIiJoWV/QXEeuVGVNOsd+8quTLysWfAOjtNg7Nu6y1CpnI7d5cD0838hjrTdfMqq4535bWCYZHheGx0zLK5Eu93xOOgAfPXvN7TazRHgABwkAAAAAAAAAAAAAAAAAAAAAAAAAAQw1c3s9M+RfdTIqb3K5yuXVVup2MenXsQIv5lsca5vwU6129Hi01GwWRdxJGRoaz0FtSQjVc5GtTNdCCfTPR0rqqdI25Imq8ELRDGyGJsbEs1qZGvh1IlLTonvuzcptmDNk7Tp5WbL3sAAoUgACFO+0Su6uhgo2rnK7bcnghv8AQ3C46TBGSPjaslSm066Xy4FT6bVPtXSJYUW6Q2j5rqfR6KNIqKCNNGsRPkbbbpjiFdfM7fMOmUEFP0gmZTMbG2yKqNTK5k6D3/3kh07rv0NXpTKs3SGscu6S3obXQZP/AHJDl7jjTO/pK/8AT6kADyWmAAAAAAAAAAACifaRMqvo4fN3+hez5z9ob74vCy/div8AM0cWPvV5PTpdAK6mhwuojmniiVsl+29G5czndO8Wpa+aCCjmbM2K6uc1bpcqfIjjkb4wxF+ynt40cS+/ZvLenrI76OR3yKFyLh9nMmziFTFfvRotuZHIjeOU0ny+hAA8lpAAAAAAAAAAAOVjNF1rOvjTtt1smp1SFS6WXRTul+s7dUvNZ2puXAWyN3E6X2apWydh+aGluPSrbtG3rUt2ruAW8EAOnZbyFvIkgId3A6raYtO9c25t8jrFRp5lgnbI3Vq3VC1wyJLE2RujkuYM9Os7edyMfW23sAGdlAAEgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQq2RVXcSaeJzdTRSKi5uSyHdK9rJrHaYhX62b2irfJ42Q1+Z6I4np18Rp7FK9Y0ZcRzJz4gl0jLyOrgdL1kizuTst08zmRxrI9rW6uWyFrpIW09O2Nu5MyjNfrGmXk5NV6wzAA895wAAB5kekcbnro1FU9HPx6f2bBKuVFzSNU9TukblE+nymrqeuxaSqdmiy7XzPqMPSDCfYmy+302TLq3rERyZaW1Pke4jPienbFF4hni2mxiFR7VXVFQmkkiuTyO10Fz6SR/uO/QrvMsnQPPpGzwjcdZPGOUVny+nAA8dqgAAAAAAAAAAA+ZdPnqvSJWqvdiafTT5h08W/SWTP9m01cT5q8vpXB6jmSemoRdCydApNjpA1ufbjchXDtdDpFj6S0mfeVW+qFeWPslNfb6uADxmoAAAAAAAAAAAAAamJUyVNK5tu03NCsLdFVFLkVrFqdYKtVROzJmhr42T/LZxcnnq0RkCczY3o5kgcwIy4ncwKpux0DnZtzTyOJzMtJOtPUMkTRFz8irJXtXSnNTtRbQQ1yOajk0VLknnT4eUAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4mPSoskcSLpmp2yq4hL11ZK7gtjRx67tto41d321uRPoQSb3poJsAiXUgdPAqfbmdM5Mm6eZ3zVw6DqKRjd6pdTaPOy27WeRlv2sAAqVgAAFd6czdV0clamr3NaWIp/2iSImG08d+9Je3Itwf+yHN/T57wF9RyHHI9dmMyydAs+kbf8JxW+RZegP/ADEn+E4rzfCU19vpoAPHagAAAAAAAAAAD5b04dfpJN4NanyPqR8p6Yqq9Jarl+hr4nyVZPTh7kJ9BqRyPSUJOn0adsdIKJ3/AFEOXyN7A7/7ao1/6qHF/jKa+32QAHjT7a4AAQAAAAAAAAAAAHOxmHraNXNS6sW50Ty9qPY5q6KljvHbrbbqk9bbU7TcN57mjWOZ0aovZVUPB6cTuNvYpO42cidxAJSegG8JpvIFjwafraNGuXtMyXyOgV3BJurq9hVyflzLEefmrqzyc1et5AAUqkKtkuuhoTY5hsD1ZJVx7Sao27rFd6VYpO6rdRRPVsTE7VstrwK56nqYOB3r2supi3G30WHGsNndaOsiVV3OXZN5rkc1Fa5HIu9D5YZ6atqqV16eokjXgjsvQsv/ABsf5lM4f0+nApdD0sqolRtYxszOKZO/gWfD8Vo8QZenlTa3sXvJyMGXi5MfuFU0mrdABmcgAAAAAAeJJo4m7Ur2sbxVxOtkRMvYOVUdIcPgujZFldwYl/mcybpW9bpT0yJ4vU6ikrq8bJb1C0Apj+kuIu7qxt8mExdJa9i/edW9OCssT9Nb/Sy6XIHNwnFosSjWybErdWHSOJjTNas1nVgAEOWKpf1dNI/4UVSpKt3Kq5qualixqTYoVb8S2K4buPX7dt/Dr4mUkZAncaWxBtYbD19YxNyZqavjc7mAw2jkmVNVshVlt1rtTnt1o66AA8728oABAAAAUf7SHJsUbfFyl4KD9pCr7XRJdbbDl+Zo43zhXf0pe5BxF/EHqqAs3QD/AJh//U4rOu8tP2etVcbevCJSrN8JTX3D6SADx2oAAAAAAAAAAA+TdL1v0kq9e8n6H1k+S9LL/wC8dZn7xr4nylVk9OPuQZWCeZO5cz0lKDdwb/jNJ/it/U008zcwb/jFJn+1acX+JV9l3IAmgPHn21QAA5SAAAAAAAAAAAAAK5jUexXOW2T0uaB2sfZ2IpOCqhxD0sU7pD1OPbdIMgAWrwehOYCHqGTqpmSIvdW5bo3I+NHJo5LoU7OxZsJl62gYvDIy8ivjbHyqf6boAMcMD5zjb9vGKldF27GhkbuMf8Vqf8RTS3n0+L4Q3U+IALFjsS2pLHOjcjmOVqot0VFzI+rAaiY1KJjayYT0pliVIsQvJHp1id5PPiWulqYaqFJaeRsjF0VFPmBt4fiNTh8vWU71t7zV0U8/kcGtvNPam+L9PpQOZhGMwYnHZvYmanaYv+h0zxr47UnrZmmJiQ0MQxekoUVJX3k3MTX+RqY/iy0bEggX756Zr8KcSnvc57lc9yuVy3VV3nVaftt43E+pHa3p16zpHWT3bBswM8Eu5eZyZZZJnq+WR0jl3uzPNiN5bEQ9SmClPUFwSMyVxu1IvktwTn4gdTo29W4vHZdUVFLuUXAFVMXg81L0U5PbxedH/kAAVMTiY+/OKO+eanHOjjb9uu2fhahz7LbeelhjVIepx41SEAE552LV6EsWrDouqoo223XUrMDOsnYzi5C3NSzEb4GXk28aYeXb1CQAYmIAAAAACgfaP/XqL/DX9S/lB+0hP6XRORMthyfM0cX5q8npTBx0I4Djkeqzhbvs6T/1WoXhF/qVHkXP7OGf0uqkzyYiFOefsl1X2v4APIagAAAAAAAAAAD5N0u/5kq/3j6yfKumbFZ0lqctbL8jZw/lKrL6cLeTuUciD0VIb+AtV2N0bU161DQ5Fg6E0i1HSCKSy7MKK9VOMk6rKaxuX1IAHjS1AAIAAAAAAAAAAAAABoYyzaoHL8K3K4hacQbtUMyb1aVVOZu40/a38SftkyJIJ46mlsQNxNgBGR2sAkuksd9LKcbcup0MEk2a1G52clirLG6KORXdJWIAHnPKfOceRExipT85oeh0+kjdnG6jXNbnMt5n0+Dzjhup8QDjqM/EsdgAAAeosoHuCaSnlSWF6se1clQvmA4wzEoNl9m1DE7TU3+JQNDYoqqWiqEmhWzkSxm5HGjLX/qrJSLem1iEzqitlkfmquVORrZZ5GxXROhrZo3Jmj1Q1zxnsYtdI0cibJwBHIlYm3gOORGQAnLgRlwHmMuBA6OAp/6vBlvL2Ubo8iLi8PMvJVkeLzv/AGAAK4YVWxN23XyqnGxqmWrdtVUq31cpiQ9OnisPZxfCDkTu0UjIZWOnbdwePbr2flzLMcHAERamR3Bp3jDyJ+55nJndwAGdnAAAAAApf2jwq6mpJbZNcqF0OZ0gwxMVwmWlblIqbUa/mQtw3it9ubxuHyEjjqZ6uknop1hqonRyNWyoqGA9aLQza8nJS+fZvEvU1kv5kaUPI+mdAqZYMB6xyWWZ6u5FHIt9jukeVmAB5bQAAAAAAAAAAAfN/tBp1jxqOe3ZljT5H0g16qipa1rUq6eKZG6bbUWxdhyfTttzau3xXgTuU+wpgeFJ/wDjqb/40MrMLw+NUVlDTIqafdNNX9yv6VfTfJKDC63EpEZR08kl96JZqc9D6Z0ZwFmCUateqPqJM5HJ+h2UajURESyJoSUZeTN/DutNAAMywAAAAAAAAAAAAAAABiqUvTyJ+VSo28y4S/hO/dUqDu87zNnG9S28P8o9R6k5EZGttMvEnLh8iMicrbgBnoHbFZEv5jXy8D3EuzKxU3OQ5tHhzeN1lcAQ1btRfAk8yfbx59qJ0sjVuNOd8TWqcW3gWHpm22JRuRO8wrx9HxZ/8UNmP4g5DLwBesEGXAJYZALC3kMs1uPQByN/BsOfiVX1TVVGoiq5f0NFrVc5GtS7nZIh9A6P4YmHUKI5PvZO09bfIy8rP9Kvj2py36xpyOlVCrJ21bE7LsnZbyv8z6LV0zKumfDKnZchQq6kkoql0MiLlovFOJ4tLbht4WeJr0n21srakqCOOZ29AGXEXsm4lPMBzIy4gAdjowxHYq1eDFUuhVeiMd6meS2jbFqKMnt4fNneQIJIXQ4qywqEv40mfvLu8Twe5VXrX5+8v6nk9Svp69PjACMvAcTp07XR5MpneR2TkYAn3Mq+J1zzc3mzys3zkABUqAAAAAAAAeJYYpm7M0bJG8HNRxrphtAi3Sip/wD4k/gbYOu9kahijpoI/wAKCJn7rET9DKgAm0z7NQAA5SAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAh4l/Cd5KVF3eXzLZU5U8i/lUqP1qbONHiW7ifkJy4/qQLmtuTzCaEcyQhBJHMghFvS4wLeCNeLUPZho12qSNfyoZjzLe3j29yqnTZmdNJuzaVXQunTKPaw5j7XVkhS+J7vAtvFDVhn7QBPAX8TYtAOYuAv4hBkdvo7gzq+ZJ5ktTsXNfiXgV5Mtcde1nFrdYb3RXB1VyV1S3JPw0Xf4lsIY1rWo1rUa1EsiISfPZ805b7ljtbtIc7F8MZiFOrckkbm11jogpidTspaazuHzieGSnmdHMmy9q5oY/FFLvjOEx18W02zZmp2XcfApk8MkEzo5Wq17eJorbb3ONyYyV1PtjHMgevqdNSeZGXEJ5fMfWoFv6Jw7OHyPt33ndOfgUXVYVCm9Uv6nQM958vnc1u2SZAAc1VqfL+NJn7y/qeeZlrG7NVKnBymE9SvqHsY/jCeZG5cwOR06dzo+v3MiePE65xej7spm+SnaPNzfJ5Wf5yAAqUgAAAAAAAAAAAAAAAAAAAAnSAADQAAgAAAAASAAAAAAAAAAAAAAAAAAAAANeuds0Uy8GlV0QsmMP2aB+euRWvU3caPtb+HH2yXJ5j61INLYkcyLDkoE8xkQmn8x4BE+lrw/8AqUX7psGvQf1OL902Dy7+3j39uZ0hh63Bp03tTaQ+e8/mfUZ4kmgkjXR7VQ+ZVEL4KiSJ6ZscqKer/HXjU1W4LMaefzGfED19T1Gk5/qOZ6jY+RyMjY5zl0RLqWfBui7lVs+IXRuqRXz5lOXPTFHlXbJFXPwPApcQcks20ymTVd7vIvMMUcELYom7LGpZEPTGtYxGsajWolkRCTwuRyLZbefTLa3aQAGZyAAAczGMJjxCLaREbM1Oy7j4KdME1nTqt5rPaHzeeGSnldHK1WvauaKY8rl5xnCWYhFtMs2dqdl3HwUpc8EtNKsczVY9F0U0Vt2h7XG5MZI1Ptj8z1EzrJWM+JUQ8nQwKBZ8UiREyYu0pK7LaK0mV3gZ1cLGJuaiGQAyz7fPT5kAAQrGKt2K+RNLrc07nTxxmzVtfuc05h6eOd0h62Cd0hJGVtR5Ena11MAciVMjV3tO8VnCZFZXx55OyLMYeRGrPM5NdXAAZ2cAAAAAAAABDnI1qucqIiaqqnLqsajju2BvWLxXJDumO1vTqlJt6dUwy1UEP4srW+FyuT4hVT325FRvBuSGrzNFeN+2qnFmfaxSYzSMWzdp3khrvx34IP8AM44uZJdGCsLo4tIdJ+N1Lu62NvK5idi1Yv7VrfJqGjnxGdzr6df07jBSPw2v9o1iot6h3I8+3Vf95k/zGDgRu1Jilf06+lT9Nhayq/vMv+dQlbVf3mT/ADKa/MknrU+nT9NlMQrE0qH87KekxStT9v6ohp+hO4jpU+lT9N9uM1aLmrF82mVmOTe/FGvktv4nLz4gicVJ/DmcFP07ceOxrbbhcni1bmePGKN+rnM/eaVwbtTicFVc8ak+ltiqoJfw5WO8lMyLcppliqqiL8OZzfC+XoVTxv0qtxJ/C2g4EONTNskzGvTimSnSp8Tpp7Ij9h3B2RTbDaqi2G9W6AlrAqVAAAAAAAAAAAAAIcrHn2pmMv3nHBv5HVx6W9QyNPdS5yz0cEapD1ONGqIBJHEuaAncB5WCEZaXGQTzPTM1RL6qRPpFvS2UqWpo0/KhlPMabMbU4Ih6PLtPmXjW9hzsRwWixHtTM2ZPjZkv8zogml5pO6yRMwqcnQ120vU1ibP5mZmWDodE116ire5ODGI3+JZwaJ5mXWtu/qWalBhtJQNtSwtau92rl5m2AZrXtbzZx7AAcgAAAAAAAAadfhtPXs2Zm9pNHJqhuAmJmE1tNZ3CpzdFqlH/AHE7HMvq/JTtYRhLMNiW7tuV2rjpA6nJMrr8i946zIADhQAADk49FeCN9u6tjh7i04hF11FK22drlVN/Htur0OJbddAJ9RxNDU9Qv6uZj01RULcx21Gjk3oU71LPhcvW0LFvmiWUy8mNxtj5Vfy3AAYmAAASAAAeXuRjHOVbI1LqejQxl6tw91lXtKiHdK7smkdp049fXyVb1ROzFfJvHxNPiCbHp1rFY8PXx0iseEbydxBBLtNr7gPUbl1CAch6gJORJBKAQEtwJ3gCPQnduIJAjkTu0HJR6hCORK6EepO5QIt4DIn1ICTkAnMbl1CHWweuekqU8rlVq91V3HdKhTqrZ41T4kLemhh5FdTt53JpFbAAMzMAAAAAAAAAGOZ/VQveuVkUmsbTVW8Sk62ukcm7JFNTdoenuVz1cqa5nk9Ssah6+OuqxAOJPqPU6doJyAAjLgZaZNupjS2rkMRt4Wzaro/BbnNp+2VeSdVlZ0yTIkA8uXkyAAhAAAAAAAAAAAAAAAAAAAAAAAAAAAIVLpZd5VK2LqauSPxuhbDi49B3J2p+VTRx7anTRx79bOOQMwbnpF/E62Az7Mj4VXvZocnmZaeVYJ2SNXNq5nF69q6VZa9qaW4HmNySRtemipdD0ebManTyp8SAAgAAANHF41koHonu2U3jy9qPY5qpkqWO6W1ZNZ62U4jcZqiJYJ3xqndWxi3anpxO3s0nddnjcZAnmSIJ3DUjOygSRzHMkCOBJFwAy4k7hvAScyNwzMscMsncYq+IRMxDFzJRTbbh0i957W/qZm4azV0jvkHE5aubck6iYfCmrnLzPSUNOiL2VXzX+YczmhyAdlKSn/s28wtJT/2acgfXhxr+IyOo+ghcnYVWqc2RixSOY7VA6rkizLQx9bWRNTjctiZIcPAYFWR8zkybkh3DDyLbtpg5Nu1gAGZnAAAAAAAADnY1N1dGrEXN62OiV7G5tuqSPcxC7BXtZbgp2u5xAJPResAjysTzCEb9QCQIyOpgTNqqe74W8Dl8zu4BHaB7195bFOadUlTyZ1R1gAec8sAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBVwe0Uz413pkZwTE6nZE6namuarHK1yZtWxHHI6mN03VzpM1Oy/JfM5Z6dLdo29bFfvU5Ekb/Eep0sd7BKnbhWB3eZmnih1SpUlQtNO2RNy5+Ra4npIxHt0cl0MGenW23m58fW23oAFDOAAAAAhx8cpVVEqGJe2T7HE3LkXF7EkY5r0uipZSrV1KtJUOjz2VzavgbePk3Gm/i5fHWWBPIakE8jU2I9CSOXyHHICSLeBPqABG5ckG4lrXPciNS7gIQzw0kk1lRuy34lNymoWs7cqbTvPJDcQKL5f01oaKKLNU2ncVNlNMsiSAom0ylONxcgLoHITzGfgQAG4D0JEOXZarl0Q4r1WeddnVy2Q3cRn/AGTf+4zYJR7SrUSJknd/iV3v1ja3f06bl1aKnSnpmxpqiZ+ZnAPNmd+WCZ3IADkAAAAAAAAeJHpHG5yrkiXKlNIssz5F1ctzvY3P1dMkbVzf+hXvU28amo7N3Ep47J36Dd4EDcam0JXQgeoQWFiSOY2kQtOGRdXQxpvVLlZp41lnYxEzcqJoW5ibLEam5LGXk28aYeVb8PQAMTEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAw1UDamB0bt+ngVWWN0Ujo3pm1bFwORjVGr2+0Rpdze9behp4+TrOpaOPk6zqXDAJupuekhNx2cErES9NIviz+BxrnprlY5HMWzkzRSu9ItGleWneulxBqYdVpVQIt+23JyG2edavWdPKtXrOpAAcoAAANTEKRtXCrffTNqm2CaWms7TW01nanPYsb3MelnIuaEHfxag69izRJ941M04nAXeinpY7xeHqYcsZKouCeA3Fi5Fh9aC4RQJaiuciNS6nWpKVIGbTs3r8jDh9OiN61yZrobwZcuTzpPJRxG5CAoPr6yCEEpcByG4JcbgAAJAw1MzYY9pdV0QySSJGxXuXJDkSSSVU+V1vk1CJ8LMdPzL3SQPrKnZRdVu5fAtEbGxxoxqWRqWNbDqRtJAie+7Nym2efmydp0z5snadQAAoUgAAAAAAAABp4lUez0jl952SHVa7nRWO0uJilR7RWOVF7LeymZpkkbj0qx1jT2MdetdFkAuSduwjcpOhG7QBlxBNyMzkdDBYkkrEd8CXLGczAotmmdIqZvU6Zgz23Z5We3a0gAKVIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQqXRUXRSQBW8Uolpptpifdu08FNAt88LJ4XRyJdFKxW0r6WZWuTs7l4ob8GXtGpehx80WjrZr8xkShF8jQ1tiiqnUs6SN03pfVCzwSsmjbJGt0VCoaG9hlc6lk2HL927VOBnzYu3mGTkYd/dCyghrmvajmrdqpdCTDrXhgAAQgAAA42K4de9RA3P3mpv8Tsg7x3ms+HeO80ncKaQdrFMMVbz0yeLmp/ocVd6KejS8Wh6ePLF4Et4Eol3IhGfIm9ludrJdyNqNjaiJuPW7Qw0sqSwoqLmmSmbmGG3sQchv1J3BwchyI45jmSHIeSE8yOakJTyPD5GRsV71siHiepjhat17XBDlTzvnddy5bkJWUx7eqmodO/8m5Dr4RQdU3r5k7bk7KcEMOEYdtKlROmSd1FO35GPNl/EOM2X/FQAGNkAAAAAAAAAAAK5jFV11T1bV7MeXM7GJVPs1Kq3Tbdk0rHae7iqqa+PT/TXxse57ShLAlUztYGx6EICEkASQpO8bgPJ6Y1XvRqJm7LQG/g0Cy1iPVMo8zi89a7V5Lda7d+niSKBjE91EMgB5kzuXkTO5AAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGvV0rKqFWOTPcvA2ATFpidpiZidwqNRTvppVjkTRfUxcci1VlJHVRK16WVNF4FbqaaSmkVkieS8T0MeWLQ9HBni3iWG3gMuBJBc0unhWIezr1Uy/drovA77VRzbot0Upvkp08LxLqLQzOXq10Xe0y5sO/MMWfB/qqwAhrmuaitW6LmhJj1phAAQAAAHMxDCknvLAiNk3pucdMHdLzSdwmtprO4U+WJ8L9iRmy5DxyLdNTxVDdmViOTicufBEW608tvyuQ105FZ9t1OTE/Jx4pXxP2mLZUN+LEUtaVll4oYpcMq4s+qV3i2ymq6N7L7bHN80L4vErZnHf8uu2sgX9oieeR6SeFdJWepxSLeR1tH0q/t3OvhT9oz1PDquBP2iL5Jc43yPbIZX9yN7vJCJtCPpUj8ug/EWJ3WOd5ms+unkTLsovA9w4VVy6s2E4uVDfgwNjc55Ff4NyQrtlpVzNsVHGjjkmfssY57uB2sPwhGWkqbK7czgdGCnigZsxMa1PAyma+ebemfJyJt4qIltAAZmcAAAAAAAAAAAhckuSc3GKxIIeqjXtv+SHdK9p06rWbTqHMxSq9oqbNvsMyQhsHs1Is8idt+TE4eJ7wuiWok6yRLRs3/EY8TqUmqLN7keSG+s6nVW6vuKVaY5AFrUZjkSMgIXyPcUSyK7cjUuq+B51OhURJR4cxi/iTLd3kc7iJ0rvfWoj8ufyLFg0HVUe0qdp+anCpIFqKpkSaKuZbGtRrUamiJYz8i+o6s3Kyf5SADExAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABhqaaOpiVkiXTcu9DMCYmYnwRMxO4VatopaR67SXYujkNUuMkbJGK2Ru01dynCr8JfEqyU6bTN7d6G3Fni3iW/DyInxZywTyPOWeSGhrjy6GG4i+mVGSdqP8A/ksMcjJWI9i7SLopTzZoq2Wkf2c2b2XM+XD28wy5uPvzVaga1JWw1TLxus7e1dUNkxzWY9sExMSAA5QAAAAABCoi6kgnaWN0ELlu6Ji+bUPPstP/AGEX+RDMDrvb9naf2xshhZ3Y2N8mohkS3AAibTKO0gAOQAAAAAAAAAAAAAACF0zAxzysghdI9cmldYyXEq1V+JbqvwoZsSq3VlQkEWbGrZPFTqUlPHQUiueudrvU11j6df8ArTWPp1/7LDiErKGibBCmbksn8TgGesqXVVQ6RdNGp4GuluBfirqNtmCnWu59pIHIz0lK+ql2G5IneXgWTOllrRWNywkGWpZGyocyJbtatrqeYYlmkbGxLqpG/GztHXs3sJpFnm6x6dhmfmp4xWbr6xbd1nZQ7D0bh+HORvut9VK7Ex1ROjES7nrmU1tuZsy0t3tN5/DsYDT7LHTu1dknkdcxwxpFE2NiZNSxkMmS3adseS3a2wAFbgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc+uwuOou+OzJOPE4NRTS079mVip47i3HiSJkzFZIxHNXiaMeea+JaMXImnhTwderwZyXfTLdPgccp7HxvVsjVa5N1jZW9bem7Hmpf0Rvcx6OY5WuTgdejxlURGVSXT40/gcYC9It7MmKt/a3wzRztR0T0c3wUyFPjkfE7bjerXcUOjT43KxLTtSROKZKZb8aY+LHfjWj4u+DRhxall1fsLwcbjHskbdjmuTwzKJpaPbPNLR7egAc6cgAIAAAAAEAACQAAAAAAAAAAAAAOTi9dsJ7NF33a2/Q2MSrm0sey1byu7qcDRwuhdNJ7VUZpe6X3+Jox1iI7Sux1iI7WZ8Ioepb18qdtdPBDXxmtSRVp4l7Kd43cVrkpourYv3j9PAri3W91zVbl2Kvae0tGGk3nvJ9aBEyJ1y3/AKmxLRyRJExUvLJo3gX701WvFfEsMMT5pWxsS6qdyViUFAsUKXkcma2+ZloKJtHDtOS8rjSxapRqOhaqdY78ReCcCjv3vqGO2Sct9Q5HHW53sFo+qj6+RO2/S/A0cJolqZesf+Gz5qWJVRjFXREQjNk19sGfJ/iHFx6fNkDVy7zicCpc3VDk8Gmg9XV1flmr3WTwQssEaRRNjamSJY5vbpTq5vbpjisMgAMjKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYp6eKoZsSsRyceBlBMTMeiJmPTh1OCvbd1M7bT4XanLkifE7ZkY5rvFC4HiWKOZqtlY1yeJopyJj204+TavtUAdyowWJ11gerF4LmhzZ8PqoLq6Laam9uZprlrZrpnpZq+RLXOat2KrV4otjz5oPrU71tZqtmzHX1bO7UO55mwzGKtveRjvNpzuQtkczSsuZw0n8Ou3HZPfgavktjKzHY178D08lOHZOA3Ll8zn6FHE8aixsxmjdkrnN82qbMdZTy9yZq+F8yp2+rknE8asq7cSPwuQKpBW1MC/dyutwXNDr0WLtmVI502Hr7yaKU3wWqz3496uoAgM6gAAAAAAAAAAA1K6tZSR37z17rbnmvr2Urdhvamdo3+JrUmHPmk9pre05c0aXUpERuy2tY92a9DRS1sy1NTfYVb8LnUrKmOjp771ya091NRHSwq9+7JE4qalLSvnl9qrEu5e4xdGod9u3mfRNu3mfTiVKzPlV86ORzs8zDxzN/GXtfXORLdlLE4XQe1PV8ifdtX1NXeIrtujJ1xxMtjB6C6pUTJl7qKddYI1mSZW9tEsi3PbURqIiJZEMNXUspYXSOXyTiY7Xte3hgte2SzDiVYlLFZF+8d3U4HBpqeSsqNlN+bnBzpq6p02nOXJL6FioKRlJCjU7y95S2ZjFX/q+dYa/9ZIImQRNjjSzUQ1MZqOppFYneflyOgcKoa7EMV6tM42alOOO1u0/hTjjdtz+GfBKXYj9oembsm+R1iGNRjEa1MmpYk4yX7S4vbtOwAHDkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFgAhgmo6ef8WJqrxtmaEuBxLnDI5i8FzOsCyuW9Vlcl6+pVuXCKtl9lGyJ4OzNSSCaK/WRvb5oW8WLq8ifyvrybR7Uz61I+tS2yUlPLm+FjvG2ZrvwijdoxzF8HFkcmq2OXH6Vv61Gv/k7zsDg92WRPmYlwLhUr/k/md/Xq7/tUcb19SU+szrrgTv7yn+T+ZlgwSNjkWWRX+FrIJzVLcmmmzhL3yULFk10TyN08tajGo1qWRE04HowXmJnbzrTudgAOUAAAAGvWVcdJHtvzXc1N5NazaSImfDOqoiXXJOJzKnEnSP8AZ6Bu29dXcDUT2zFXrdVjgvyOvSUkVJHsxtuu93Eu61p7W6rSPLDQ4ckC9bMqyTLnddxsVNSymiV8i+Sb1FRUsp49p2ar3UTVVNenpXyy+1Vff9xm5hHmfMuffmUU1O+eVKmrzd7jPhNirnbT075HLmmSJczOVGtVVWyIV+okkxOsSOK/VtXJf9SaR2ncuqR2nc+mGjpZK6pVy5NvdziyRRsijaxiWa1MkPFLTspoUjjTJPme5ZWQxq+Rdlqbxe83nUGS83nUEsrIo3SSLZqFbqZ5sRqka1q2vZreBmqJ58UnSKFqpGmiL+p1qChZRx5dp66uU7jWOu/y7rrHXc+3nD6BlJHfWRdVN0Az2tNp3Ki1ptO5a1fP1FK5yd5cmp4mPC6X2eDaen3kmblPT4/aaxquT7uHROLjbOpt1rqE71GgAFbkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwTUkE0iSSx7Tm6XUzgmJmDaGojUsiWTdkeZZEijV7s0TROJ7IVEVc0TIRKGpBTukl9pqc3+62+TENzcDn4jVuZanp+1O/5HcbtLusdp01sSqn1MqUdNnde0qHQoKNlJCjW5uXvLxPOH0TaWO7u1I7Nym0umSZnV7eOsOr28da+mOonjp4lkldZE+ZyuoqcTk25rxQX7Ld6nT9ma+TrZe25NEXRORnIreKevbmtuvpiggjp40ZE2yfqZQCuZmZc7mZAumQBA8tajW2Q9ACfIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIa1ZU9QxGsTblf3WoeKKj6lVmlXanfm5eHgZo4EbI6Vy7Ujsr8E4GYsm2o1DrYACtAAAgAASAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/2Q==', '232332323', 1, 0, 0, '2023-05-09 22:25:26.000000', 0, '2024-10-25 15:40:31.000000', b'0', 24);\nINSERT INTO `m_user` VALUES (3, 1834763884364705792, 'dongdong', '5023047047573fd83145245f5201eeef', '8gb1qw', '东东', '东东', '11111111111', '11111111111', 1, NULL, NULL, 1, 1834763883194494976, 0, '2024-09-14 09:19:33.047847', 0, '2024-10-23 22:31:21.000000', b'0', 4);\nINSERT INTO `m_user` VALUES (4, 1834817021968625664, 'dongdong1', 'c0048f46ccbc7fd4079ef298e5e066db', 'uwgrap', '东东1号', '东东1号', '111', '111', 1, NULL, '1', 1, 1834763883194494976, 1834763884364705792, '2024-09-14 12:50:42.041340', 0, '2024-10-23 22:31:39.000000', b'0', 2);\nINSERT INTO `m_user` VALUES (5, 1836055978990497792, 'ceshi111', '41e43c75e3426adb407c05000f6f8501', 'et1t8b', '测试租户111', '测试租户111', '111', '111', 1, NULL, NULL, 1, 1836055976968843264, 0, '2024-09-17 22:53:52.392398', 1836055978990497792, '2024-09-17 23:17:41.221513', b'0', 1);\nINSERT INTO `m_user` VALUES (6, 1836056110997827584, 'ceshi222', 'c709119dd102a77fe92b972578ac226f', '5gxkja', '测试租户222', '测试租户222', '111', '111', 1, NULL, NULL, 1, 1836056110565814272, 0, '2024-09-17 22:54:23.866476', 0, '2024-09-17 22:54:23.866476', b'0', 0);\nINSERT INTO `m_user` VALUES (7, 1836059921829097472, 'ceshi111-1', '2dcc424ea713c122ae0dcf10ff90406f', 'sm74mg', 'ceshi111-1', '111', 'ceshi111-1', 'ceshi111-1', 1, NULL, NULL, 1, 1836055976968843264, 1836055978990497792, '2024-09-17 23:09:32.433005', 1836055978990497792, '2024-09-17 23:18:01.576329', b'0', 1);\nINSERT INTO `m_user` VALUES (8, 1836060007078326272, 'ceshi111-2', '8ffd827af99a14c8802e54ae1df6a5f2', '2l9538', 'ceshi111-2', 'ceshi111-2', 'ceshi111-2', 'ceshi111-2', 1, NULL, NULL, 1, 1836055976968843264, 1836055978990497792, '2024-09-17 23:09:52.749490', 1836055978990497792, '2024-09-17 23:18:09.068931', b'0', 1);\nINSERT INTO `m_user` VALUES (9, 1846431773870239744, 'test333', '32f7b6bf534219c83cfa97b2d3f53d53', 'qADvVl', '测试租户333', '测试租户333', NULL, '13888888888', 1, NULL, NULL, 1, 1846431773459197952, 0, '2024-10-16 14:03:34.624580', 0, '2024-10-16 14:03:34.624580', b'0', 0);\nINSERT INTO `m_user` VALUES (10, 1890404237300404224, 'test444', '3dc6fa0fbe21ce106f5b7bf078751558', 'B6pgBd', '测试租户444', '测试租户444', '123456@qq.com', '123456', 1, NULL, NULL, 1, 1890404237296209920, 0, '2025-02-14 22:14:26.000000', 0, '2025-02-14 22:14:26.000000', b'1', 0);\nINSERT INTO `m_user` VALUES (11, 1890405820369145856, 'test4', '6a9b52b45fbe14ec8fc34fb45f8c52df', 'mOUdML', '测试租户4', '4', '4', '4', 1, NULL, NULL, 1, 1890405820364951552, 0, '2025-02-14 22:20:43.000000', 0, '2025-02-14 22:20:43.000000', b'1', 0);\nINSERT INTO `m_user` VALUES (14, 1890406866181099521, 'test44', 'a8159b654d7cf51429c77eebca7b3afa', 'tqUSaV', '租户44', '租户44', '1', '1', 1, NULL, NULL, 1, 1890406866181099520, 0, '2025-02-14 22:24:53.000000', 0, '2025-02-14 22:24:53.000000', b'0', 0);\n\n-- ----------------------------\n-- Table structure for m_user_dept\n-- ----------------------------\nDROP TABLE IF EXISTS `m_user_dept`;\nCREATE TABLE `m_user_dept`  (\n  `id` bigint(0) NOT NULL AUTO_INCREMENT,\n  `user_id` bigint(0) NOT NULL COMMENT '用户ID',\n  `dept_id` bigint(0) NOT NULL COMMENT '部门ID',\n  PRIMARY KEY (`id`) USING BTREE,\n  INDEX `idx_dept_id`(`dept_id`) USING BTREE COMMENT '部门ID索引',\n  INDEX `idx_user_id`(`user_id`) USING BTREE COMMENT '用户ID索引'\n) ENGINE = InnoDB AUTO_INCREMENT = 70 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户与岗位关联表' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of m_user_dept\n-- ----------------------------\nINSERT INTO `m_user_dept` VALUES (19, 1679006018092986368, 1677964231673425920);\nINSERT INTO `m_user_dept` VALUES (20, 1679006018092986368, 1677964029214371840);\nINSERT INTO `m_user_dept` VALUES (21, 1679006018092986368, 1686021621496999936);\nINSERT INTO `m_user_dept` VALUES (22, 1679006018092986368, 1677964435873116160);\nINSERT INTO `m_user_dept` VALUES (32, 1836055978990497792, 1836060155556687872);\nINSERT INTO `m_user_dept` VALUES (33, 1836055978990497792, 1836060210875363328);\nINSERT INTO `m_user_dept` VALUES (34, 1836055978990497792, 1836060257859956736);\nINSERT INTO `m_user_dept` VALUES (35, 1836059921829097472, 1836060155556687872);\nINSERT INTO `m_user_dept` VALUES (36, 1836059921829097472, 1836060210875363328);\nINSERT INTO `m_user_dept` VALUES (37, 1836060007078326272, 1836060155556687872);\nINSERT INTO `m_user_dept` VALUES (38, 1836060007078326272, 1836060257859956736);\nINSERT INTO `m_user_dept` VALUES (60, 1834817021968625664, 1834774513448329216);\nINSERT INTO `m_user_dept` VALUES (61, 1834817021968625664, 1834773502369406976);\nINSERT INTO `m_user_dept` VALUES (62, 0, 1677964682431082496);\nINSERT INTO `m_user_dept` VALUES (63, 0, 1677964531410972672);\nINSERT INTO `m_user_dept` VALUES (64, 0, 1686349179535036416);\nINSERT INTO `m_user_dept` VALUES (65, 0, 1677964231673425920);\nINSERT INTO `m_user_dept` VALUES (66, 0, 1677964029214371840);\nINSERT INTO `m_user_dept` VALUES (67, 0, 1686019822887170048);\nINSERT INTO `m_user_dept` VALUES (68, 0, 1686349332929122304);\nINSERT INTO `m_user_dept` VALUES (69, 0, 1686021776396840960);\n\n-- ----------------------------\n-- Table structure for m_user_post\n-- ----------------------------\nDROP TABLE IF EXISTS `m_user_post`;\nCREATE TABLE `m_user_post`  (\n  `id` bigint(0) NOT NULL AUTO_INCREMENT,\n  `user_id` bigint(0) NOT NULL COMMENT '用户ID',\n  `post_id` bigint(0) NOT NULL COMMENT '岗位ID',\n  PRIMARY KEY (`id`) USING BTREE,\n  INDEX `post_id_idx`(`post_id`) USING BTREE COMMENT '岗位ID索引',\n  INDEX `user_id_idx`(`user_id`) USING BTREE COMMENT '用户ID索引'\n) ENGINE = InnoDB AUTO_INCREMENT = 26 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户与岗位关联表 1用户-N岗位' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of m_user_post\n-- ----------------------------\nINSERT INTO `m_user_post` VALUES (14, 1679006018092986368, 1676046492030717952);\nINSERT INTO `m_user_post` VALUES (17, 1836055978990497792, 1836060483333156864);\nINSERT INTO `m_user_post` VALUES (18, 1836059921829097472, 1836060404593487872);\nINSERT INTO `m_user_post` VALUES (19, 1836060007078326272, 1836060433332858880);\nINSERT INTO `m_user_post` VALUES (24, 0, 1676046492030717952);\nINSERT INTO `m_user_post` VALUES (25, 0, 1676044813214400512);\n\n-- ----------------------------\n-- Table structure for m_user_role\n-- ----------------------------\nDROP TABLE IF EXISTS `m_user_role`;\nCREATE TABLE `m_user_role`  (\n  `id` bigint(0) NOT NULL AUTO_INCREMENT,\n  `user_id` bigint(0) NOT NULL COMMENT '用户ID',\n  `role_id` bigint(0) NOT NULL COMMENT '角色ID',\n  PRIMARY KEY (`id`) USING BTREE,\n  INDEX `role_id_idx`(`role_id`) USING BTREE COMMENT '角色ID索引',\n  INDEX `user_id_idx`(`user_id`) USING BTREE COMMENT '用户ID索引'\n) ENGINE = InnoDB AUTO_INCREMENT = 31 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户与角色关联表 1用户-N角色' ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Records of m_user_role\n-- ----------------------------\nINSERT INTO `m_user_role` VALUES (9, 1679006018092986368, 1677966412497596416);\nINSERT INTO `m_user_role` VALUES (16, 1836056110997827584, 1836056110565814273);\nINSERT INTO `m_user_role` VALUES (19, 1836055978990497792, 1836055976973037568);\nINSERT INTO `m_user_role` VALUES (20, 1836059921829097472, 1836057975659556864);\nINSERT INTO `m_user_role` VALUES (21, 1836060007078326272, 1836057975659556864);\nINSERT INTO `m_user_role` VALUES (24, 1846431773870239744, 1846431773459197953);\nINSERT INTO `m_user_role` VALUES (25, 1834763884364705792, 1834763883194494977);\nINSERT INTO `m_user_role` VALUES (26, 1834817021968625664, 1834790150900002816);\nINSERT INTO `m_user_role` VALUES (27, 0, 1671337763855073280);\nINSERT INTO `m_user_role` VALUES (30, 1890406866181099521, 1890406866659250176);\n\nSET FOREIGN_KEY_CHECKS = 1;\n"
  }
]